From 72c492f3bcd10924e12ca978384cc67aedc9a40e Mon Sep 17 00:00:00 2001 From: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Date: Thu, 28 May 2026 13:10:59 +0000 Subject: [PATCH] refactor(nc-gui): extract LTAR list key-nav into shared composable --- .../virtual-cell/components/LinkedItems.vue | 78 ++++----------- .../virtual-cell/components/UnLinkedItems.vue | 82 ++++------------ .../nc-gui/composables/useLTARListKeyNav.ts | 95 +++++++++++++++++++ 3 files changed, 128 insertions(+), 127 deletions(-) create mode 100644 packages/nc-gui/composables/useLTARListKeyNav.ts diff --git a/packages/nc-gui/components/virtual-cell/components/LinkedItems.vue b/packages/nc-gui/components/virtual-cell/components/LinkedItems.vue index e5b22dbfc1..f04817090c 100644 --- a/packages/nc-gui/components/virtual-cell/components/LinkedItems.vue +++ b/packages/nc-gui/components/virtual-cell/components/LinkedItems.vue @@ -48,6 +48,8 @@ const reloadViewDataTrigger = inject(ReloadViewDataHookInj, createEventHook()) const filterQueryRef = ref() +const scrollContainerRef = ref() + const { isDataReadOnly } = useRoles() const { isSharedBase } = storeToRefs(useBase()) @@ -347,51 +349,8 @@ watch([filterQueryRef, isDataExist], () => { } }) -function focusListItemByIndex(idx: number) { - const items = scrollContainerRef.value - ? Array.from(scrollContainerRef.value.querySelectorAll('[data-testid="nc-child-list-item"]')) - : [] - const wrapper = items[idx] - const focusable = wrapper?.querySelector('[tabindex="0"]') ?? wrapper - focusable?.focus() -} - -function linkedShortcuts(e: KeyboardEvent) { - if (e.key === 'Escape') { - vModel.value = false - return - } - - if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { - const target = e.target as HTMLElement | null - const currentWrapper = target?.closest('[data-testid="nc-child-list-item"]') - if (!currentWrapper || !scrollContainerRef.value) return - - e.preventDefault() - - const items = Array.from(scrollContainerRef.value.querySelectorAll('[data-testid="nc-child-list-item"]')) - const idx = items.indexOf(currentWrapper) - if (idx === -1) return - - if (e.key === 'ArrowDown') { - if (idx < items.length - 1) focusListItemByIndex(idx + 1) - } else if (e.key === 'ArrowUp') { - if (idx === 0) filterQueryRef.value?.focus() - else focusListItemByIndex(idx - 1) - } - return - } - - if (!expandedFormDlg.value && e.key !== 'Tab' && e.key !== 'Shift' && e.key !== 'Enter' && e.key !== ' ') { - try { - filterQueryRef.value?.focus() - } catch (e) {} - } -} - onMounted(() => { loadRelatedTableMeta() - window.addEventListener('keydown', linkedShortcuts) // Load initial chunk for virtual scroll fetchChildrenChunk(0) @@ -403,8 +362,6 @@ onMounted(() => { }, 100) }) -const scrollContainerRef = ref() - const ROW_VIRTUAL_MARGIN = 5 const rowSlice = reactive({ start: 0, end: 0 }) @@ -467,7 +424,6 @@ onUnmounted(() => { resetChildrenListOffsetCount() resetChildrenCache() childrenListPagination.query = '' - window.removeEventListener('keydown', linkedShortcuts) }) const onFilterChange = () => { @@ -479,23 +435,21 @@ const onFilterChange = () => { const isSearchInputFocused = ref(false) -const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - if (!childrenListPagination.query) emit('escape') - filterQueryRef.value?.blur() - } else if (e.key === 'Enter') { +const { handleSearchKeydown: handleKeyDown } = useLTARListKeyNav({ + scrollContainerRef, + filterQueryRef, + itemTestId: 'nc-child-list-item', + expandedFormDlg, + closeModal: () => { + vModel.value = false + }, + getQuery: () => childrenListPagination.query, + onEscapeEmptyQuery: () => emit('escape'), + onEnterWithQuery: () => { const list = childrenList.value?.list ?? state.value?.[colTitle.value] - - if (childrenListPagination.query && ncIsArray(list) && list.length) { - linkOrUnLink(list[0], '0') - } - } else if (e.key === 'ArrowDown') { - e.preventDefault() - focusListItemByIndex(0) - } else if (e.key === 'ArrowUp') { - e.preventDefault() - } -} + if (ncIsArray(list) && list.length) linkOrUnLink(list[0], '0') + }, +})