* [#786] fully disable long press * [#786] added hook to handle tap separatly from slide
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import { setIsMobileSearchOpen } from '@/features/Calendars/CalendarSlice'
|
||||
import EventPopover from '@/features/Events/EventModal'
|
||||
import EventPreviewModal from '@/features/Events/EventPreview'
|
||||
import { CalendarEvent } from '@/features/Events/EventsTypes'
|
||||
@@ -32,20 +33,20 @@ import { EventErrorSnackbar } from '../Error/ErrorSnackbar'
|
||||
import { EventErrorHandler } from '../Error/EventErrorHandler'
|
||||
import { EditModeDialog } from '../Event/EditModeDialog'
|
||||
import { Menubar, MenubarProps } from '../Menubar/Menubar'
|
||||
import { TimezoneSelector } from '../Timezone/TimezoneSelector'
|
||||
import './Calendar.styl'
|
||||
import './CustomCalendar.styl'
|
||||
import { useCalendarEventHandlers } from './hooks/useCalendarEventHandlers'
|
||||
import { useCalendarViewHandlers } from './hooks/useCalendarViewHandlers'
|
||||
import { TimezoneSelector } from '../Timezone/TimezoneSelector'
|
||||
import Sidebar from './Sidebar/SideBar'
|
||||
import TempSearchDialog from './TempSearchDialog'
|
||||
import { useTouchListener } from './useTouchListener'
|
||||
import {
|
||||
eventToFullCalendarFormat,
|
||||
extractEvents,
|
||||
updateSlotLabelVisibility
|
||||
} from './utils/calendarUtils'
|
||||
import { CALENDAR_VIEWS } from './utils/constants'
|
||||
import Sidebar from './Sidebar/SideBar'
|
||||
import TempSearchDialog from './TempSearchDialog'
|
||||
import { setIsMobileSearchOpen } from '@/features/Calendars/CalendarSlice'
|
||||
import ViewMoreEvents from './ViewMoreEvents'
|
||||
|
||||
const localeMap: Record<string, LocaleInput | undefined> = {
|
||||
@@ -381,6 +382,13 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
}, [calendarRef])
|
||||
|
||||
const { t, lang } = useI18n()
|
||||
const calendarWrapperRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useTouchListener(
|
||||
eventHandlers.handleDateSelect,
|
||||
isTablet || isMobile,
|
||||
calendarWrapperRef
|
||||
)
|
||||
|
||||
return (
|
||||
<main
|
||||
@@ -405,7 +413,7 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
<div className="calendar">
|
||||
<ImportAlert />
|
||||
{menubarProps?.isIframe && <Menubar {...menubarProps} />}
|
||||
{isTablet && (
|
||||
{(isTablet || isMobile) && (
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label={t('event.createEvent')}
|
||||
@@ -422,6 +430,7 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
</Fab>
|
||||
)}
|
||||
{view === 'calendar' && (
|
||||
<div ref={calendarWrapperRef} style={{ height: '100%' }}>
|
||||
<FullCalendar
|
||||
key={hiddenDays.join(',')}
|
||||
ref={ref => {
|
||||
@@ -449,7 +458,6 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
timeZone={timezone}
|
||||
height="100%"
|
||||
select={eventHandlers.handleDateSelect}
|
||||
longPressDelay={100}
|
||||
nowIndicator
|
||||
slotLabelClassNames={arg => [
|
||||
updateSlotLabelVisibility(new Date(), arg, timezone)
|
||||
@@ -457,7 +465,9 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
nowIndicatorContent={viewHandlers.handleNowIndicatorContent}
|
||||
headerToolbar={false}
|
||||
views={{
|
||||
timeGridWeek: { titleFormat: { month: 'long', year: 'numeric' } }
|
||||
timeGridWeek: {
|
||||
titleFormat: { month: 'long', year: 'numeric' }
|
||||
}
|
||||
}}
|
||||
dayMaxEvents={true}
|
||||
moreLinkClick={handleMoreLinkClick}
|
||||
@@ -487,7 +497,9 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
)}
|
||||
<TimezoneSelector
|
||||
value={timezone}
|
||||
referenceDate={calendarRef.current?.getDate() ?? new Date()}
|
||||
referenceDate={
|
||||
calendarRef.current?.getDate() ?? new Date()
|
||||
}
|
||||
onChange={(newTimezone: string) =>
|
||||
dispatch(setTimeZone(newTimezone))
|
||||
}
|
||||
@@ -507,7 +519,8 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
arg.isToday ? 'current-date' : ''
|
||||
}`}
|
||||
>
|
||||
{arg.dayNumberText === '1' ? month : ''} {arg.dayNumberText}
|
||||
{arg.dayNumberText === '1' ? month : ''}{' '}
|
||||
{arg.dayNumberText}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -594,6 +607,7 @@ const CalendarApp: React.FC<CalendarAppProps> = ({
|
||||
eventContent={viewHandlers.handleEventContent}
|
||||
eventDidMount={viewHandlers.handleEventDidMount}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{view === 'search' && <SearchResultsPage />}
|
||||
<EventPopover
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { DateSelectArg } from '@fullcalendar/core'
|
||||
import { RefObject, useEffect } from 'react'
|
||||
|
||||
const MOVE_THRESHOLD = 10
|
||||
const TAP_DURATION = 200
|
||||
|
||||
function isTapGesture(dx: number, dy: number, duration: number): boolean {
|
||||
return dx < MOVE_THRESHOLD && dy < MOVE_THRESHOLD && duration < TAP_DURATION
|
||||
}
|
||||
|
||||
function getTimeAtPoint(x: number, y: number): string | null {
|
||||
const el = document.elementFromPoint(x, y)
|
||||
return (
|
||||
(el as HTMLElement)?.closest('[data-time]')?.getAttribute('data-time') ??
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
function getDateAtPoint(x: number): string | null {
|
||||
const col = Array.from(document.querySelectorAll('[data-date]')).find(el => {
|
||||
const rect = el.getBoundingClientRect()
|
||||
return x >= rect.left && x <= rect.right
|
||||
})
|
||||
return col?.getAttribute('data-date') ?? null
|
||||
}
|
||||
|
||||
function buildSelectArg(date: string, time: string): DateSelectArg {
|
||||
const start = new Date(`${date}T${time}`)
|
||||
const end = new Date(start.getTime() + 30 * 60 * 1000)
|
||||
return { start, end, allDay: false } as DateSelectArg
|
||||
}
|
||||
|
||||
export function useTouchListener(
|
||||
handleDateSelect: (selectInfo: DateSelectArg | null) => void,
|
||||
isTouch: boolean,
|
||||
wrapperRef: RefObject<HTMLDivElement>
|
||||
): void {
|
||||
useEffect(() => {
|
||||
const el = wrapperRef.current
|
||||
if (!el || !isTouch) return
|
||||
|
||||
let startX = 0,
|
||||
startY = 0,
|
||||
startTime = 0
|
||||
|
||||
const onTouchStart = (e: TouchEvent): void => {
|
||||
startX = e.touches[0].clientX
|
||||
startY = e.touches[0].clientY
|
||||
startTime = Date.now()
|
||||
}
|
||||
|
||||
const onTouchEnd = (e: TouchEvent): void => {
|
||||
const touch = e.changedTouches[0]
|
||||
const dx = Math.abs(touch.clientX - startX)
|
||||
const dy = Math.abs(touch.clientY - startY)
|
||||
|
||||
if (!isTapGesture(dx, dy, Date.now() - startTime)) return
|
||||
|
||||
const time = getTimeAtPoint(touch.clientX, touch.clientY)
|
||||
const date = getDateAtPoint(touch.clientX)
|
||||
|
||||
if (time && date) {
|
||||
handleDateSelect(buildSelectArg(date, time))
|
||||
}
|
||||
}
|
||||
|
||||
el.addEventListener('touchstart', onTouchStart, { passive: true })
|
||||
el.addEventListener('touchend', onTouchEnd, { passive: true })
|
||||
|
||||
return (): void => {
|
||||
el.removeEventListener('touchstart', onTouchStart)
|
||||
el.removeEventListener('touchend', onTouchEnd)
|
||||
}
|
||||
}, [handleDateSelect, isTouch, wrapperRef])
|
||||
}
|
||||
Reference in New Issue
Block a user