diff --git a/frontend/src/components/TableDesigner.tsx b/frontend/src/components/TableDesigner.tsx
index 82af7fc..0e0c3ea 100644
--- a/frontend/src/components/TableDesigner.tsx
+++ b/frontend/src/components/TableDesigner.tsx
@@ -1,18 +1,14 @@
-import React, { useEffect, useState, useContext, useMemo, useRef } from 'react';
+import React, { useEffect, useState, useContext, useMemo, useRef, useCallback } from 'react';
import { Table, Tabs, Button, message, Input, Checkbox, Modal, AutoComplete, Tooltip, Select, Empty, Space } from 'antd';
import { ReloadOutlined, SaveOutlined, PlusOutlined, DeleteOutlined, MenuOutlined, FileTextOutlined, EyeOutlined, EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
-import { Resizable } from 'react-resizable';
import Editor, { loader } from '@monaco-editor/react';
import { TabData, ColumnDefinition, IndexDefinition, ForeignKeyDefinition, TriggerDefinition } from '../types';
import { useStore } from '../store';
import { DBGetColumns, DBGetIndexes, DBQuery, DBGetForeignKeys, DBGetTriggers, DBShowCreateTable } from '../../wailsjs/go/app/App';
-// Need styles for react-resizable
-import 'react-resizable/css/styles.css';
-
interface EditableColumn extends ColumnDefinition {
_key: string;
isNew?: boolean;
@@ -58,45 +54,43 @@ const COLLATIONS = {
]
};
-// --- Resizable Header Component ---
+// --- Resizable Header Component (Native, same interaction as DataGrid) ---
const ResizableTitle = (props: any) => {
- const { onResize, width, ...restProps } = props;
+ const { onResizeStart, width, ...restProps } = props;
+ const nextStyle = { ...(restProps.style || {}) } as React.CSSProperties;
+
+ if (width) {
+ nextStyle.width = width;
+ }
if (!width) {
- return
| ;
+ return | ;
}
return (
- {
- e.stopPropagation();
- e.preventDefault();
- }}
- onMouseDown={(e) => {
- e.stopPropagation();
- e.preventDefault(); // Prevent text selection and focus hijacking
- }}
- style={{
- position: 'absolute',
- right: -5,
- bottom: 0,
- top: 0,
- width: 10,
- cursor: 'col-resize',
- zIndex: 10
- }}
- />
- }
- onResize={onResize}
- draggableOpts={{ enableUserSelectHack: true }}
- >
- |
-
+
+ {restProps.children}
+ {
+ e.stopPropagation();
+ if (typeof onResizeStart === 'function') {
+ onResizeStart(e);
+ }
+ }}
+ onClick={(e) => e.stopPropagation()}
+ style={{
+ position: 'absolute',
+ right: 0,
+ bottom: 0,
+ top: 0,
+ width: 10,
+ cursor: 'col-resize',
+ zIndex: 10,
+ touchAction: 'none',
+ }}
+ />
+ |
);
};
@@ -218,6 +212,14 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
// --- Resizable Columns State ---
const [tableColumns, setTableColumns] = useState([]);
+ const resizeDragRef = useRef<{ startX: number; startWidth: number; index: number; containerLeft: number } | null>(null);
+ const resizeRafRef = useRef(null);
+ const latestResizeXRef = useRef(null);
+ const ghostRef = useRef(null);
+ const resizeListenerRef = useRef<{ move: ((e: MouseEvent) => void) | null; up: ((e: MouseEvent) => void) | null }>({
+ move: null,
+ up: null,
+ });
const sensors = useSensors(
useSensor(PointerSensor),
@@ -318,25 +320,97 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
setTableColumns(initialCols);
}, [readOnly]); // Re-create if readOnly changes
- const rafRef = React.useRef(null);
+ const flushResizeGhost = useCallback(() => {
+ resizeRafRef.current = null;
+ if (!resizeDragRef.current || !ghostRef.current) return;
+ if (latestResizeXRef.current === null) return;
+ const relativeLeft = latestResizeXRef.current - resizeDragRef.current.containerLeft;
+ ghostRef.current.style.transform = `translateX(${relativeLeft}px)`;
+ }, []);
- // Resize Handler
- const handleResize = (index: number) => (_: React.SyntheticEvent, { size }: { size: { width: number } }) => {
- if (rafRef.current) {
- cancelAnimationFrame(rafRef.current);
- }
- rafRef.current = requestAnimationFrame(() => {
- setTableColumns((columns) => {
- const nextColumns = [...columns];
- nextColumns[index] = {
- ...nextColumns[index],
- width: size.width,
- };
- return nextColumns;
+ const detachResizeListeners = useCallback(() => {
+ if (resizeListenerRef.current.move) {
+ document.removeEventListener('mousemove', resizeListenerRef.current.move);
+ resizeListenerRef.current.move = null;
+ }
+ if (resizeListenerRef.current.up) {
+ document.removeEventListener('mouseup', resizeListenerRef.current.up);
+ resizeListenerRef.current.up = null;
+ }
+ }, []);
+
+ const cleanupResizeState = useCallback(() => {
+ if (resizeRafRef.current !== null) {
+ cancelAnimationFrame(resizeRafRef.current);
+ resizeRafRef.current = null;
+ }
+ latestResizeXRef.current = null;
+ resizeDragRef.current = null;
+ if (ghostRef.current) {
+ ghostRef.current.style.display = 'none';
+ }
+ document.body.style.cursor = '';
+ document.body.style.userSelect = '';
+ }, []);
+
+ const handleResizeStart = useCallback((index: number) => (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const startX = e.clientX;
+ const currentWidth = Number(tableColumns[index]?.width || 200);
+ const containerLeft = containerRef.current?.getBoundingClientRect().left ?? 0;
+ resizeDragRef.current = { startX, startWidth: currentWidth, index, containerLeft };
+ latestResizeXRef.current = startX;
+
+ if (ghostRef.current && containerRef.current) {
+ const relativeLeft = startX - containerLeft;
+ ghostRef.current.style.transform = `translateX(${relativeLeft}px)`;
+ ghostRef.current.style.display = 'block';
+ }
+
+ detachResizeListeners();
+
+ const onMove = (event: MouseEvent) => {
+ if (!resizeDragRef.current) return;
+ latestResizeXRef.current = event.clientX;
+ if (resizeRafRef.current !== null) return;
+ resizeRafRef.current = requestAnimationFrame(flushResizeGhost);
+ };
+
+ const onUp = (event: MouseEvent) => {
+ if (resizeDragRef.current) {
+ const { startX: dragStartX, startWidth, index: dragIndex } = resizeDragRef.current;
+ const deltaX = event.clientX - dragStartX;
+ const newWidth = Math.max(50, startWidth + deltaX);
+ setTableColumns((prevColumns) => {
+ if (!prevColumns[dragIndex]) return prevColumns;
+ const nextColumns = [...prevColumns];
+ nextColumns[dragIndex] = {
+ ...nextColumns[dragIndex],
+ width: newWidth,
+ };
+ return nextColumns;
});
- rafRef.current = null;
- });
- };
+ }
+
+ detachResizeListeners();
+ cleanupResizeState();
+ };
+
+ resizeListenerRef.current = { move: onMove, up: onUp };
+ document.addEventListener('mousemove', onMove);
+ document.addEventListener('mouseup', onUp);
+ document.body.style.cursor = 'col-resize';
+ document.body.style.userSelect = 'none';
+ }, [cleanupResizeState, detachResizeListeners, flushResizeGhost, tableColumns]);
+
+ useEffect(() => {
+ return () => {
+ detachResizeListeners();
+ cleanupResizeState();
+ };
+ }, [cleanupResizeState, detachResizeListeners]);
const fetchData = async () => {
if (isNewTable) return; // Don't fetch for new table
@@ -786,7 +860,7 @@ ${selectedTrigger.statement}`;
...col,
onHeaderCell: (column: any) => ({
width: column.width,
- onResize: handleResize(index),
+ onResizeStart: handleResizeStart(index),
}),
}));
@@ -833,6 +907,21 @@ ${selectedTrigger.statement}`;
)}
+
);