1 parent
cd18b7a3db
commit
f621e40655
15 files changed
+457
-2
No files matched your search
@@ -182,7 +182,20 @@ import { temporalCellEditorConfig, type TemporalCellEditorConfig } from "@/lib/d
|
||||
import { BOOLEAN_CELL_EDITOR_VALUES, booleanCellEditorValue, isBooleanCellValue, isBooleanColumnType, isPointInBooleanCheckbox, nextBooleanCellValue, normalizeBooleanCellValue, parseBooleanCellEditorValue } from "@/lib/dataGrid/dataGridBooleanColumn";
|
||||
import { resolveDataGridColumnNullability, resolveDataGridColumnsByResultIndex } from "@/lib/dataGrid/dataGridColumnMetadata";
|
||||
import { resolveDataGridNewRowCellPlaceholder } from "@/lib/dataGrid/dataGridDefaultPlaceholder";
|
||||
import { isCancelSearchShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowShortcut, isFocusSearchShortcut, isGoToColumnShortcut, isModRShortcut, isSaveShortcut, isToggleTransposeShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import {
|
||||
isCancelSearchShortcut,
|
||||
isCopyCurrentRowShortcut,
|
||||
isDeleteCurrentRowShortcut,
|
||||
isFocusSearchShortcut,
|
||||
isGoToColumnShortcut,
|
||||
isGoToFirstPageShortcut,
|
||||
isGoToLastPageShortcut,
|
||||
isGoToNextPageShortcut,
|
||||
isGoToPreviousPageShortcut,
|
||||
isModRShortcut,
|
||||
isSaveShortcut,
|
||||
isToggleTransposeShortcut,
|
||||
} from "@/lib/editor/keyboardShortcuts";
|
||||
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid/dataGridScrollGutter";
|
||||
import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
|
||||
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
|
||||
@@ -3742,6 +3755,21 @@ async function lastPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleGridPaginationShortcut(event: KeyboardEvent): boolean {
|
||||
if (!props.paginationEnabled || gridPaginationBusy.value || infiniteScrollEnabled.value) return false;
|
||||
const shortcuts = settingsStore.editorSettings.shortcuts;
|
||||
let navigate: (() => void) | undefined;
|
||||
if (currentPage.value > 1 && isGoToFirstPageShortcut(event, shortcuts)) navigate = firstPage;
|
||||
else if (currentPage.value > 1 && isGoToPreviousPageShortcut(event, shortcuts)) navigate = prevPage;
|
||||
else if (canGoNextPage.value && isGoToNextPageShortcut(event, shortcuts)) navigate = nextPage;
|
||||
else if (canJumpLastPage.value && isGoToLastPageShortcut(event, shortcuts)) navigate = lastPage;
|
||||
if (!navigate) return false;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
void navigate();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function buildCurrentCountTarget(): Promise<{ sql: string; schema?: string } | undefined> {
|
||||
if (props.countSql) return { sql: props.countSql, schema: props.schema };
|
||||
if (props.tableMeta) {
|
||||
@@ -8987,6 +9015,7 @@ async function onGridKeydown(event: KeyboardEvent) {
|
||||
goToColumnOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (!targetAllowsNativeClipboard && handleGridPaginationShortcut(event)) return;
|
||||
if (isFocusSearchShortcut(event)) {
|
||||
event.preventDefault();
|
||||
focusSearch();
|
||||
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { createApp, defineComponent, h, markRaw, nextTick, type App, type PropType } from "vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import type { ShortcutActionId } from "@/lib/editor/shortcutRegistry";
|
||||
|
||||
vi.mock("@/composables/useDataGridColumnResize", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@/composables/useDataGridColumnResize")>();
|
||||
const { ref } = await import("vue");
|
||||
return {
|
||||
...actual,
|
||||
useDataGridColumnResize: () => ({
|
||||
initColumnWidths: vi.fn(),
|
||||
onResizeStart: vi.fn(),
|
||||
autoFitColumn: vi.fn(),
|
||||
renderedColumnWidths: ref([120]),
|
||||
totalWidth: ref(120),
|
||||
columnVars: ref({ "--total-w": "120px" }),
|
||||
getIsResizing: () => false,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import DataGrid from "../DataGrid.vue";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
|
||||
const dataGridSource = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGrid.vue"), "utf8");
|
||||
const mountedApps: Array<{ app: App; host: HTMLElement }> = [];
|
||||
const shortcutCases = [
|
||||
{ actionId: "goToFirstPage", key: "F1", offset: 0, functionName: "firstPage" },
|
||||
{ actionId: "goToPreviousPage", key: "F2", offset: 100, functionName: "prevPage" },
|
||||
{ actionId: "goToNextPage", key: "F3", offset: 300, functionName: "nextPage" },
|
||||
{ actionId: "goToLastPage", key: "F4", offset: 400, functionName: "lastPage" },
|
||||
] as const;
|
||||
|
||||
const RecycleScroller = defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array as PropType<unknown[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, slots }) {
|
||||
return () =>
|
||||
h(
|
||||
"div",
|
||||
attrs,
|
||||
props.items.map((item) => slots.default?.({ item })),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function mountGrid(
|
||||
options: {
|
||||
pageOffset?: number;
|
||||
totalRowCount?: number;
|
||||
loading?: boolean;
|
||||
paginationEnabled?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
empty?: boolean;
|
||||
configurePaginationShortcuts?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
const settingsStore = useSettingsStore();
|
||||
settingsStore.updateEditorSettings({
|
||||
dataGridRenderMode: "canvas",
|
||||
infiniteScroll: options.infiniteScroll ?? false,
|
||||
shortcuts: {
|
||||
...settingsStore.editorSettings.shortcuts,
|
||||
...(options.configurePaginationShortcuts === false
|
||||
? {}
|
||||
: {
|
||||
goToFirstPage: "Alt+F1",
|
||||
goToPreviousPage: "Alt+F2",
|
||||
goToNextPage: "Alt+F3",
|
||||
goToLastPage: "Alt+F4",
|
||||
}),
|
||||
},
|
||||
});
|
||||
const result = markRaw<QueryResult>({
|
||||
columns: ["id"],
|
||||
rows: options.empty ? [] : [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 0,
|
||||
});
|
||||
const paginate = vi.fn();
|
||||
|
||||
const host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
const Root = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
TooltipProvider,
|
||||
{ delayDuration: 0 },
|
||||
{
|
||||
default: () =>
|
||||
h(DataGrid, {
|
||||
result,
|
||||
databaseType: "mysql",
|
||||
context: "table-data",
|
||||
pageLimit: 100,
|
||||
pageOffset: options.pageOffset ?? 200,
|
||||
totalRowCount: options.totalRowCount ?? 500,
|
||||
loading: options.loading ?? false,
|
||||
paginationEnabled: options.paginationEnabled ?? true,
|
||||
onPaginate: paginate,
|
||||
}),
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
const app = createApp(Root);
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
app.component("RecycleScroller", RecycleScroller);
|
||||
app.mount(host);
|
||||
const mounted = { app, host };
|
||||
mountedApps.push(mounted);
|
||||
return { host, paginate };
|
||||
}
|
||||
|
||||
async function settle() {
|
||||
await nextTick();
|
||||
await Promise.resolve();
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
function gridRoot(host: HTMLElement): HTMLElement {
|
||||
const root = host.querySelector<HTMLElement>("[data-grid-root]");
|
||||
if (!root) throw new Error("Data grid root not found");
|
||||
return root;
|
||||
}
|
||||
|
||||
function dispatchShortcut(target: HTMLElement, key: string): KeyboardEvent {
|
||||
const event = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, altKey: true, key });
|
||||
target.dispatchEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const { app, host } of mountedApps.splice(0)) {
|
||||
app.unmount();
|
||||
host.remove();
|
||||
}
|
||||
});
|
||||
|
||||
describe("DataGrid pagination shortcuts", () => {
|
||||
it.each(shortcutCases)("runs $functionName through the configured $actionId shortcut", async ({ key, offset }) => {
|
||||
const { host, paginate } = mountGrid();
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
root.focus();
|
||||
|
||||
const event = dispatchShortcut(root, key);
|
||||
await settle();
|
||||
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
expect(bubbled).not.toHaveBeenCalled();
|
||||
expect(paginate).toHaveBeenCalledOnce();
|
||||
expect(paginate.mock.calls[0]?.slice(0, 2)).toEqual([offset, 100]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["goToFirstPage", "F1"],
|
||||
["goToPreviousPage", "F2"],
|
||||
] as const)("does not consume %s on the first page", async (_actionId, key) => {
|
||||
const { host, paginate } = mountGrid({ pageOffset: 0 });
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
|
||||
const event = dispatchShortcut(root, key);
|
||||
await settle();
|
||||
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
expect(bubbled).toHaveBeenCalledOnce();
|
||||
expect(paginate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["goToNextPage", "F3"],
|
||||
["goToLastPage", "F4"],
|
||||
] as const)("does not consume %s on the last page", async (_actionId, key) => {
|
||||
const { host, paginate } = mountGrid({ pageOffset: 400 });
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
|
||||
const event = dispatchShortcut(root, key);
|
||||
await settle();
|
||||
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
expect(bubbled).toHaveBeenCalledOnce();
|
||||
expect(paginate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["loading", { loading: true }],
|
||||
["disabled pagination", { paginationEnabled: false }],
|
||||
["infinite scroll", { infiniteScroll: true }],
|
||||
["no available pages", { pageOffset: 0, totalRowCount: 0, empty: true }],
|
||||
] as const)("does not consume pagination shortcuts during %s", async (_label, options) => {
|
||||
const { host, paginate } = mountGrid(options);
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
|
||||
for (const { key } of shortcutCases) {
|
||||
const event = dispatchShortcut(root, key);
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
}
|
||||
await settle();
|
||||
|
||||
expect(bubbled).toHaveBeenCalledTimes(shortcutCases.length);
|
||||
expect(paginate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not trigger or consume pagination shortcuts from editable targets", async () => {
|
||||
const { host, paginate } = mountGrid();
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
const targets = [document.createElement("input"), document.createElement("textarea"), document.createElement("div"), document.createElement("div")];
|
||||
targets[2]!.setAttribute("contenteditable", "true");
|
||||
targets[3]!.setAttribute("role", "textbox");
|
||||
|
||||
for (const target of targets) {
|
||||
root.append(target);
|
||||
const event = dispatchShortcut(target, "F3");
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
}
|
||||
await settle();
|
||||
|
||||
expect(bubbled).toHaveBeenCalledTimes(targets.length);
|
||||
expect(paginate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("routes shortcuts to the existing pagination functions and preserves PageUp/PageDown navigation", () => {
|
||||
expect(dataGridSource).toMatch(/if \(!targetAllowsNativeClipboard && handleGridPaginationShortcut\(event\)\) return;/);
|
||||
for (const { actionId, functionName } of shortcutCases) {
|
||||
expect(dataGridSource).toContain(`is${actionId[0]!.toUpperCase()}${actionId.slice(1)}Shortcut(event, shortcuts)`);
|
||||
expect(dataGridSource).toContain(`navigate = ${functionName}`);
|
||||
}
|
||||
expect(dataGridSource).toContain('event.key === "PageUp" && navigateSelectedCell("pageUp", event.shiftKey)');
|
||||
expect(dataGridSource).toContain('event.key === "PageDown" && navigateSelectedCell("pageDown", event.shiftKey)');
|
||||
});
|
||||
|
||||
it("keeps legacy settings without pagination mappings keyboard-neutral", async () => {
|
||||
const { host, paginate } = mountGrid({ configurePaginationShortcuts: false });
|
||||
await settle();
|
||||
const root = gridRoot(host);
|
||||
const bubbled = vi.fn();
|
||||
host.addEventListener("keydown", bubbled);
|
||||
|
||||
const event = dispatchShortcut(root, "F3");
|
||||
await settle();
|
||||
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
expect(bubbled).toHaveBeenCalledOnce();
|
||||
expect(paginate).not.toHaveBeenCalled();
|
||||
const actionIds: ShortcutActionId[] = shortcutCases.map(({ actionId }) => actionId);
|
||||
expect(actionIds).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
@@ -6519,6 +6519,10 @@ export default {
|
||||
shortcutCopyCurrentRow: "Copy current data row",
|
||||
shortcutDeleteCurrentRow: "Delete current data row",
|
||||
shortcutGoToColumn: "Go to column",
|
||||
shortcutGoToFirstPage: "First page",
|
||||
shortcutGoToPreviousPage: "Previous page",
|
||||
shortcutGoToNextPage: "Next page",
|
||||
shortcutGoToLastPage: "Last page",
|
||||
shortcutNewQuery: "New query",
|
||||
shortcutOpenSettings: "Open settings",
|
||||
shortcutCloseTab: "Close tab",
|
||||
|
||||
@@ -6190,6 +6190,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "Copiar fila de datos actual",
|
||||
shortcutDeleteCurrentRow: "Eliminar fila de datos actual",
|
||||
shortcutGoToColumn: "Ir a la columna",
|
||||
shortcutGoToFirstPage: "Primera página",
|
||||
shortcutGoToPreviousPage: "Página anterior",
|
||||
shortcutGoToNextPage: "Página siguiente",
|
||||
shortcutGoToLastPage: "Última página",
|
||||
shortcutNewQuery: "Nueva consulta",
|
||||
shortcutOpenSettings: "Abrir configuración",
|
||||
shortcutCloseTab: "Cerrar pestaña",
|
||||
|
||||
@@ -6190,6 +6190,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "Copia riga dati corrente",
|
||||
shortcutDeleteCurrentRow: "Elimina riga dati corrente",
|
||||
shortcutGoToColumn: "Vai alla colonna",
|
||||
shortcutGoToFirstPage: "Prima pagina",
|
||||
shortcutGoToPreviousPage: "Pagina precedente",
|
||||
shortcutGoToNextPage: "Pagina successiva",
|
||||
shortcutGoToLastPage: "Ultima pagina",
|
||||
shortcutNewQuery: "Nuova query",
|
||||
shortcutOpenSettings: "Apri impostazioni",
|
||||
shortcutCloseTab: "Chiudi scheda",
|
||||
|
||||
@@ -6204,6 +6204,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "現在のデータ行をコピー",
|
||||
shortcutDeleteCurrentRow: "現在のデータ行を削除",
|
||||
shortcutGoToColumn: "列へ移動",
|
||||
shortcutGoToFirstPage: "最初のページ",
|
||||
shortcutGoToPreviousPage: "前のページ",
|
||||
shortcutGoToNextPage: "次のページ",
|
||||
shortcutGoToLastPage: "最後のページ",
|
||||
shortcutNewQuery: "新しいクエリ",
|
||||
shortcutOpenSettings: "設定を開く",
|
||||
shortcutCloseTab: "タブを閉じる",
|
||||
|
||||
@@ -5901,6 +5901,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "현재 데이터 행 복사",
|
||||
shortcutDeleteCurrentRow: "현재 데이터 행 삭제",
|
||||
shortcutGoToColumn: "컬럼으로 이동",
|
||||
shortcutGoToFirstPage: "첫 페이지",
|
||||
shortcutGoToPreviousPage: "이전 페이지",
|
||||
shortcutGoToNextPage: "다음 페이지",
|
||||
shortcutGoToLastPage: "마지막 페이지",
|
||||
shortcutNewQuery: "새 쿼리",
|
||||
shortcutOpenSettings: "설정 열기",
|
||||
shortcutCloseTab: "탭 닫기",
|
||||
|
||||
@@ -6192,6 +6192,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "Copiar linha de dados atual",
|
||||
shortcutDeleteCurrentRow: "Excluir linha de dados atual",
|
||||
shortcutGoToColumn: "Ir para a coluna",
|
||||
shortcutGoToFirstPage: "Primeira página",
|
||||
shortcutGoToPreviousPage: "Página anterior",
|
||||
shortcutGoToNextPage: "Próxima página",
|
||||
shortcutGoToLastPage: "Última página",
|
||||
shortcutNewQuery: "Nova consulta",
|
||||
shortcutOpenSettings: "Abrir configurações",
|
||||
shortcutCloseTab: "Fechar aba",
|
||||
|
||||
@@ -6505,6 +6505,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "复制当前数据行",
|
||||
shortcutDeleteCurrentRow: "删除当前数据行",
|
||||
shortcutGoToColumn: "跳转列",
|
||||
shortcutGoToFirstPage: "第一页",
|
||||
shortcutGoToPreviousPage: "上一页",
|
||||
shortcutGoToNextPage: "下一页",
|
||||
shortcutGoToLastPage: "末页",
|
||||
shortcutNewQuery: "新建查询",
|
||||
shortcutOpenSettings: "打开设置",
|
||||
shortcutCloseTab: "关闭标签页",
|
||||
|
||||
@@ -5497,6 +5497,10 @@ export default withEnglishFallback({
|
||||
shortcutCopyCurrentRow: "複製目前資料列",
|
||||
shortcutDeleteCurrentRow: "刪除目前資料列",
|
||||
shortcutGoToColumn: "跳轉欄",
|
||||
shortcutGoToFirstPage: "第一頁",
|
||||
shortcutGoToPreviousPage: "上一頁",
|
||||
shortcutGoToNextPage: "下一頁",
|
||||
shortcutGoToLastPage: "末頁",
|
||||
shortcutNewQuery: "建立查詢",
|
||||
shortcutOpenSettings: "開啟設定",
|
||||
shortcutCloseTab: "關閉分頁",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { eventToModifierOnlyShortcut, eventToShortcut, isExecuteSqlInNewResultTabShortcut, isGoToColumnShortcut, matchesModifierOnlyShortcut, matchesShortcut, tabSwitcherDirectionFromShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import {
|
||||
eventToModifierOnlyShortcut,
|
||||
eventToShortcut,
|
||||
isExecuteSqlInNewResultTabShortcut,
|
||||
isGoToColumnShortcut,
|
||||
isGoToFirstPageShortcut,
|
||||
isGoToLastPageShortcut,
|
||||
isGoToNextPageShortcut,
|
||||
isGoToPreviousPageShortcut,
|
||||
matchesModifierOnlyShortcut,
|
||||
matchesShortcut,
|
||||
tabSwitcherDirectionFromShortcut,
|
||||
} from "@/lib/editor/keyboardShortcuts";
|
||||
import { formatShortcutDisplay, isMacShortcutPlatform } from "@/lib/editor/shortcutDisplay";
|
||||
|
||||
describe("keyboard shortcut matching", () => {
|
||||
@@ -94,6 +106,21 @@ describe("keyboard shortcut matching", () => {
|
||||
expect(isGoToColumnShortcut({ key: "g", ctrlKey: true }, { goToColumn: "" })).toBe(false);
|
||||
expect(isGoToColumnShortcut({ key: "g", ctrlKey: true, isComposing: true }, { goToColumn: "Mod+G" })).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["goToFirstPage", isGoToFirstPageShortcut, "F1"],
|
||||
["goToPreviousPage", isGoToPreviousPageShortcut, "F2"],
|
||||
["goToNextPage", isGoToNextPageShortcut, "F3"],
|
||||
["goToLastPage", isGoToLastPageShortcut, "F4"],
|
||||
] as const)("matches only the configured pagination shortcut for %s", (actionId, matcher, key) => {
|
||||
const shortcuts = { [actionId]: `Alt+${key}` };
|
||||
|
||||
expect(matcher({ key, altKey: true }, shortcuts)).toBe(true);
|
||||
expect(matcher({ key }, shortcuts)).toBe(false);
|
||||
expect(matcher({ key: "F8", altKey: true }, shortcuts)).toBe(false);
|
||||
expect(matcher({ key, altKey: true, isComposing: true }, shortcuts)).toBe(false);
|
||||
expect(matcher({ key, altKey: true })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tabSwitcherDirectionFromShortcut", () => {
|
||||
|
||||
@@ -25,6 +25,43 @@ describe("shortcutRegistry editor actions", () => {
|
||||
];
|
||||
const sidebarShortcutActionIds: ShortcutActionId[] = ["copySidebarSelection", "pasteSidebarSelection", "editSidebarConnection", "viewTableDdl"];
|
||||
|
||||
it("registers pagination navigation as unassigned grid shortcuts", () => {
|
||||
const paginationActions = [
|
||||
["goToFirstPage", "settings.shortcutGoToFirstPage"],
|
||||
["goToPreviousPage", "settings.shortcutGoToPreviousPage"],
|
||||
["goToNextPage", "settings.shortcutGoToNextPage"],
|
||||
["goToLastPage", "settings.shortcutGoToLastPage"],
|
||||
] as const;
|
||||
|
||||
for (const [id, labelKey] of paginationActions) {
|
||||
expect(SHORTCUT_DEFINITIONS.find((item) => item.id === id)).toMatchObject({ id, labelKey, scope: "grid", defaultShortcut: "" });
|
||||
expect(DEFAULT_SHORTCUT_SETTINGS[id]).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes missing, legacy, cleared, and configured pagination shortcuts", () => {
|
||||
const missing = normalizeShortcutSettings();
|
||||
const legacy = normalizeShortcutSettings({ goToColumn: "Mod+G" });
|
||||
const configured = normalizeShortcutSettings({ goToFirstPage: "Alt+F1", goToPreviousPage: "Alt+F2", goToNextPage: "Alt+F3", goToLastPage: "Alt+F4" });
|
||||
|
||||
for (const actionId of ["goToFirstPage", "goToPreviousPage", "goToNextPage", "goToLastPage"] as const) {
|
||||
expect(missing[actionId]).toBe("");
|
||||
expect(legacy[actionId]).toBe("");
|
||||
}
|
||||
expect(configured.goToFirstPage).toBe("Alt+F1");
|
||||
expect(configured.goToPreviousPage).toBe("Alt+F2");
|
||||
expect(configured.goToNextPage).toBe("Alt+F3");
|
||||
expect(configured.goToLastPage).toBe("Alt+F4");
|
||||
expect(configured.goToColumn).toBe("");
|
||||
});
|
||||
|
||||
it("detects pagination shortcut conflicts in the grid scope", () => {
|
||||
const shortcuts = normalizeShortcutSettings({ goToFirstPage: "Alt+F1", goToPreviousPage: "Alt+F1" });
|
||||
|
||||
expect(findShortcutConflict("goToFirstPage", shortcuts.goToFirstPage, shortcuts)).toBe("goToPreviousPage");
|
||||
expect(findShortcutConflict("goToFirstPage", "Mod+F", shortcuts)).toBeNull();
|
||||
});
|
||||
|
||||
it("registers go to column as an unassigned grid shortcut", () => {
|
||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "goToColumn");
|
||||
|
||||
|
||||
@@ -211,6 +211,22 @@ export function isGoToColumnShortcut(event: ShortcutLikeEvent, shortcuts?: Parti
|
||||
return matchesShortcut(event, actionShortcut("goToColumn", shortcuts));
|
||||
}
|
||||
|
||||
export function isGoToFirstPageShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("goToFirstPage", shortcuts));
|
||||
}
|
||||
|
||||
export function isGoToPreviousPageShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("goToPreviousPage", shortcuts));
|
||||
}
|
||||
|
||||
export function isGoToNextPageShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("goToNextPage", shortcuts));
|
||||
}
|
||||
|
||||
export function isGoToLastPageShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("goToLastPage", shortcuts));
|
||||
}
|
||||
|
||||
export function isCancelSearchShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("cancelSearch", shortcuts));
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ export type ShortcutActionId =
|
||||
| "copyCurrentRow"
|
||||
| "deleteCurrentRow"
|
||||
| "goToColumn"
|
||||
| "goToFirstPage"
|
||||
| "goToPreviousPage"
|
||||
| "goToNextPage"
|
||||
| "goToLastPage"
|
||||
| "newQuery"
|
||||
| "openSettings"
|
||||
| "closeTab"
|
||||
@@ -287,6 +291,30 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||
scope: "grid",
|
||||
defaultShortcut: "",
|
||||
},
|
||||
{
|
||||
id: "goToFirstPage",
|
||||
labelKey: "settings.shortcutGoToFirstPage",
|
||||
scope: "grid",
|
||||
defaultShortcut: "",
|
||||
},
|
||||
{
|
||||
id: "goToPreviousPage",
|
||||
labelKey: "settings.shortcutGoToPreviousPage",
|
||||
scope: "grid",
|
||||
defaultShortcut: "",
|
||||
},
|
||||
{
|
||||
id: "goToNextPage",
|
||||
labelKey: "settings.shortcutGoToNextPage",
|
||||
scope: "grid",
|
||||
defaultShortcut: "",
|
||||
},
|
||||
{
|
||||
id: "goToLastPage",
|
||||
labelKey: "settings.shortcutGoToLastPage",
|
||||
scope: "grid",
|
||||
defaultShortcut: "",
|
||||
},
|
||||
{
|
||||
id: "newQuery",
|
||||
labelKey: "settings.shortcutNewQuery",
|
||||
|
||||
Reference in new issue
Block a user