diff --git a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
index 8079cd6bf8..d3cf53b122 100644
--- a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
+++ b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
@@ -15,6 +15,9 @@ const dialogShow = computed({
const renderCmdOrCtrlKey = () => {
return isMac() ? '⌘' : 'CTRL'
}
+const renderAltOrOptlKey = () => {
+ return isMac() ? '⌥' : 'ALT'
+}
const shortcutList = [
{
@@ -197,6 +200,22 @@ const shortcutList = [
keys: [renderCmdOrCtrlKey(), 'Enter'],
behaviour: 'Save current expanded form item',
},
+ {
+ keys: [renderAltOrOptlKey(), '→'],
+ behaviour: 'Switch to next row',
+ },
+ {
+ keys: [renderAltOrOptlKey(), '←'],
+ behaviour: 'Switch to previous row',
+ },
+ {
+ keys: [renderAltOrOptlKey(), 'S'],
+ behaviour: 'Save current expanded form item',
+ },
+ {
+ keys: [renderAltOrOptlKey(), 'N'],
+ behaviour: 'Create a new row',
+ },
],
},
]
diff --git a/packages/nc-gui/components/general/ShortcutLabel.vue b/packages/nc-gui/components/general/ShortcutLabel.vue
new file mode 100644
index 0000000000..2e861cd948
--- /dev/null
+++ b/packages/nc-gui/components/general/ShortcutLabel.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+ {{ getLabel(key) }}
+
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue
index 87965126a7..d358804fa9 100644
--- a/packages/nc-gui/components/smartsheet/Grid.vue
+++ b/packages/nc-gui/components/smartsheet/Grid.vue
@@ -118,6 +118,7 @@ const {
selectedAllRecords,
removeRowIfNew,
navigateToSiblingRow,
+ getExpandedRowIndex,
} = useViewData(meta, view, xWhere)
const { getMeta } = useMetas()
@@ -980,6 +981,8 @@ const closeAddColumnDropdown = () => {
:row-id="routeQuery.rowId"
:view="view"
show-next-prev-icons
+ :first-row="getExpandedRowIndex() === 0"
+ :last-row="getExpandedRowIndex() === data.length - 1"
@next="navigateToSiblingRow(NavigateDir.NEXT)"
@prev="navigateToSiblingRow(NavigateDir.PREV)"
/>
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
index 80ed48f93d..f453f8f8db 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
@@ -18,7 +18,7 @@ const route = useRoute()
const { meta, isSqlView } = useSmartsheetStoreOrThrow()
-const { commentsDrawer, displayValue, primaryKey, save: _save, loadRow } = useExpandedFormStoreOrThrow()
+const { commentsDrawer, displayValue, primaryKey, save: _save, loadRow, saveRowAndStay } = useExpandedFormStoreOrThrow()
const { isNew, syncLTARRefs, state } = useSmartsheetRowStoreOrThrow()
@@ -26,8 +26,6 @@ const { isUIAllowed } = useUIPermission()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
-const saveRowAndStay = ref(0)
-
const save = async () => {
if (isNew.value) {
const data = await _save(state.value)
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
index 20eae1d22b..f72807f0cf 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
@@ -22,6 +22,7 @@ import {
useVModel,
watch,
} from '#imports'
+import { useActiveKeyupListener } from '~/composables/useSelectedCellKeyupListener'
import type { Row } from '~/lib'
interface Props {
@@ -34,12 +35,16 @@ interface Props {
rowId?: string
view?: ViewType
showNextPrevIcons?: boolean
+ firstRow?: boolean
+ lastRow?: boolean
}
const props = defineProps()
const emits = defineEmits(['update:modelValue', 'cancel', 'next', 'prev'])
+const key = ref(0)
+
const { t } = useI18n()
const row = ref(props.row)
@@ -64,7 +69,16 @@ const isKanban = inject(IsKanbanInj, ref(false))
provide(MetaInj, meta)
-const { commentsDrawer, changedColumns, state: rowState, isNew, loadRow, save } = useProvideExpandedFormStore(meta, row)
+const {
+ commentsDrawer,
+ changedColumns,
+ state: rowState,
+ isNew,
+ loadRow,
+ saveRowAndStay,
+ syncLTARRefs,
+ save,
+} = useProvideExpandedFormStore(meta, row)
const duplicatingRowInProgress = ref(false)
@@ -157,6 +171,92 @@ const cellWrapperEl = ref()
onMounted(() => {
setTimeout(() => (cellWrapperEl.value?.querySelector('input,select,textarea') as HTMLInputElement)?.focus())
})
+
+const addNewRow = () => {
+ setTimeout(async () => {
+ row.value = {
+ row: {},
+ oldRow: {},
+ rowMeta: { new: true },
+ }
+ rowState.value = {}
+ key.value++
+ isExpanded.value = true
+ }, 500)
+}
+
+// attach keyboard listeners to switch between rows
+// using alt + left/right arrow keys
+useActiveKeyupListener(
+ isExpanded,
+ async (e: KeyboardEvent) => {
+ if (!e.altKey) return
+ if (e.key === 'ArrowLeft') {
+ e.stopPropagation()
+ emits('prev')
+ } else if (e.key === 'ArrowRight') {
+ e.stopPropagation()
+ emits('next')
+ }
+ // on alt + s save record
+ else if (e.code === 'KeyS') {
+ // remove focus from the active input if any
+ document.activeElement?.blur()
+
+ e.stopPropagation()
+
+ if (isNew.value) {
+ const data = await save(rowState.value)
+ await syncLTARRefs(data)
+ reloadHook?.trigger(null)
+ } else {
+ await save()
+ reloadHook?.trigger(null)
+ }
+ if (!saveRowAndStay.value) {
+ onClose()
+ }
+ // on alt + n create new record
+ } else if (e.code === 'KeyN') {
+ // remove focus from the active input if any to avoid unwanted input
+ ;(document.activeElement as HTMLInputElement)?.blur?.()
+
+ if (changedColumns.value.size > 0) {
+ await Modal.confirm({
+ title: 'Do you want to save the changes?',
+ okText: 'Save',
+ cancelText: 'Discard',
+ onOk: async () => {
+ await save()
+ reloadHook?.trigger(null)
+ addNewRow()
+ },
+ onCancel: () => {
+ addNewRow()
+ },
+ })
+ } else if (isNew.value) {
+ await Modal.confirm({
+ title: 'Do you want to save the record?',
+ okText: 'Save',
+ cancelText: 'Discard',
+ onOk: async () => {
+ const data = await save(rowState.value)
+ await syncLTARRefs(data)
+ reloadHook?.trigger(null)
+ addNewRow()
+ },
+ onCancel: () => {
+ addNewRow()
+ },
+ })
+ } else {
+ addNewRow()
+ }
+ }
+ },
+ { immediate: true },
+)