diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.test.tsx b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.test.tsx
new file mode 100644
index 0000000000..13ae2dc8d3
--- /dev/null
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.test.tsx
@@ -0,0 +1,294 @@
+import { aColumn, aTable } from '@liam-hq/schema'
+import { render, screen, within } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { ReactFlowProvider } from '@xyflow/react'
+import { Command } from 'cmdk'
+import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
+import type { ReactNode } from 'react'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import {
+ SchemaProvider,
+ type SchemaProviderValue,
+ UserEditingProvider,
+} from '../../../../../../stores'
+import * as UseTableSelection from '../../../../hooks'
+import { CommandPaletteProvider } from '../CommandPaletteProvider'
+import * as UseCommandPalette from '../CommandPaletteProvider/hooks'
+import { TableColumnOptions } from './TableColumnOptions'
+
+beforeEach(() => {
+ window.location.hash = ''
+})
+
+afterEach(() => {
+ vi.clearAllMocks()
+})
+
+const mockSetCommandPaletteDialogOpen = vi.fn()
+const mockSelectTable = vi.fn()
+const mockWindowOpen = vi.fn()
+
+const originalUseCommandPaletteOrThrow =
+ UseCommandPalette.useCommandPaletteOrThrow
+vi.spyOn(UseCommandPalette, 'useCommandPaletteOrThrow').mockImplementation(
+ () => {
+ const original = originalUseCommandPaletteOrThrow()
+ return {
+ ...original,
+ setOpen: mockSetCommandPaletteDialogOpen,
+ }
+ },
+)
+const originalUseTableSelection = UseTableSelection.useTableSelection
+vi.spyOn(UseTableSelection, 'useTableSelection').mockImplementation(() => {
+ const original = originalUseTableSelection()
+ return {
+ ...original,
+ selectTable: mockSelectTable,
+ }
+})
+vi.spyOn(window, 'open').mockImplementation(mockWindowOpen)
+
+const schema: SchemaProviderValue = {
+ current: {
+ tables: {
+ users: aTable({
+ name: 'users',
+ columns: {
+ id: aColumn({ name: 'id' }),
+ created_at: aColumn({ name: 'created_at', type: 'timestamp' }),
+ },
+ }),
+ posts: aTable({ name: 'posts' }),
+ },
+ enums: {},
+ extensions: {},
+ },
+}
+
+const wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+
+
+ {children}
+
+
+
+
+
+)
+
+it('displays selected table option and its columns', () => {
+ render(, {
+ wrapper,
+ })
+
+ // table option
+ const userTableOption = screen.getByRole('option', { name: 'users' })
+ expect(userTableOption).toBeInTheDocument()
+ expect(within(userTableOption).getByRole('link')).toHaveAttribute(
+ 'href',
+ '?active=users',
+ )
+
+ // column options
+ const idColumnOption = screen.getByRole('option', { name: 'id' })
+ expect(idColumnOption).toBeInTheDocument()
+ expect(within(idColumnOption).getByRole('link')).toHaveAttribute(
+ 'href',
+ '?active=users#users__columns__id',
+ )
+ const createdAtColumnOption = screen.getByRole('option', {
+ name: 'created_at',
+ })
+ expect(createdAtColumnOption).toBeInTheDocument()
+ expect(within(createdAtColumnOption).getByRole('link')).toHaveAttribute(
+ 'href',
+ '?active=users#users__columns__created_at',
+ )
+
+ // other tables are not displayed
+ expect(screen.queryByRole('link', { name: 'posts' })).not.toBeInTheDocument()
+})
+
+describe('mouse interactions', () => {
+ describe('table option', () => {
+ it('moves to clicked table in ERD and closes the dialog', async () => {
+ const user = userEvent.setup()
+ render(, {
+ wrapper,
+ })
+
+ await user.click(screen.getByRole('link', { name: 'users' }))
+
+ expect(mockSelectTable).toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ })
+
+ it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
+ const user = userEvent.setup()
+ render(, {
+ wrapper,
+ })
+
+ await user.keyboard('{Meta>}')
+ await user.click(screen.getByRole('link', { name: 'users' }))
+ await user.keyboard('{/Meta}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('column options', () => {
+ it('moves to clicked table column in ERD and closes the dialog', async () => {
+ const user = userEvent.setup()
+ render(, {
+ wrapper,
+ })
+
+ await user.click(screen.getByRole('link', { name: 'created_at' }))
+
+ expect(mockSelectTable).toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ expect(window.location.hash).toBe('#users__columns__created_at')
+ })
+
+ it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
+ const user = userEvent.setup()
+ render(, {
+ wrapper,
+ })
+
+ await user.keyboard('{Meta>}')
+ await user.click(screen.getByRole('link', { name: 'created_at' }))
+ await user.keyboard('{/Meta}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+
+ // FIXME: jsdom doesn't implement behavior of ⌘ + click to open a link in a new tab, but it changes the URL of the current window
+ // So, the following assertion doesn't pass
+ // expect(window.location.hash).toBe('')
+ })
+ })
+})
+
+describe('keyboard interactions', () => {
+ describe('table option', () => {
+ it('moves to suggested table in ERD and closes the dialog on Enter', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Enter}')
+
+ expect(mockSelectTable).toHaveBeenCalledWith({
+ displayArea: 'main',
+ tableId: 'users',
+ })
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ expect(window.location.hash).toBe('')
+
+ // other functions are not called
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+
+ it('opens suggested table in another tab on ⌘Enter', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockWindowOpen).toHaveBeenCalledWith('?active=users')
+
+ // other functions are not called
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('column option', () => {
+ it('moves to suggested table column in ERD and closes the dialog on Enter', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Enter}')
+
+ expect(mockSelectTable).toHaveBeenCalledWith({
+ displayArea: 'main',
+ tableId: 'users',
+ })
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ expect(window.location.hash).toBe('#users__columns__created_at')
+
+ // other functions are not called
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+
+ it('opens suggested table column in another tab on ⌘Enter', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockWindowOpen).toHaveBeenCalledWith(
+ '?active=users#users__columns__created_at',
+ )
+
+ // other functions are not called
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ it('does nothing on Enter when suggestion is not table', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+})
diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.tsx b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.tsx
index 683fb3c037..8c19119603 100644
--- a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.tsx
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.tsx
@@ -7,24 +7,16 @@ import {
} from '@liam-hq/ui'
import clsx from 'clsx'
import { Command } from 'cmdk'
+import { type ComponentProps, type FC, useMemo } from 'react'
import {
- type ComponentProps,
- type FC,
- useCallback,
- useEffect,
- useMemo,
-} from 'react'
-import {
- getTableColumnElementId,
getTableColumnLinkHref,
getTableLinkHref,
} from '../../../../../../features'
import { useSchemaOrThrow } from '../../../../../../stores'
-import { useTableSelection } from '../../../../hooks'
-import { useCommandPaletteOrThrow } from '../CommandPaletteProvider'
import type { CommandPaletteSuggestion } from '../types'
import { getSuggestionText } from '../utils'
import styles from './CommandPaletteOptions.module.css'
+import { useTableOptionSelect } from './hooks/useTableOptionSelect'
import { type ColumnType, getColumnTypeMap } from './utils/getColumnTypeMap'
type Props = {
@@ -50,65 +42,8 @@ const ColumnIcon: FC & { columnType: ColumnType }> = ({
export const TableColumnOptions: FC = ({ tableName, suggestion }) => {
const schema = useSchemaOrThrow()
- const { selectTable } = useTableSelection()
- const { setOpen } = useCommandPaletteOrThrow()
-
- const goToERD = useCallback(
- (tableName: string, columnName?: string) => {
- selectTable({ tableId: tableName, displayArea: 'main' })
- setOpen(false)
- if (columnName) {
- window.location.hash = getTableColumnElementId(tableName, columnName)
- }
- },
- [setOpen, selectTable],
- )
-
- // Select option by pressing [Enter] key (with/without ⌘ key)
- useEffect(() => {
- // It doesn't subscribe a keydown event listener if the suggestion type is not "table"
- if (suggestion?.type !== 'table') return
-
- const down = (event: KeyboardEvent) => {
- const suggestedTableName = suggestion.name
-
- if (event.key === 'Enter') {
- event.preventDefault()
-
- if (event.metaKey || event.ctrlKey) {
- window.open(getTableLinkHref(suggestedTableName))
- } else {
- goToERD(suggestedTableName)
- }
- }
- }
-
- document.addEventListener('keydown', down)
- return () => document.removeEventListener('keydown', down)
- }, [suggestion, goToERD])
- // Select option by pressing [Enter] key (with/without ⌘ key)
- useEffect(() => {
- // It doesn't subscribe a keydown event listener if the suggestion type is not "column"
- if (suggestion?.type !== 'column') return
-
- const down = (event: KeyboardEvent) => {
- const { tableName, columnName } = suggestion
-
- if (event.key === 'Enter') {
- event.preventDefault()
-
- if (event.metaKey || event.ctrlKey) {
- window.open(getTableColumnLinkHref(tableName, columnName))
- } else {
- goToERD(tableName, columnName)
- }
- }
- }
-
- document.addEventListener('keydown', down)
- return () => document.removeEventListener('keydown', down)
- }, [suggestion, goToERD])
+ const { optionSelectHandler } = useTableOptionSelect(suggestion)
const table = schema.current.tables[tableName]
const columnTypeMap = useMemo(
@@ -128,15 +63,7 @@ export const TableColumnOptions: FC = ({ tableName, suggestion }) => {
{
- // Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
- if (event.ctrlKey || event.metaKey) {
- return
- }
-
- event.preventDefault()
- goToERD(table.name)
- }}
+ onClick={(event) => optionSelectHandler(event, table.name)}
>
{table.name}
@@ -154,15 +81,9 @@ export const TableColumnOptions: FC = ({ tableName, suggestion }) => {
{
- // Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
- if (event.ctrlKey || event.metaKey) {
- return
- }
-
- event.preventDefault()
- goToERD(table.name, column.name)
- }}
+ onClick={(event) =>
+ optionSelectHandler(event, table.name, column.name)
+ }
>
{columnTypeMap[column.name] && (
= ({ suggestion }) => {
- const { setOpen } = useCommandPaletteOrThrow()
-
const schema = useSchemaOrThrow()
- const { selectTable } = useTableSelection()
-
- const goToERD = useCallback(
- (tableName: string) => {
- selectTable({ tableId: tableName, displayArea: 'main' })
- setOpen(false)
- },
- [selectTable, setOpen],
- )
-
- // Select option by pressing [Enter] key (with/without ⌘ key)
- useEffect(() => {
- // It doesn't subscribe a keydown event listener if the suggestion type is not "table"
- if (suggestion?.type !== 'table') return
- const down = (event: KeyboardEvent) => {
- const suggestedTableName = suggestion.name
-
- if (event.key === 'Enter') {
- event.preventDefault()
-
- if (event.metaKey || event.ctrlKey) {
- window.open(getTableLinkHref(suggestedTableName))
- } else {
- goToERD(suggestedTableName)
- }
- }
- }
-
- document.addEventListener('keydown', down)
- return () => document.removeEventListener('keydown', down)
- }, [suggestion, goToERD])
+ const { optionSelectHandler } = useTableOptionSelect(suggestion)
return (
@@ -60,15 +27,7 @@ export const TableOptions: FC = ({ suggestion }) => {
{
- // Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
- if (event.ctrlKey || event.metaKey) {
- return
- }
-
- event.preventDefault()
- goToERD(table.name)
- }}
+ onClick={(event) => optionSelectHandler(event, table.name)}
>
{table.name}
diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.test.tsx b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.test.tsx
new file mode 100644
index 0000000000..7474f7f157
--- /dev/null
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.test.tsx
@@ -0,0 +1,265 @@
+import { render, renderHook, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { ReactFlowProvider } from '@xyflow/react'
+import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
+import type { FC, ReactNode } from 'react'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { UserEditingProvider } from '../../../../../../../stores'
+import * as UseTableSelection from '../../../../../hooks'
+import { CommandPaletteProvider } from '../../CommandPaletteProvider'
+import * as UseCommandPalette from '../../CommandPaletteProvider/hooks'
+import type { CommandPaletteSuggestion } from '../../types'
+import { useTableOptionSelect } from './useTableOptionSelect'
+
+beforeEach(() => {
+ window.location.hash = ''
+})
+
+afterEach(() => {
+ vi.clearAllMocks()
+})
+
+const mockSetCommandPaletteDialogOpen = vi.fn()
+const mockSelectTable = vi.fn()
+const mockWindowOpen = vi.fn()
+
+const originalUseCommandPaletteOrThrow =
+ UseCommandPalette.useCommandPaletteOrThrow
+vi.spyOn(UseCommandPalette, 'useCommandPaletteOrThrow').mockImplementation(
+ () => {
+ const original = originalUseCommandPaletteOrThrow()
+ return {
+ ...original,
+ setOpen: mockSetCommandPaletteDialogOpen,
+ }
+ },
+)
+const originalUseTableSelection = UseTableSelection.useTableSelection
+vi.spyOn(UseTableSelection, 'useTableSelection').mockImplementation(() => {
+ const original = originalUseTableSelection()
+ return {
+ ...original,
+ selectTable: mockSelectTable,
+ }
+})
+vi.spyOn(window, 'open').mockImplementation(mockWindowOpen)
+
+const wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+)
+
+describe('keyboard interactions', () => {
+ describe('when suggestion is a table', () => {
+ it('moves to suggested table in ERD and closes the dialog on Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(() => useTableOptionSelect({ type: 'table', name: 'users' }), {
+ wrapper,
+ })
+
+ await user.keyboard('{Enter}')
+
+ expect(mockSelectTable).toHaveBeenCalledWith({
+ displayArea: 'main',
+ tableId: 'users',
+ })
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+
+ // other functions are not called
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+
+ it('opens suggested table in another tab on ⌘Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(() => useTableOptionSelect({ type: 'table', name: 'users' }), {
+ wrapper,
+ })
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockWindowOpen).toHaveBeenCalledWith('?active=users')
+
+ // other functions are not called
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('when suggestion is a column', () => {
+ it('moves to suggested table column in ERD and closes the dialog on Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(
+ () =>
+ useTableOptionSelect({
+ type: 'column',
+ tableName: 'users',
+ columnName: 'name',
+ }),
+ { wrapper },
+ )
+
+ await user.keyboard('{Enter}')
+
+ expect(mockSelectTable).toHaveBeenCalledWith({
+ displayArea: 'main',
+ tableId: 'users',
+ })
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+
+ // other functions are not called
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+
+ it('opens suggested table in another tab on ⌘Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(
+ () =>
+ useTableOptionSelect({
+ type: 'column',
+ tableName: 'users',
+ columnName: 'name',
+ }),
+ { wrapper },
+ )
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockWindowOpen).toHaveBeenCalledWith(
+ '?active=users#users__columns__name',
+ )
+
+ // other functions are not called
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ describe.each([
+ { type: 'command', name: 'copy link' },
+ null,
+ ])('when suggestion is other than tables, suggestion = %o', (suggestion) => {
+ it('does nothing when on Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(() => useTableOptionSelect(suggestion), { wrapper })
+
+ await user.keyboard('{Enter}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+
+ it('does nothing when on ⌘Enter', async () => {
+ const user = userEvent.setup()
+ renderHook(() => useTableOptionSelect(suggestion), { wrapper })
+
+ await user.keyboard('{Meta>}{Enter}{/Meta}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ expect(mockWindowOpen).not.toHaveBeenCalled()
+ })
+ })
+})
+
+describe('optionSelectHandler', () => {
+ // a component for testing the "optionSelectHandler" method of the hook
+ // in the test cases, we simulate the method clicking a link of a table option
+ const TableOptionLinkWithSelectHandler: FC<{
+ tableName: string
+ columnName: string | undefined
+ }> = ({ tableName, columnName }) => {
+ const { optionSelectHandler } = useTableOptionSelect(null)
+
+ return (
+ optionSelectHandler(event, tableName, columnName)}
+ >
+ table option link
+
+ )
+ }
+
+ describe('when passing only tableName', () => {
+ it('moves to clicked table in ERD and closes the dialog', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.click(screen.getByRole('link', { name: 'table option link' }))
+
+ expect(mockSelectTable).toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ })
+
+ it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.keyboard('{Meta>}')
+ await user.click(screen.getByRole('link', { name: 'table option link' }))
+ await user.keyboard('{/Meta}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('when passing both tableName and columnName', () => {
+ it('moves to clicked table in ERD and closes the dialog', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ { wrapper },
+ )
+
+ await user.click(screen.getByRole('link', { name: 'table option link' }))
+
+ expect(mockSelectTable).toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
+ expect(window.location.hash).toBe('#follows__columns__user_id')
+ })
+
+ it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
+ const user = userEvent.setup()
+ render(
+ ,
+ {
+ wrapper,
+ },
+ )
+
+ await user.keyboard('{Meta>}')
+ await user.click(screen.getByRole('link', { name: 'table option link' }))
+ await user.keyboard('{/Meta}')
+
+ expect(mockSelectTable).not.toHaveBeenCalled()
+ expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
+ expect(window.location.hash).toBe('')
+ })
+ })
+})
diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.ts b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.ts
new file mode 100644
index 0000000000..e184496bd0
--- /dev/null
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.ts
@@ -0,0 +1,89 @@
+import { useCallback, useEffect } from 'react'
+import { useTableSelection } from '../../../../../../erd/hooks'
+import {
+ getTableColumnElementId,
+ getTableColumnLinkHref,
+ getTableLinkHref,
+} from '../../../../../utils'
+import { useCommandPaletteOrThrow } from '../../CommandPaletteProvider'
+import type { CommandPaletteSuggestion } from '../../types'
+
+export const useTableOptionSelect = (
+ suggestion: CommandPaletteSuggestion | null,
+) => {
+ const { setOpen } = useCommandPaletteOrThrow()
+
+ const { selectTable } = useTableSelection()
+ const goToERD = useCallback(
+ (tableName: string, columnName?: string) => {
+ selectTable({ tableId: tableName, displayArea: 'main' })
+ if (columnName) {
+ window.location.hash = getTableColumnElementId(tableName, columnName)
+ }
+
+ setOpen(false)
+ },
+ [selectTable, setOpen],
+ )
+
+ const optionSelectHandler = useCallback(
+ (event: React.MouseEvent, tableName: string, columnName?: string) => {
+ // Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
+ if (event.ctrlKey || event.metaKey) {
+ return
+ }
+
+ event.preventDefault()
+ goToERD(tableName, columnName)
+ },
+ [goToERD],
+ )
+
+ // Select table option by pressing [Enter] key (with/without ⌘ key)
+ useEffect(() => {
+ // It doesn't subscribe a keydown event listener if the suggestion type is not "table"
+ if (suggestion?.type !== 'table') return
+
+ const down = (event: KeyboardEvent) => {
+ const suggestedTableName = suggestion.name
+
+ if (event.key === 'Enter') {
+ event.preventDefault()
+
+ if (event.metaKey || event.ctrlKey) {
+ window.open(getTableLinkHref(suggestedTableName))
+ } else {
+ goToERD(suggestedTableName)
+ }
+ }
+ }
+
+ document.addEventListener('keydown', down)
+ return () => document.removeEventListener('keydown', down)
+ }, [suggestion, goToERD])
+
+ // Select column option by pressing [Enter] key (with/without ⌘ key)
+ useEffect(() => {
+ // It doesn't subscribe a keydown event listener if the suggestion type is not "column"
+ if (suggestion?.type !== 'column') return
+
+ const down = (event: KeyboardEvent) => {
+ const { tableName, columnName } = suggestion
+
+ if (event.key === 'Enter') {
+ event.preventDefault()
+
+ if (event.metaKey || event.ctrlKey) {
+ window.open(getTableColumnLinkHref(tableName, columnName))
+ } else {
+ goToERD(tableName, columnName)
+ }
+ }
+ }
+
+ document.addEventListener('keydown', down)
+ return () => document.removeEventListener('keydown', down)
+ }, [suggestion, goToERD])
+
+ return { optionSelectHandler }
+}