fix: add keyboard shortcuts for event creation and management in Calendar components

This commit is contained in:
Shariq Ansari 2025-09-02 21:26:30 +05:30
parent 1e99192448
commit 557dc1f94c
2 changed files with 102 additions and 3 deletions

View File

@ -177,6 +177,7 @@
:debounce="500"
:placeholder="__('Event title')"
@change="sync"
@keyup.enter="saveEvent"
/>
</div>
<div class="flex justify-between py-2.5 px-4.5 text-ink-gray-6">
@ -372,7 +373,7 @@ import {
CalendarActiveEvent as activeEvent,
createDocumentResource,
} from 'frappe-ui'
import { ref, computed, watch, h } from 'vue'
import { ref, computed, watch, h, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
@ -664,5 +665,61 @@ function updateEvent(_e) {
Object.assign(_event.value, _e)
}
// Keyboard shortcuts
function isTypingEvent(e) {
const el = e.target
if (!el) return false
const tag = el.tagName
const editable = el.isContentEditable
return (
editable ||
tag === 'INPUT' ||
tag === 'TEXTAREA' ||
tag === 'SELECT' ||
(el.closest && el.closest('[contenteditable="true"]'))
)
}
function keydownHandler(e) {
if (!show.value) return
// Esc always closes the panel
if (e.key === 'Escape') {
e.preventDefault()
close()
return
}
if (!['details', 'edit'].includes(props.mode)) return
if (isTypingEvent(e)) return
// Delete (no modifier) -> delete event
if (e.key === 'Delete' || e.key === 'Backspace') {
// Avoid capturing Backspace if it would navigate away when no focus
e.preventDefault()
deleteEvent()
return
}
// Cmd/Ctrl + D -> duplicate event
if (
(e.metaKey || e.ctrlKey) &&
!e.shiftKey &&
!e.altKey &&
e.key.toLowerCase() === 'd'
) {
e.preventDefault()
duplicateEvent()
}
}
onMounted(() => {
window.addEventListener('keydown', keydownHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', keydownHandler)
})
defineExpose({ updateEvent })
</script>

View File

@ -113,6 +113,7 @@
v-model="showEventPanel"
v-model:event="event"
:mode="mode"
@new="newEvent"
@save="saveEvent"
@edit="editDetails"
@delete="deleteEvent"
@ -139,7 +140,7 @@ import {
CalendarActiveEvent as activeEvent,
call,
} from 'frappe-ui'
import { onMounted, ref, computed } from 'vue'
import { onMounted, onBeforeUnmount, ref, computed } from 'vue'
const { user } = sessionStore()
const { $dialog } = globalStore()
@ -189,7 +190,7 @@ const event = ref({})
const mode = ref('')
const isCreateDisabled = computed(() =>
['edit', 'new-event', 'duplicate-event'].includes(mode.value),
['edit', 'new', 'duplicate'].includes(mode.value),
)
// Temp event helpers
@ -325,6 +326,47 @@ onMounted(() => {
showEventPanel.value = false
})
// Global shortcut: Cmd/Ctrl + E -> new event (when not already creating/editing)
function isTypingEvent(e) {
const el = e.target
if (!el) return false
const tag = el.tagName
const editable = el.isContentEditable
return (
editable ||
tag === 'INPUT' ||
tag === 'TEXTAREA' ||
tag === 'SELECT' ||
(el.closest && el.closest('[contenteditable="true"]'))
)
}
function calendarKeydown(e) {
if (isTypingEvent(e)) return
if (
(e.metaKey || e.ctrlKey) &&
!e.shiftKey &&
!e.altKey &&
e.key.toLowerCase() === 'e'
) {
if (isCreateDisabled.value) return
e.preventDefault()
newEvent({
date: dayjs().format('YYYY-MM-DD'),
time: dayjs().format('HH:mm'),
isFullDay: false,
})
}
}
onMounted(() => {
window.addEventListener('keydown', calendarKeydown)
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', calendarKeydown)
})
function showDetails(e, reloadEvent = false) {
openEvent(e, 'details', reloadEvent)
}