mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Data tables UI fixes (no-changelog) (#18966)
This commit is contained in:
@@ -82,8 +82,11 @@ const onToggleSave = (value: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onAddColumn = (column: DataStoreColumnCreatePayload) => {
|
||||
dataStoreTableRef.value?.addColumn(column);
|
||||
const onAddColumn = async (column: DataStoreColumnCreatePayload) => {
|
||||
if (!dataStoreTableRef.value) {
|
||||
return false;
|
||||
}
|
||||
return await dataStoreTableRef.value.addColumn(column);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -43,7 +43,7 @@ vi.mock('@n8n/i18n', async (importOriginal) => ({
|
||||
}));
|
||||
|
||||
describe('AddColumnButton', () => {
|
||||
const addColumnHandler = vi.fn();
|
||||
const addColumnHandler = vi.fn().mockResolvedValue(true);
|
||||
const renderComponent = createComponentRenderer(AddColumnButton, {
|
||||
props: {
|
||||
params: {
|
||||
@@ -244,6 +244,24 @@ describe('AddColumnButton', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not close popover if submission fails', async () => {
|
||||
const { getByPlaceholderText, getByTestId } = renderComponent();
|
||||
addColumnHandler.mockResolvedValueOnce(false);
|
||||
const addButton = getByTestId('data-store-add-column-trigger-button');
|
||||
|
||||
await fireEvent.click(addButton);
|
||||
|
||||
const nameInput = getByPlaceholderText('Enter column name');
|
||||
await fireEvent.update(nameInput, 'testColumn');
|
||||
|
||||
const submitButton = getByTestId('data-store-add-column-submit-button');
|
||||
await fireEvent.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('data-store-add-column-submit-button')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow submission with Enter key', async () => {
|
||||
const { getByTestId, getByPlaceholderText } = renderComponent();
|
||||
const addButton = getByTestId('data-store-add-column-trigger-button');
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useDebounce } from '@/composables/useDebounce';
|
||||
const props = defineProps<{
|
||||
// the params key is needed so that we can pass this directly to ag-grid as column
|
||||
params: {
|
||||
onAddColumn: (column: DataStoreColumnCreatePayload) => void;
|
||||
onAddColumn: (column: DataStoreColumnCreatePayload) => Promise<boolean>;
|
||||
};
|
||||
popoverId?: string;
|
||||
useTextTrigger?: boolean;
|
||||
@@ -38,11 +38,18 @@ const isSelectOpen = ref(false);
|
||||
|
||||
const popoverId = computed(() => props.popoverId ?? 'add-column-popover');
|
||||
|
||||
const onAddButtonClicked = () => {
|
||||
if (!columnName.value || !columnType.value) {
|
||||
const onAddButtonClicked = async () => {
|
||||
validateName();
|
||||
if (!columnName.value || !columnType.value || error.value) {
|
||||
return;
|
||||
}
|
||||
const success = await props.params.onAddColumn({
|
||||
name: columnName.value,
|
||||
type: columnType.value,
|
||||
});
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
props.params.onAddColumn({ name: columnName.value, type: columnType.value });
|
||||
columnName.value = '';
|
||||
columnType.value = 'string';
|
||||
popoverOpen.value = false;
|
||||
|
||||
@@ -20,6 +20,7 @@ vi.mock('ag-grid-vue3', () => ({
|
||||
api: {
|
||||
refreshHeader: vi.fn(),
|
||||
applyTransaction: vi.fn(),
|
||||
setGridOption: vi.fn(),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -105,7 +105,7 @@ const { mapToAGCellType } = useDataStoreTypes();
|
||||
|
||||
const dataStoreStore = useDataStoreStore();
|
||||
|
||||
useClipboard({ onPaste: onClipboardPaste });
|
||||
const { copy: copyToClipboard } = useClipboard({ onPaste: onClipboardPaste });
|
||||
|
||||
// AG Grid State
|
||||
const gridApi = ref<GridApi | null>(null);
|
||||
@@ -125,7 +125,7 @@ const contentLoading = ref(false);
|
||||
const lastFocusedCell = ref<{ rowIndex: number; colId: string } | null>(null);
|
||||
const isTextEditorOpen = ref(false);
|
||||
|
||||
const gridContainer = useTemplateRef('gridContainer');
|
||||
const gridContainer = useTemplateRef<HTMLDivElement>('gridContainer');
|
||||
|
||||
// Pagination
|
||||
const pageSizeOptions = [10, 20, 50];
|
||||
@@ -141,6 +141,11 @@ const selectedCount = computed(() => selectedRowIds.value.size);
|
||||
|
||||
const onGridReady = (params: GridReadyEvent) => {
|
||||
gridApi.value = params.api;
|
||||
// Ensure popups (e.g., agLargeTextCellEditor) are positioned relative to the grid container
|
||||
// to avoid misalignment when the page scrolls.
|
||||
if (gridContainer?.value) {
|
||||
params.api.setGridOption('popupParent', gridContainer.value as unknown as HTMLElement);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshGridData = () => {
|
||||
@@ -241,8 +246,10 @@ const onAddColumn = async (column: DataStoreColumnCreatePayload) => {
|
||||
return { ...row, [newColumn.name]: null };
|
||||
});
|
||||
refreshGridData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('dataStore.addColumn.error'));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -307,6 +314,9 @@ const createColumnDef = (col: DataStoreColumn, extraProps: Partial<ColDef> = {})
|
||||
// Enable large text editor for text columns
|
||||
if (col.type === 'string') {
|
||||
columnDef.cellEditor = 'agLargeTextCellEditor';
|
||||
// Use popup editor so it is not clipped by the grid viewport and positions correctly
|
||||
columnDef.cellEditorPopup = true;
|
||||
columnDef.cellEditorPopupPosition = 'over';
|
||||
// Provide initial value for the editor, otherwise agLargeTextCellEditor breaks
|
||||
columnDef.cellEditorParams = (params: CellEditRequestEvent<DataStoreRow>) => ({
|
||||
value: params.value ?? '',
|
||||
@@ -596,8 +606,20 @@ function onClipboardPaste(data: string) {
|
||||
const colDef = focusedCell.column.getColDef();
|
||||
if (colDef.cellDataType === 'text') {
|
||||
row.setDataValue(focusedCell.column.getColId(), data);
|
||||
} else if (!Number.isNaN(Number(data))) {
|
||||
row.setDataValue(focusedCell.column.getColId(), Number(data));
|
||||
} else if (colDef.cellDataType === 'number') {
|
||||
if (!Number.isNaN(Number(data))) {
|
||||
row.setDataValue(focusedCell.column.getColId(), Number(data));
|
||||
}
|
||||
} else if (colDef.cellDataType === 'date') {
|
||||
if (!Number.isNaN(Date.parse(data))) {
|
||||
row.setDataValue(focusedCell.column.getColId(), new Date(data));
|
||||
}
|
||||
} else if (colDef.cellDataType === 'boolean') {
|
||||
if (data === 'true') {
|
||||
row.setDataValue(focusedCell.column.getColId(), true);
|
||||
} else if (data === 'false') {
|
||||
row.setDataValue(focusedCell.column.getColId(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,16 +666,38 @@ const onSelectionChanged = () => {
|
||||
};
|
||||
|
||||
const onCellKeyDown = async (params: CellKeyDownEvent<DataStoreRow>) => {
|
||||
const key = (params.event as KeyboardEvent).key;
|
||||
if (key !== 'Delete' && key !== 'Backspace') return;
|
||||
if (params.api.getEditingCells().length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isEditing = params.api.getEditingCells().length > 0;
|
||||
if (isEditing || selectedRowIds.value.size === 0) return;
|
||||
const event = params.event as KeyboardEvent;
|
||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'c') {
|
||||
event.preventDefault();
|
||||
await handleCopyFocusedCell(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.event?.preventDefault();
|
||||
if ((event.key !== 'Delete' && event.key !== 'Backspace') || selectedRowIds.value.size === 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
await handleDeleteSelected();
|
||||
};
|
||||
|
||||
const handleCopyFocusedCell = async (params: CellKeyDownEvent<DataStoreRow>) => {
|
||||
const focused = params.api.getFocusedCell();
|
||||
if (!focused) {
|
||||
return;
|
||||
}
|
||||
const row = params.api.getDisplayedRowAtIndex(focused.rowIndex);
|
||||
const colDef = focused.column.getColDef();
|
||||
if (row?.data && colDef.field) {
|
||||
const rawValue = row.data[colDef.field];
|
||||
const text = rawValue === null || rawValue === undefined ? '' : String(rawValue);
|
||||
await copyToClipboard(text);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteSelected = async () => {
|
||||
if (selectedRowIds.value.size === 0) return;
|
||||
|
||||
@@ -683,8 +727,9 @@ const handleDeleteSelected = async () => {
|
||||
|
||||
await fetchDataStoreContent();
|
||||
|
||||
toast.showMessage({
|
||||
toast.showToast({
|
||||
title: i18n.baseText('dataStore.deleteRows.success'),
|
||||
message: '',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -858,7 +903,8 @@ defineExpose({
|
||||
}
|
||||
|
||||
:global(.ag-large-text-input) {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
min-width: 420px;
|
||||
padding: 0;
|
||||
|
||||
textarea {
|
||||
|
||||
Reference in New Issue
Block a user