Compare commits

...
Author SHA1 Message Date
sergeyteleshevandDaria Marutkina 5030127d81 Critical UX Issue: Delete key marks entire row as deleted during inline cell editing (#4062)
* dbeaver/pro#4061 changes KEY_BINDING_DELETE_ROW shortcut to prevent deleting row during cell editing

* considers the case with deleting whole row during editing

* uses only delete shortcut

* considers browser default behavior

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-21 20:28:08 +08:00
Sychev Andrey cbd51257ad dbeaver/pro#8051 fix: support time-only formatting in DateTimeFormatter and update related tests 2026-01-17 10:38:31 +01:00
sergeyteleshev 573b69bcd8 dbeaver/pro#8025 fixes filters for select cell (#4054) 2026-01-14 16:28:19 +01:00
7 changed files with 30 additions and 13 deletions
@@ -266,6 +266,7 @@ export class DataGridContextMenuFilterService {
const cellValue = format.getText(format.get(key));
const supportedOperations = data.getColumnOperations(key.column);
const columnLabel = data.getColumn(key.column)?.label || '';
const { clippedLabel: clippedColumnLabel } = getMenuLabelClipped(columnLabel);
const filters = supportedOperations
.filter(operation => !nullOperationsFilter(operation))
@@ -273,14 +274,15 @@ export class DataGridContextMenuFilterService {
const wrappedValue = wrapOperationArgument(operation.id, cellValue);
const { clippedLabel: clippedValue } = getMenuLabelClipped(wrappedValue);
const fullLabel = `${columnLabel} ${operation.expression} ${wrappedValue}`;
const tooltip = fullLabel !== clippedValue ? fullLabel : undefined;
const label = `${clippedColumnLabel} ${operation.expression} ${clippedValue}`;
const tooltip = fullLabel !== label ? fullLabel : undefined;
return new MenuBaseItem(
{
id: operation.id,
label: clippedValue,
icon: 'filter',
label,
tooltip,
icon: 'filter',
},
{
onSelect: async () => {
@@ -42,16 +42,25 @@ export const DateTimeFormatter = observer<ICellFormatterProps>(function DateTime
let dateFormatter: Intl.DateTimeFormat | null = null;
switch (extendedDateKind) {
case DateTimeKind.DateTime:
case DateTimeKind.TimeOnly:
dateFormatter = formattingContext.formatters.dateTime;
break;
case DateTimeKind.TimeOnly:
dateFormatter = formattingContext.formatters.timeOnly;
break;
case DateTimeKind.DateOnly:
dateFormatter = formattingContext.formatters.dateOnly;
break;
}
if (dateFormatter) {
const date = new Date(displayValue);
value = dateFormatter.format(date);
if (DateTimeKind.TimeOnly === extendedDateKind) {
const [h = 0, m = 0, s = 0] = displayValue.split(':').map(Number);
const date = new Date();
date.setHours(h, m, s, 0);
value = dateFormatter.format(date);
} else {
const date = new Date(displayValue);
value = dateFormatter.format(date);
}
}
}
@@ -19,6 +19,7 @@ export interface IDataGridFormatters {
locale: string;
dateTime: Intl.DateTimeFormat;
dateOnly: Intl.DateTimeFormat;
timeOnly: Intl.DateTimeFormat;
number: Intl.NumberFormat;
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,10 +17,10 @@ describe('detectDateTimeKind', () => {
expect(detectDateTimeKind('1999-12-31')).toBe(DateTimeKind.DateOnly);
});
test('should detect time-only format (HH:MM:SS)', () => {
test('should detect time-only format (HH:MM:SS or HH:MM)', () => {
expect(detectDateTimeKind('14:30:00')).toBe(DateTimeKind.TimeOnly);
expect(detectDateTimeKind('00:00:00')).toBe(DateTimeKind.TimeOnly);
expect(detectDateTimeKind('23:59:59')).toBe(DateTimeKind.TimeOnly);
expect(detectDateTimeKind('23:59')).toBe(DateTimeKind.TimeOnly);
expect(detectDateTimeKind('14:30:00.123')).toBe(DateTimeKind.TimeOnly);
});
@@ -10,13 +10,14 @@ import { DateTimeKind } from '../FormattingContext.js';
const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
const TIME_ONLY_REGEX = /^\d{2}:\d{2}:\d{2}/;
const TIME_ONLY_NO_SECONDS_REGEX = /^\d{2}:\d{2}$/;
export function detectDateTimeKind(displayValue: string): DateTimeKind {
if (DATE_ONLY_REGEX.test(displayValue)) {
return DateTimeKind.DateOnly;
}
if (TIME_ONLY_REGEX.test(displayValue) && !displayValue.includes('-')) {
if ((TIME_ONLY_REGEX.test(displayValue) || TIME_ONLY_NO_SECONDS_REGEX.test(displayValue)) && !displayValue.includes('-')) {
return DateTimeKind.TimeOnly;
}
@@ -38,6 +38,11 @@ export function useFormatting(tableData: ITableData, cache: ResultSetCacheAction
return {
locale,
timeOnly: new Intl.DateTimeFormat(locale, {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
}),
dateTime: new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
@@ -50,7 +55,6 @@ export function useFormatting(tableData: ITableData, cache: ResultSetCacheAction
year: 'numeric',
month: 'numeric',
day: 'numeric',
timeZone: 'UTC',
}),
number: new Intl.NumberFormat(locale),
};
@@ -24,8 +24,8 @@ export const KEY_BINDING_DUPLICATE_ROW: IKeyBinding = {
export const KEY_BINDING_DELETE_ROW: IKeyBinding = {
id: 'data-viewer-delete-row',
keys: ['Delete'],
keysMac: ['Fn+Backspace'],
keys: ['mod+shift+backspace'],
preventDefault: true,
};
export const KEY_BINDING_SAVE: IKeyBinding = {