Browse Source

fix: Fixed issue with focus for Mulit select, single select, percent, Links, belongs to and disabled for Barcode and QRCode and minor fixes

pull/7280/head
Muhammed Mustafa 11 months ago
parent
commit
92e37cffe1
  1. 12
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 48
      packages/nc-gui/components/cell/Percent.vue
  3. 27
      packages/nc-gui/components/cell/SingleSelect.vue
  4. 3
      packages/nc-gui/components/cell/attachment/index.vue
  5. 1
      packages/nc-gui/components/nc/Button.vue
  6. 26
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  7. 9
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  8. 17
      packages/nc-gui/components/virtual-cell/Links.vue
  9. 2
      packages/nc-gui/components/virtual-cell/QrCode.vue
  10. 6
      packages/nc-gui/components/virtual-cell/barcode/Barcode.vue

12
packages/nc-gui/components/cell/MultiSelect.vue

@ -341,6 +341,16 @@ const selectedOpts = computed(() => {
return selectedOptions return selectedOptions
}, []) }, [])
}) })
const onKeyDown = (e: KeyboardEvent) => {
// Tab
if (e.key === 'Tab') {
isOpen.value = false
return
}
e.stopPropagation()
}
</script> </script>
<template> <template>
@ -403,7 +413,7 @@ const selectedOpts = computed(() => {
:class="{ 'caret-transparent': !hasEditRoles }" :class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell !min-w-200px ${isOpen ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-multi-select-cell !min-w-200px ${isOpen ? 'active' : ''}`"
@search="search" @search="search"
@keydown.stop @keydown="onKeyDown"
@focus="isOpen = true" @focus="isOpen = true"
@blur="isOpen = false" @blur="isOpen = false"
> >

48
packages/nc-gui/components/cell/Percent.vue

@ -20,6 +20,8 @@ const isEditColumn = inject(EditColumnInj, ref(false))
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
const wrapperRef = ref<HTMLElement>()
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value) => { set: (value) => {
@ -56,6 +58,18 @@ const onBlur = () => {
const onFocus = () => { const onFocus = () => {
cellFocused.value = true cellFocused.value = true
editEnabled.value = true
expandedEditEnabled.value = true
}
const onWrapperFocus = () => {
cellFocused.value = true
editEnabled.value = true
expandedEditEnabled.value = true
nextTick(() => {
wrapperRef.value?.querySelector('input')?.focus()
})
} }
const onMouseover = () => { const onMouseover = () => {
@ -67,10 +81,41 @@ const onMouseleave = () => {
expandedEditEnabled.value = false expandedEditEnabled.value = false
} }
} }
const onTabPress = (e: KeyboardEvent) => {
if (e.shiftKey) {
e.preventDefault()
// Shift + Tab does not work for percent cell
// so we manually focus on the last form item
const focusesNcCellIndex = Array.from(document.querySelectorAll('.nc-expanded-form-row .nc-data-cell')).findIndex((el) => {
return el.querySelector('.nc-filter-value-select') === wrapperRef.value
})
if (focusesNcCellIndex >= 0) {
const nodes = document.querySelectorAll('.nc-expanded-form-row .nc-data-cell')
for (let i = focusesNcCellIndex - 1; i >= 0; i--) {
const lastFormItem = nodes[i].querySelector('[tabindex="0"]') as HTMLElement
if (lastFormItem) {
lastFormItem.focus()
break
}
}
}
}
}
</script> </script>
<template> <template>
<div class="nc-filter-value-select w-full" @mouseover="onMouseover" @mouseleave="onMouseleave"> <div
ref="wrapperRef"
tabindex="0"
class="nc-filter-value-select w-full focus:outline-transparent"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
@focus="onWrapperFocus"
>
<input <input
v-if="(!isExpandedFormOpen && editEnabled) || (isExpandedFormOpen && expandedEditEnabled)" v-if="(!isExpandedFormOpen && editEnabled) || (isExpandedFormOpen && expandedEditEnabled)"
:ref="focus" :ref="focus"
@ -86,6 +131,7 @@ const onMouseleave = () => {
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.tab="onTabPress"
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

27
packages/nc-gui/components/cell/SingleSelect.vue

@ -215,6 +215,14 @@ const onKeydown = (e: KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.stopPropagation() e.stopPropagation()
} }
if (e.key === 'Escape') {
isOpen.value = false
setTimeout(() => {
aselect.value?.$el.querySelector('.ant-select-selection-search > input').focus()
}, 100)
}
} }
const onSelect = () => { const onSelect = () => {
@ -259,6 +267,17 @@ useEventListener(document, 'click', handleClose, true)
const selectedOpt = computed(() => { const selectedOpt = computed(() => {
return options.value.find((o) => o.value === vModel.value || o.value === vModel.value?.trim()) return options.value.find((o) => o.value === vModel.value || o.value === vModel.value?.trim())
}) })
watch(aselect, () => {
if (aselect.value) {
const inputDom = aselect.value.$el.querySelector('.ant-select-selection-search > input')
// Add tabindex="-1" to input element
if (inputDom) {
inputDom.setAttribute('tabindex', '-1')
}
}
})
</script> </script>
<template> <template>
@ -299,7 +318,7 @@ const selectedOpt = computed(() => {
</a-tag> </a-tag>
</div> </div>
<a-select <NcSelect
v-else v-else
ref="aselect" ref="aselect"
v-model:value="vModel" v-model:value="vModel"
@ -311,11 +330,11 @@ const selectedOpt = computed(() => {
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed" :disabled="readOnly || !editAllowed"
:show-arrow="hasEditRoles && !readOnly && active && vModel === null" :show-arrow="hasEditRoles && !readOnly && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell !min-w-200px ${isOpen && active ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="!isMobileMode && isOpen && active"
@select="onSelect" @select="onSelect"
@keydown="onKeydown($event)" @keydown="onKeydown($event)"
@search="search" @search="search"
@blur="isOpen = false"
> >
<a-select-option <a-select-option
v-for="op of options" v-for="op of options"
@ -361,7 +380,7 @@ const selectedOpt = computed(() => {
</div> </div>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </NcSelect>
</div> </div>
</template> </template>

3
packages/nc-gui/components/cell/attachment/index.vue

@ -180,7 +180,6 @@ const onImageClick = (item: any) => {
<template> <template>
<div <div
ref="attachmentCellRef" ref="attachmentCellRef"
tabindex="0"
:style="{ :style="{
height: isForm || isExpandedForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`, height: isForm || isExpandedForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
}" }"
@ -207,7 +206,9 @@ const onImageClick = (item: any) => {
:class="{ 'sm:(mx-auto px-4) xs:(w-full min-w-8)': !visibleItems.length }" :class="{ 'sm:(mx-auto px-4) xs:(w-full min-w-8)': !visibleItems.length }"
class="group cursor-pointer py-1 flex gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-none shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)" class="group cursor-pointer py-1 flex gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-none shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
data-testid="attachment-cell-file-picker-button" data-testid="attachment-cell-file-picker-button"
tabindex="0"
@click="open" @click="open"
@keydown.enter="open"
> >
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />

1
packages/nc-gui/components/nc/Button.vue

@ -84,6 +84,7 @@ useEventListener(NcButton, 'mousedown', () => {
xxsmall: size === 'xxsmall', xxsmall: size === 'xxsmall',
focused: isFocused, focused: isFocused,
}" }"
:tabindex="props.disabled ? -1 : 0"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
> >

26
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -90,6 +90,8 @@ const { isUIAllowed } = useRoles()
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value) const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value)
const expandedFormScrollWrapper = ref()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { addOrEditStackRow } = useKanbanViewStoreOrThrow() const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
@ -439,6 +441,13 @@ const preventModalStatus = computed({
}) })
const onIsExpandedUpdate = (v: boolean) => { const onIsExpandedUpdate = (v: boolean) => {
let isDropdownOpen = false
document.querySelectorAll('.ant-select-dropdown').forEach((el) => {
isDropdownOpen = isDropdownOpen || el.checkVisibility()
})
if (isDropdownOpen) return
if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) { if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) {
isExpanded.value = v isExpanded.value = v
} else if (!v) { } else if (!v) {
@ -451,6 +460,22 @@ const onIsExpandedUpdate = (v: boolean) => {
const isReadOnlyVirtualCell = (column: ColumnType) => { const isReadOnlyVirtualCell = (column: ColumnType) => {
return isRollup(column) || isFormula(column) || isBarcode(column) || isLookup(column) || isQrCode(column) return isRollup(column) || isFormula(column) || isBarcode(column) || isLookup(column) || isQrCode(column)
} }
// Small hack. We need to scroll to the bottom of the form after its mounted and back to top.
// So that tab to next row works properly, as otherwise browser will focus to save button
// when we reach to the bottom of the visual scrollable area, not the actual bottom of the form
watch([expandedFormScrollWrapper, isLoading], () => {
if (isMobileMode.value) return
if (expandedFormScrollWrapper.value && !isLoading.value) {
const height = expandedFormScrollWrapper.value.scrollHeight
expandedFormScrollWrapper.value.scrollTop = height
setTimeout(() => {
expandedFormScrollWrapper.value.scrollTop = 0
}, 125)
}
})
</script> </script>
<script lang="ts"> <script lang="ts">
@ -620,6 +645,7 @@ export default {
}" }"
> >
<div <div
ref="expandedFormScrollWrapper"
class="flex flex-col flex-grow mt-2 h-full max-h-full nc-scrollbar-md pb-6 items-center w-full bg-white p-4 xs:p-0" class="flex flex-col flex-grow mt-2 h-full max-h-full nc-scrollbar-md pb-6 items-center w-full bg-white p-4 xs:p-0"
> >
<div <div

9
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -83,6 +83,14 @@ const belongsToColumn = computed(
() => () =>
relatedTableMeta.value?.columns?.find((c: any) => c.title === relatedTableDisplayValueProp.value) as ColumnType | undefined, relatedTableMeta.value?.columns?.find((c: any) => c.title === relatedTableDisplayValueProp.value) as ColumnType | undefined,
) )
const plusBtnRef = ref<HTMLElement | null>(null)
watch([listItemsDlg], () => {
if (!listItemsDlg.value) {
plusBtnRef.value?.focus()
}
})
</script> </script>
<template> <template>
@ -101,6 +109,7 @@ const belongsToColumn = computed(
<div <div
v-if="!readOnly && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup" v-if="!readOnly && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup"
ref="plusBtnRef"
class="flex justify-end group gap-1 min-h-[30px] items-center" class="flex justify-end group gap-1 min-h-[30px] items-center"
tabindex="0" tabindex="0"
@keydown.enter.stop="listItemsDlg = true" @keydown.enter.stop="listItemsDlg = true"

17
packages/nc-gui/components/virtual-cell/Links.vue

@ -102,6 +102,21 @@ const openListDlg = () => {
listItemsDlg.value = true listItemsDlg.value = true
} }
const plusBtnRef = ref<HTMLElement | null>(null)
const childListDlgRef = ref<HTMLElement | null>(null)
watch([childListDlg], () => {
if (!childListDlg.value) {
childListDlgRef.value?.focus()
}
})
watch([listItemsDlg], () => {
if (!listItemsDlg.value) {
plusBtnRef.value?.focus()
}
})
</script> </script>
<template> <template>
@ -109,6 +124,7 @@ const openListDlg = () => {
<div class="block flex-shrink truncate"> <div class="block flex-shrink truncate">
<component <component
:is="isUnderLookup ? 'span' : 'a'" :is="isUnderLookup ? 'span' : 'a'"
ref="childListDlgRef"
v-e="['c:cell:links:modal:open']" v-e="['c:cell:links:modal:open']"
:title="textVal" :title="textVal"
class="text-center nc-datatype-link underline-transparent" class="text-center nc-datatype-link underline-transparent"
@ -124,6 +140,7 @@ const openListDlg = () => {
<div <div
v-if="!isUnderLookup" v-if="!isUnderLookup"
ref="plusBtnRef"
tabindex="0" tabindex="0"
class="!xs:hidden flex group justify-end group-hover:flex items-center" class="!xs:hidden flex group justify-end group-hover:flex items-center"
@keydown.enter.stop="openListDlg" @keydown.enter.stop="openListDlg"

2
packages/nc-gui/components/virtual-cell/QrCode.vue

@ -89,13 +89,11 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
> >
<img <img
v-if="showQrCode && rowHeight" v-if="showQrCode && rowHeight"
tabindex="0"
:style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }" :style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }"
:src="qrCode" :src="qrCode"
:alt="$t('title.qrCode')" :alt="$t('title.qrCode')"
class="min-w-[1.4em]" class="min-w-[1.4em]"
@click="showQrModal" @click="showQrModal"
@keydown.enter.stop="showQrModal"
/> />
<img v-else-if="showQrCode" class="mx-auto min-w-[1.4em]" :src="qrCode" :alt="$t('title.qrCode')" @click="showQrModal" /> <img v-else-if="showQrCode" class="mx-auto min-w-[1.4em]" :src="qrCode" :alt="$t('title.qrCode')" @click="showQrModal" />
</div> </div>

6
packages/nc-gui/components/virtual-cell/barcode/Barcode.vue

@ -64,11 +64,10 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
<JsBarcodeWrapper <JsBarcodeWrapper
v-if="showBarcode && rowHeight" v-if="showBarcode && rowHeight"
:barcode-value="barcodeValue" :barcode-value="barcodeValue"
tabindex="0" tabindex="-1"
:barcode-format="barcodeMeta.barcodeFormat" :barcode-format="barcodeMeta.barcodeFormat"
:custom-style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem`, width: 40 }" :custom-style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem`, width: 40 }"
@on-click-barcode="showBarcodeModal" @on-click-barcode="showBarcodeModal"
@keydown.enter.stop="showBarcodeModal"
> >
<template #barcodeRenderError> <template #barcodeRenderError>
<div class="text-left text-wrap mt-2 text-[#e65100] text-xs" data-testid="barcode-invalid-input-message"> <div class="text-left text-wrap mt-2 text-[#e65100] text-xs" data-testid="barcode-invalid-input-message">
@ -78,11 +77,10 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
</JsBarcodeWrapper> </JsBarcodeWrapper>
<JsBarcodeWrapper <JsBarcodeWrapper
v-else-if="showBarcode" v-else-if="showBarcode"
tabindex="0" tabindex="-1"
:barcode-value="barcodeValue" :barcode-value="barcodeValue"
:barcode-format="barcodeMeta.barcodeFormat" :barcode-format="barcodeMeta.barcodeFormat"
@on-click-barcode="showBarcodeModal" @on-click-barcode="showBarcodeModal"
@keydown.enter.stop="showBarcodeModal"
> >
<template #barcodeRenderError> <template #barcodeRenderError>
<div class="text-left text-wrap mt-2 text-[#e65100] text-xs" data-testid="barcode-invalid-input-message"> <div class="text-left text-wrap mt-2 text-[#e65100] text-xs" data-testid="barcode-invalid-input-message">

Loading…
Cancel
Save