fix: add keyboard shortcuts for event creation and management in Calendar components
This commit is contained in:
parent
1e99192448
commit
557dc1f94c
@ -177,6 +177,7 @@
|
|||||||
:debounce="500"
|
:debounce="500"
|
||||||
:placeholder="__('Event title')"
|
:placeholder="__('Event title')"
|
||||||
@change="sync"
|
@change="sync"
|
||||||
|
@keyup.enter="saveEvent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between py-2.5 px-4.5 text-ink-gray-6">
|
<div class="flex justify-between py-2.5 px-4.5 text-ink-gray-6">
|
||||||
@ -372,7 +373,7 @@ import {
|
|||||||
CalendarActiveEvent as activeEvent,
|
CalendarActiveEvent as activeEvent,
|
||||||
createDocumentResource,
|
createDocumentResource,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, watch, h } from 'vue'
|
import { ref, computed, watch, h, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -664,5 +665,61 @@ function updateEvent(_e) {
|
|||||||
Object.assign(_event.value, _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 })
|
defineExpose({ updateEvent })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -113,6 +113,7 @@
|
|||||||
v-model="showEventPanel"
|
v-model="showEventPanel"
|
||||||
v-model:event="event"
|
v-model:event="event"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
@new="newEvent"
|
||||||
@save="saveEvent"
|
@save="saveEvent"
|
||||||
@edit="editDetails"
|
@edit="editDetails"
|
||||||
@delete="deleteEvent"
|
@delete="deleteEvent"
|
||||||
@ -139,7 +140,7 @@ import {
|
|||||||
CalendarActiveEvent as activeEvent,
|
CalendarActiveEvent as activeEvent,
|
||||||
call,
|
call,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, onBeforeUnmount, ref, computed } from 'vue'
|
||||||
|
|
||||||
const { user } = sessionStore()
|
const { user } = sessionStore()
|
||||||
const { $dialog } = globalStore()
|
const { $dialog } = globalStore()
|
||||||
@ -189,7 +190,7 @@ const event = ref({})
|
|||||||
const mode = ref('')
|
const mode = ref('')
|
||||||
|
|
||||||
const isCreateDisabled = computed(() =>
|
const isCreateDisabled = computed(() =>
|
||||||
['edit', 'new-event', 'duplicate-event'].includes(mode.value),
|
['edit', 'new', 'duplicate'].includes(mode.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Temp event helpers
|
// Temp event helpers
|
||||||
@ -325,6 +326,47 @@ onMounted(() => {
|
|||||||
showEventPanel.value = false
|
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) {
|
function showDetails(e, reloadEvent = false) {
|
||||||
openEvent(e, 'details', reloadEvent)
|
openEvent(e, 'details', reloadEvent)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user