feat(grid): use temporal inputs for date cells
This commit is contained in:
1 parent
b76efa56d6
commit
b6e519dfaa
3 files changed
+140
-1
No files matched your search
@@ -104,6 +104,12 @@ import {
|
||||
type ColumnFormatterConfig,
|
||||
type DateTimeFormatterUnit,
|
||||
} from "@/lib/columnFormatter";
|
||||
import {
|
||||
formatTemporalInputValue,
|
||||
parseTemporalInputValue,
|
||||
temporalCellEditorKind,
|
||||
type TemporalCellEditorKind,
|
||||
} from "@/lib/dataGridTemporalEditor";
|
||||
import { isCancelSearchShortcut, isFocusSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGridScrollGutter";
|
||||
import { dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGridSaveUi";
|
||||
@@ -1386,6 +1392,43 @@ function canEditCellItem(item: RowItem | undefined, columnIndex: number): boolea
|
||||
return true;
|
||||
}
|
||||
|
||||
function tableColumnForGridColumn(columnIndex: number): ColumnInfo | undefined {
|
||||
const columnName = props.sourceColumns?.[columnIndex] ?? props.result.columns[columnIndex];
|
||||
if (!columnName) return undefined;
|
||||
return props.tableMeta?.columns.find((column) => column.name.toLowerCase() === columnName.toLowerCase());
|
||||
}
|
||||
|
||||
function temporalEditorKindForColumn(columnIndex: number): TemporalCellEditorKind | undefined {
|
||||
return temporalCellEditorKind(tableColumnForGridColumn(columnIndex)?.data_type, props.databaseType);
|
||||
}
|
||||
|
||||
function cellEditInputType(columnIndex: number): string {
|
||||
const kind = temporalEditorKindForColumn(columnIndex);
|
||||
if (kind === "date") return "date";
|
||||
if (kind === "time") return "time";
|
||||
if (kind === "datetime") return "datetime-local";
|
||||
return "text";
|
||||
}
|
||||
|
||||
function cellEditInputStep(columnIndex: number): string | undefined {
|
||||
return temporalEditorKindForColumn(columnIndex) ? "1" : undefined;
|
||||
}
|
||||
|
||||
function cellEditInputValue(columnIndex: number): string {
|
||||
const kind = temporalEditorKindForColumn(columnIndex);
|
||||
return kind ? formatTemporalInputValue(editValue.value, kind) : editValue.value;
|
||||
}
|
||||
|
||||
function onCellEditInput(columnIndex: number, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const kind = temporalEditorKindForColumn(columnIndex);
|
||||
if (!kind) {
|
||||
editValue.value = target.value;
|
||||
return;
|
||||
}
|
||||
editValue.value = parseTemporalInputValue(target.value, kind) ?? "NULL";
|
||||
}
|
||||
|
||||
function canDeleteRowItem(item: RowItem | undefined): boolean {
|
||||
return !!props.editable && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
|
||||
}
|
||||
@@ -3500,11 +3543,14 @@ defineExpose({
|
||||
>
|
||||
<template v-if="editingCell?.rowId === item.id && editingCell?.col === actualColIdx">
|
||||
<input
|
||||
v-model="editValue"
|
||||
:type="cellEditInputType(actualColIdx)"
|
||||
:step="cellEditInputStep(actualColIdx)"
|
||||
:value="cellEditInputValue(actualColIdx)"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input absolute inset-0 bg-background border-2 border-primary px-2 py-0.5 text-xs outline-none z-10"
|
||||
@input="onCellEditInput(actualColIdx, $event)"
|
||||
@blur="commitEdit"
|
||||
@click.stop
|
||||
@keydown.stop="onEditKeydown"
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
export type TemporalCellEditorKind = "date" | "time" | "datetime";
|
||||
|
||||
export function temporalCellEditorKind(
|
||||
dataType: string | undefined,
|
||||
_databaseType?: DatabaseType,
|
||||
): TemporalCellEditorKind | undefined {
|
||||
const normalized = normalizeTemporalType(dataType);
|
||||
if (!normalized) return undefined;
|
||||
if (/\b(?:datetime|datetime2|datetime64|smalldatetime|datetimeoffset|timestamp|timestamptz)\b/.test(normalized)) {
|
||||
return "datetime";
|
||||
}
|
||||
if (/\bdate(?:32)?\b/.test(normalized)) return "date";
|
||||
if (/\b(?:time|timetz|time64)\b/.test(normalized)) return "time";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function formatTemporalInputValue(
|
||||
value: string | number | boolean | null,
|
||||
kind: TemporalCellEditorKind,
|
||||
): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
const text = String(value).trim();
|
||||
if (!text) return "";
|
||||
|
||||
if (kind === "date") {
|
||||
const date = text.match(/^(\d{4}-\d{2}-\d{2})/);
|
||||
return date?.[1] ?? "";
|
||||
}
|
||||
|
||||
if (kind === "time") {
|
||||
return normalizeTimeInput(text);
|
||||
}
|
||||
|
||||
const datetime = text.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})(?::(\d{2}))?/);
|
||||
if (!datetime) return "";
|
||||
return `${datetime[1]}T${datetime[2]}:${datetime[3] ?? "00"}`;
|
||||
}
|
||||
|
||||
export function parseTemporalInputValue(value: string, kind: TemporalCellEditorKind): string | null {
|
||||
const text = value.trim();
|
||||
if (!text) return null;
|
||||
if (kind === "date") return text;
|
||||
if (kind === "time") return normalizeTimeInput(text) || null;
|
||||
|
||||
const [date, time = ""] = text.split("T");
|
||||
const normalizedTime = normalizeTimeInput(time);
|
||||
return date && normalizedTime ? `${date} ${normalizedTime}` : text.replace("T", " ");
|
||||
}
|
||||
|
||||
function normalizeTemporalType(dataType: string | undefined): string {
|
||||
return (dataType ?? "").toLowerCase().replace(/_/g, "").replace(/[(),]/g, " ").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function normalizeTimeInput(text: string): string {
|
||||
const match = text.match(/^(\d{2}:\d{2})(?::(\d{2}))?/);
|
||||
if (!match) return "";
|
||||
return `${match[1]}:${match[2] ?? "00"}`;
|
||||
}
|
||||
Reference in new issue
Block a user