diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue index 019eb1e586..5cde5ec01f 100644 --- a/packages/nc-gui/components/cell/DatePicker.vue +++ b/packages/nc-gui/components/cell/DatePicker.vue @@ -70,6 +70,8 @@ watch( (next) => { if (next) { onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) + } else { + editable.value = false } }, { flush: 'post' }, diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 82362faa69..397ceda16f 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -122,6 +122,8 @@ watch( (next) => { if (next) { onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) + } else { + editable.value = false } }, { flush: 'post' }, diff --git a/packages/nc-gui/components/cell/TimePicker.vue b/packages/nc-gui/components/cell/TimePicker.vue index 205b2bed6b..3ea3e8b5a2 100644 --- a/packages/nc-gui/components/cell/TimePicker.vue +++ b/packages/nc-gui/components/cell/TimePicker.vue @@ -69,6 +69,8 @@ watch( (next) => { if (next) { onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) + } else { + editable.value = false } }, { flush: 'post' }, diff --git a/packages/nc-gui/components/cell/YearPicker.vue b/packages/nc-gui/components/cell/YearPicker.vue index 3d9f795b40..58312273a6 100644 --- a/packages/nc-gui/components/cell/YearPicker.vue +++ b/packages/nc-gui/components/cell/YearPicker.vue @@ -55,6 +55,8 @@ watch( (next) => { if (next) { onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) + } else { + editable.value = false } }, { flush: 'post' }, diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue index b16ae4518d..fc8c4f98a3 100644 --- a/packages/nc-gui/components/smartsheet/Grid.vue +++ b/packages/nc-gui/components/smartsheet/Grid.vue @@ -101,6 +101,8 @@ const contextMenu = computed({ }) const contextMenuClosing = ref(false) +const scrolling = ref(false) + const bulkUpdateDlg = ref(false) const routeQuery = $computed(() => route.query as Record) @@ -111,6 +113,9 @@ const expandedFormRowState = ref>() const gridWrapper = ref() const tableHeadEl = ref() const tableBodyEl = ref() +const fillHandle = ref() + +const gridRect = useElementBounding(gridWrapper) const isAddingColumnAllowed = $computed(() => !readOnly.value && !isLocked.value && isUIAllowed('add-column') && !isSqlView.value) @@ -209,6 +214,8 @@ const { resetSelectedRange, makeActive, selectedRange, + isCellInFillRange, + isFillMode, } = useMultiSelect( meta, fields, @@ -350,6 +357,7 @@ const { await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title) }, bulkUpdateRows, + fillHandle, ) function scrollToCell(row?: number | null, col?: number | null) { @@ -652,10 +660,15 @@ useEventListener(document, 'keyup', async (e: KeyboardEvent) => { } }) -/** On clicking outside of table reset active cell */ const smartTable = ref(null) +/** On clicking outside of table reset active cell */ onClickOutside(tableBodyEl, (e) => { + // do nothing if mousedown on the scrollbar (scrolling) + if (scrolling.value) { + return + } + // do nothing if context menu was open if (contextMenu.value) return @@ -665,6 +678,9 @@ onClickOutside(tableBodyEl, (e) => { if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return + // skip if fill mode is active + if (isFillMode.value) return + // ignore unselecting if clicked inside or on the picker(Date, Time, DateTime, Year) // or single/multi select options const activePickerOrDropdownEl = document.querySelector( @@ -865,6 +881,15 @@ const closeAddColumnDropdown = (scrollToLastCol = false) => { const confirmDeleteRow = (row: number) => { try { deleteRow(row) + + if (selectedRange.isRowInRange(row)) { + clearSelectedRange() + } + + if (activeCell.row === row) { + activeCell.row = null + activeCell.col = null + } } catch (e: any) { message.error(e.message) } @@ -883,10 +908,65 @@ function addEmptyRow(row?: number) { nextTick().then(() => { clearSelectedRange() makeActive(row ?? data.value.length - 1, 0) + selectedRange.startRange({ row: activeCell.row!, col: activeCell.col! }) scrollToCell?.() }) return rowObj } + +const fillHandleTop = ref() +const fillHandleLeft = ref() + +const cellRefs = ref<{ el: HTMLElement }[]>([]) + +const showFillHandle = computed( + () => + !readOnly.value && + !isLocked.value && + !editEnabled && + (!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) && + !data.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new, +) + +const refreshFillHandle = () => { + const cellRef = cellRefs.value.find( + (cell) => + cell.el.dataset.rowIndex === String(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) && + cell.el.dataset.colIndex === String(isNaN(selectedRange.end.col) ? activeCell.col : selectedRange.end.col), + ) + if (cellRef) { + const cellRect = useElementBounding(cellRef.el) + if (!cellRect || !gridWrapper.value) return + fillHandleTop.value = cellRect.top.value + cellRect.height.value - gridRect.top.value + gridWrapper.value.scrollTop + fillHandleLeft.value = cellRect.left.value + cellRect.width.value - gridRect.left.value + gridWrapper.value.scrollLeft + } +} + +watch( + [() => selectedRange.end.row, () => selectedRange.end.col, () => activeCell.row, () => activeCell.col], + ([sr, sc, ar, ac], [osr, osc, oar, oac]) => { + if (sr !== osr || sc !== osc || ar !== oar || ac !== oac) { + refreshFillHandle() + } + }, +) + +useEventListener(gridWrapper, 'scroll', () => { + refreshFillHandle() +}) + +useEventListener(document, 'mousedown', (e) => { + if (e.offsetX > (e.target as HTMLElement)?.clientWidth || e.offsetY > (e.target as HTMLElement)?.clientHeight) { + scrolling.value = true + } +}) + +useEventListener(document, 'mouseup', () => { + // wait for click event to finish before setting scrolling to false + setTimeout(() => { + scrolling.value = false + }, 100) +}) + + + + +
+ +
+ + + + + + + +
+