From 6017ccc91a227b440608862445c725644cba27ac Mon Sep 17 00:00:00 2001 From: dineug Date: Mon, 30 Oct 2023 04:35:51 +0900 Subject: [PATCH] feat: keyboard focus --- .../src/components/erd-editor/ErdEditor.ts | 25 + packages/erd-editor/src/components/erd/Erd.ts | 5 +- .../erd/canvas/table/column/Column.styles.ts | 1 - .../erd/canvas/table/column/Column.ts | 1 - .../src/components/erd/useErdShortcut.ts | 79 +++- .../primitives/edit-input/EditInput.ts | 15 +- .../src/engine/modules/editor/atom.actions.ts | 11 +- .../modules/editor/generator.actions.ts | 54 ++- .../src/engine/modules/editor/state.ts | 3 + .../src/engine/modules/editor/utils/focus.ts | 438 ++++++++++++++++++ .../modules/editor/utils/selectRangeColumn.ts | 4 +- .../modules/tableColumn/generator.actions.ts | 34 +- .../erd-editor/src/hooks/useKeyBindingMap.ts | 4 +- .../erd-editor/src/utils/internalEvents.ts | 24 + 14 files changed, 668 insertions(+), 30 deletions(-) create mode 100644 packages/erd-editor/src/engine/modules/editor/utils/focus.ts create mode 100644 packages/erd-editor/src/utils/internalEvents.ts diff --git a/packages/erd-editor/src/components/erd-editor/ErdEditor.ts b/packages/erd-editor/src/components/erd-editor/ErdEditor.ts index b908134a..a521b9ef 100644 --- a/packages/erd-editor/src/components/erd-editor/ErdEditor.ts +++ b/packages/erd-editor/src/components/erd-editor/ErdEditor.ts @@ -8,6 +8,7 @@ import { ref, useProvider, } from '@dineug/r-html'; +import { fromEvent, merge, throttleTime } from 'rxjs'; import { appContext, createAppContext } from '@/components/context'; import Erd from '@/components/erd/Erd'; @@ -18,6 +19,7 @@ import { changeViewportAction } from '@/engine/modules/editor/atom.actions'; import { useKeyBindingMap } from '@/hooks/useKeyBindingMap'; import { useUnmounted } from '@/hooks/useUnmounted'; import { AccentColor, createTheme, GrayColor } from '@/themes/radix-ui-theme'; +import { InternalEventType } from '@/utils/internalEvents'; import { createText } from '@/utils/text'; import * as styles from './ErdEditor.styles'; @@ -57,6 +59,29 @@ const ErdEditor: FC = (props, ctx) => { keydown$.next(event); }; + const handleFocus = () => { + const $root = root.value; + setTimeout(() => { + if (document.activeElement !== ctx) { + $root.focus(); + } + }, 1); + }; + + onMounted(() => { + const $root = root.value; + handleFocus(); + addUnsubscribe( + merge( + fromEvent($root, 'mousedown'), + fromEvent($root, 'touchstart'), + fromEvent(ctx, InternalEventType.focus) + ) + .pipe(throttleTime(50)) + .subscribe(handleFocus) + ); + }); + onMounted(() => { const $root = root.value; const { store } = appContextValue; diff --git a/packages/erd-editor/src/components/erd/Erd.ts b/packages/erd-editor/src/components/erd/Erd.ts index bb3eecfb..5c05619a 100644 --- a/packages/erd-editor/src/components/erd/Erd.ts +++ b/packages/erd-editor/src/components/erd/Erd.ts @@ -65,7 +65,10 @@ const Erd: FC = (props, ctx) => { const el = event.target as HTMLElement | null; if (!el) return; - const canDrag = !el.closest('.table') && !el.closest('.memo'); + const canDrag = + !el.closest('.table') && + !el.closest('.memo') && + !el.closest('.edit-input'); if (!canDrag) return; const { store } = app.value; diff --git a/packages/erd-editor/src/components/erd/canvas/table/column/Column.styles.ts b/packages/erd-editor/src/components/erd/canvas/table/column/Column.styles.ts index 4e399b20..c50788ba 100644 --- a/packages/erd-editor/src/components/erd/canvas/table/column/Column.styles.ts +++ b/packages/erd-editor/src/components/erd/canvas/table/column/Column.styles.ts @@ -15,7 +15,6 @@ export const root = css` fill: transparent; color: transparent; padding: 0 ${TABLE_PADDING}px; - transition: background-color 0.15s; &:hover { fill: var(--foreground); diff --git a/packages/erd-editor/src/components/erd/canvas/table/column/Column.ts b/packages/erd-editor/src/components/erd/canvas/table/column/Column.ts index 0731e9b1..0ee4d5cd 100644 --- a/packages/erd-editor/src/components/erd/canvas/table/column/Column.ts +++ b/packages/erd-editor/src/components/erd/canvas/table/column/Column.ts @@ -331,7 +331,6 @@ const Column: FC = (props, ctx) => { title=${simpleShortcutToString( keyBindingMap.removeColumn[0]?.shortcut )} - useTransition=${true} .onClick=${handleRemoveColumn} /> diff --git a/packages/erd-editor/src/components/erd/useErdShortcut.ts b/packages/erd-editor/src/components/erd/useErdShortcut.ts index 3893ac7c..350b056d 100644 --- a/packages/erd-editor/src/components/erd/useErdShortcut.ts +++ b/packages/erd-editor/src/components/erd/useErdShortcut.ts @@ -1,12 +1,25 @@ -import { onMounted } from '@dineug/r-html'; +import { nextTick, onMounted } from '@dineug/r-html'; import { useAppContext } from '@/components/context'; -import { selectAllAction } from '@/engine/modules/editor/atom.actions'; -import { removeSelectedAction$ } from '@/engine/modules/editor/generator.actions'; +import { + editTableAction, + editTableEndAction, + focusMoveTableAction, + selectAllAction, +} from '@/engine/modules/editor/atom.actions'; +import { + focusMoveTableAction$, + removeSelectedAction$, +} from '@/engine/modules/editor/generator.actions'; +import { hasMoveKeys, MoveKey } from '@/engine/modules/editor/state'; import { addMemoAction$ } from '@/engine/modules/memo/generator.actions'; import { streamZoomLevelAction } from '@/engine/modules/settings/atom.actions'; import { addTableAction$ } from '@/engine/modules/table/generator.actions'; -import { addColumnAction$ } from '@/engine/modules/tableColumn/generator.actions'; +import { + addColumnAction$, + isToggleColumnTypes, + toggleColumnValueAction$, +} from '@/engine/modules/tableColumn/generator.actions'; import { useUnmounted } from '@/hooks/useUnmounted'; import { KeyBindingName } from '@/utils/keyboard-shortcut'; @@ -15,10 +28,66 @@ export function useErdShortcut(ctx: Parameters[0]) { const { addUnsubscribe } = useUnmounted(); onMounted(() => { - const { store, shortcut$ } = app.value; + const { store, shortcut$, keydown$ } = app.value; addUnsubscribe( + keydown$.subscribe(event => { + const { editor } = store.state; + + if ( + editor.focusTable && + !editor.focusTable.edit && + event.key !== MoveKey.Tab && + hasMoveKeys(event.key) + ) { + store.dispatch( + focusMoveTableAction({ + moveKey: event.key, + shiftKey: event.shiftKey, + }) + ); + } + + if (editor.focusTable && event.key === MoveKey.Tab) { + event.preventDefault(); + store.dispatch(focusMoveTableAction$(event.key, event.shiftKey)); + + nextTick(() => { + if ( + !editor.focusTable || + isToggleColumnTypes(editor.focusTable.focusType) + ) { + return; + } + + store.dispatch(editTableAction()); + }); + } + }), shortcut$.subscribe(action => { + const { editor } = store.state; + + if (editor.focusTable && action === KeyBindingName.edit) { + const focusTable = editor.focusTable; + + if (focusTable.edit) { + store.dispatch(editTableEndAction()); + } else if ( + focusTable.columnId && + isToggleColumnTypes(focusTable.focusType) + ) { + store.dispatch( + toggleColumnValueAction$( + focusTable.focusType, + focusTable.tableId, + focusTable.columnId + ) + ); + } else { + store.dispatch(editTableAction()); + } + } + switch (action) { case KeyBindingName.edit: break; diff --git a/packages/erd-editor/src/components/primitives/edit-input/EditInput.ts b/packages/erd-editor/src/components/primitives/edit-input/EditInput.ts index 8c7fd8ce..dd04ad82 100644 --- a/packages/erd-editor/src/components/primitives/edit-input/EditInput.ts +++ b/packages/erd-editor/src/components/primitives/edit-input/EditInput.ts @@ -2,6 +2,7 @@ import { createRef, FC, html, onBeforeMount, ref, watch } from '@dineug/r-html'; import { useUnmounted } from '@/hooks/useUnmounted'; import { lastCursorFocus } from '@/utils/focus'; +import { focusEvent } from '@/utils/internalEvents'; import * as styles from './EditInput.styles'; @@ -31,6 +32,11 @@ const EditInput: FC = (props, ctx) => { }; }; + const handleBlur = (event: FocusEvent) => { + props.onBlur?.(event); + ctx.host.dispatchEvent(focusEvent()); + }; + onBeforeMount(() => { addUnsubscribe( watch(props).subscribe(propName => { @@ -40,6 +46,13 @@ const EditInput: FC = (props, ctx) => { } lastCursorFocus($input); + }), + watch(props).subscribe(propName => { + if (propName !== 'edit') return; + + if (!props.edit) { + ctx.host.dispatchEvent(focusEvent()); + } }) ); }); @@ -60,7 +73,7 @@ const EditInput: FC = (props, ctx) => { title=${props.title} .value=${props.value ?? ''} @input=${props.onInput} - @blur=${props.onBlur} + @blur=${handleBlur} @keyup=${props.onKeyup} /> ` diff --git a/packages/erd-editor/src/engine/modules/editor/atom.actions.ts b/packages/erd-editor/src/engine/modules/editor/atom.actions.ts index cb4bc0d7..76373e6e 100644 --- a/packages/erd-editor/src/engine/modules/editor/atom.actions.ts +++ b/packages/erd-editor/src/engine/modules/editor/atom.actions.ts @@ -5,6 +5,7 @@ import { query } from '@/utils/collection/query'; import { ActionMap, ActionType, ReducerType } from './actions'; import { FocusType, MoveKey, SelectType } from './state'; +import { arrowDown, arrowLeft, arrowRight, arrowUp } from './utils/focus'; import { appendSelectColumns, appendSelectRangeColumns, @@ -238,19 +239,19 @@ const focusMoveTable: ReducerType = ( switch (payload.moveKey) { case MoveKey.ArrowUp: - // arrowUp + arrowUp(state, payload); break; case MoveKey.ArrowDown: - // arrowDown + arrowDown(state, payload); break; case MoveKey.ArrowLeft: - // arrowLeft + arrowLeft(state, payload); break; case MoveKey.ArrowRight: - // arrowRight + arrowRight(state, payload); break; case MoveKey.Tab: - // shiftKey ? arrowLeft : arrowRight + payload.shiftKey ? arrowLeft(state, payload) : arrowRight(state, payload); break; } }; diff --git a/packages/erd-editor/src/engine/modules/editor/generator.actions.ts b/packages/erd-editor/src/engine/modules/editor/generator.actions.ts index 0273ad90..335b35df 100644 --- a/packages/erd-editor/src/engine/modules/editor/generator.actions.ts +++ b/packages/erd-editor/src/engine/modules/editor/generator.actions.ts @@ -1,23 +1,33 @@ import { isEmpty } from 'lodash-es'; import { GeneratorAction } from '@/engine/generator.actions'; -import { - clearAction, - focusTableEndAction, - loadJsonAction, - selectAction, - unselectAllAction, -} from '@/engine/modules/editor/atom.actions'; -import { SelectType } from '@/engine/modules/editor/state'; import { moveMemoAction } from '@/engine/modules/memo/atom.actions'; import { removeMemoAction$ } from '@/engine/modules/memo/generator.actions'; import { moveTableAction } from '@/engine/modules/table/atom.actions'; import { removeTableAction$ } from '@/engine/modules/table/generator.actions'; +import { addColumnAction$ } from '@/engine/modules/tableColumn/generator.actions'; import { Point } from '@/internal-types'; import { calcMemoHeight, calcMemoWidth } from '@/utils/calcMemo'; import { calcTableHeight, calcTableWidths } from '@/utils/calcTable'; import { query } from '@/utils/collection/query'; +import { + clearAction, + focusMoveTableAction, + focusTableEndAction, + loadJsonAction, + selectAction, + unselectAllAction, +} from './atom.actions'; +import { MoveKey, SelectType } from './state'; +import { + isColumns, + isLastColumn, + isLastRowColumn, + isLastTable, + isTableFocusType, +} from './utils/focus'; + type SelectTypeIds = { tableIds: string[]; memoIds: string[]; @@ -134,9 +144,37 @@ export const unselectAllAction$ = (): GeneratorAction => yield focusTableEndAction(); }; +export const focusMoveTableAction$ = ( + moveKey: MoveKey, + shiftKey: boolean +): GeneratorAction => + function* (state) { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return; + + if ( + moveKey === MoveKey.Tab && + !shiftKey && + ((isTableFocusType(focusTable.focusType) && + isLastTable(state) && + !isColumns(state)) || + (!isTableFocusType(focusTable.focusType) && + isLastColumn(state) && + isLastRowColumn(state))) + ) { + yield addColumnAction$(focusTable.tableId); + } else { + yield focusMoveTableAction({ moveKey, shiftKey }); + } + }; + export const actions$ = { loadJsonAction$, moveAllAction$, removeSelectedAction$, dragSelectAction$, + unselectAllAction$, + focusMoveTableAction$, }; diff --git a/packages/erd-editor/src/engine/modules/editor/state.ts b/packages/erd-editor/src/engine/modules/editor/state.ts index 0b229cb7..62680797 100644 --- a/packages/erd-editor/src/engine/modules/editor/state.ts +++ b/packages/erd-editor/src/engine/modules/editor/state.ts @@ -1,3 +1,5 @@ +import { arrayHas } from '@dineug/shared'; + import { DEFAULT_HEIGHT, DEFAULT_WIDTH } from '@/constants/layout'; import { ValuesType } from '@/internal-types'; @@ -50,6 +52,7 @@ export const MoveKey = { Tab: 'Tab', }; export type MoveKey = ValuesType; +export const hasMoveKeys = arrayHas(Object.values(MoveKey)); export const createEditor = (): Editor => ({ selectedMap: {}, diff --git a/packages/erd-editor/src/engine/modules/editor/utils/focus.ts b/packages/erd-editor/src/engine/modules/editor/utils/focus.ts new file mode 100644 index 00000000..94080640 --- /dev/null +++ b/packages/erd-editor/src/engine/modules/editor/utils/focus.ts @@ -0,0 +1,438 @@ +import { SchemaV3Constants } from '@dineug/erd-editor-schema'; +import { arrayHas } from '@dineug/shared'; + +import { ActionMap, ActionType } from '@/engine/modules/editor/actions'; +import { FocusTable, FocusType, MoveKey } from '@/engine/modules/editor/state'; +import { RootState } from '@/engine/state'; +import { Table } from '@/internal-types'; +import { bHas } from '@/utils/bit'; +import { query } from '@/utils/collection/query'; + +import { appendSelectColumns } from './selectRangeColumn'; + +type Payload = ActionMap[typeof ActionType.focusMoveTable]; + +const ColumnType = SchemaV3Constants.ColumnType; +const Show = SchemaV3Constants.Show; +const ColumnTypeToShowType: Record = { + [ColumnType.columnName]: true, + [ColumnType.columnDataType]: Show.columnDataType, + [ColumnType.columnNotNull]: Show.columnNotNull, + [ColumnType.columnUnique]: Show.columnUnique, + [ColumnType.columnAutoIncrement]: Show.columnAutoIncrement, + [ColumnType.columnDefault]: Show.columnDefault, + [ColumnType.columnComment]: Show.columnComment, +}; +const ColumnTypeToFocusType: Record = { + [ColumnType.columnName]: FocusType.columnName, + [ColumnType.columnDataType]: FocusType.columnDataType, + [ColumnType.columnNotNull]: FocusType.columnNotNull, + [ColumnType.columnUnique]: FocusType.columnUnique, + [ColumnType.columnAutoIncrement]: FocusType.columnAutoIncrement, + [ColumnType.columnDefault]: FocusType.columnDefault, + [ColumnType.columnComment]: FocusType.columnComment, +}; +const TableFocusTypes: FocusType[] = [ + FocusType.tableName, + FocusType.tableComment, +]; + +function getColumnTypes({ + settings: { show, columnOrder }, +}: RootState): FocusType[] { + return columnOrder + .filter(key => { + const showType = ColumnTypeToShowType[key]; + return showType === true ? true : bHas(show, showType); + }) + .map(key => ColumnTypeToFocusType[key]); +} + +export function isColumns({ collections, editor: { focusTable } }: RootState) { + if (!focusTable) return false; + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + return Boolean(table?.columnIds.length); +} + +export function isLastColumn(state: RootState) { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return true; + + const columnTypes = getColumnTypes(state); + const index = columnTypes.indexOf(focusTable.focusType); + return index === columnTypes.length - 1; +} + +function isFirstColumn(state: RootState) { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return true; + + const columnTypes = getColumnTypes(state); + const index = columnTypes.indexOf(focusTable.focusType); + return index === 0; +} + +export function isLastRowColumn({ + collections, + editor: { focusTable }, +}: RootState) { + if (!focusTable?.columnId) { + return true; + } + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return true; + + const index = table.columnIds.indexOf(focusTable.columnId); + return index === table.columnIds.length - 1; +} + +function isFirstRowColumn({ collections, editor: { focusTable } }: RootState) { + if (!focusTable?.columnId) { + return true; + } + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return true; + + const index = table.columnIds.indexOf(focusTable.columnId); + return index === 0; +} + +function getLastColumnType(state: RootState): FocusType { + const columnTypes = getColumnTypes(state); + return columnTypes[columnTypes.length - 1]; +} + +function getFirstColumnType(state: RootState): FocusType { + const columnTypes = getColumnTypes(state); + return columnTypes[0]; +} + +function getNextRightColumnType(state: RootState): FocusType { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return FocusType.columnName; + + const columnTypes = getColumnTypes(state); + const index = columnTypes.indexOf(focusTable.focusType); + return isLastColumn(state) ? columnTypes[0] : columnTypes[index + 1]; +} + +function getNextLeftColumnType(state: RootState): FocusType { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return FocusType.columnName; + + const columnTypes = getColumnTypes(state); + const index = columnTypes.indexOf(focusTable.focusType); + return isFirstColumn(state) + ? columnTypes[columnTypes.length - 1] + : columnTypes[index - 1]; +} + +export function getRemoveFirstColumnId(state: RootState, columnIds: string[]) { + const { + collections, + editor: { focusTable }, + } = state; + if (!focusTable?.columnId) return null; + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return null; + + const columnIndex = table.columnIds.indexOf(focusTable.columnId); + if (columnIndex <= 0) return null; + + let columnId = null; + for (let i = columnIndex; i >= 0; i--) { + const currentColumnId = table.columnIds[i]; + + if (!columnIds.includes(currentColumnId)) { + columnId = currentColumnId; + break; + } + } + + return columnId; +} + +function getTableTypes({ settings: { show } }: RootState): FocusType[] { + return bHas(show, Show.tableComment) + ? TableFocusTypes + : [FocusType.tableName]; +} + +export function isLastTable(state: RootState) { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return true; + + const tableTypes = getTableTypes(state); + const index = tableTypes.indexOf(focusTable.focusType); + return index === tableTypes.length - 1; +} + +function isFirstTable(state: RootState) { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return true; + + const tableTypes = getTableTypes(state); + const index = tableTypes.indexOf(focusTable.focusType); + return index === 0; +} + +export const isTableFocusType = arrayHas(TableFocusTypes); + +function getNextRightTableType(state: RootState): FocusType { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return FocusType.tableName; + + const tableTypes = getTableTypes(state); + const index = tableTypes.indexOf(focusTable.focusType); + return isLastTable(state) ? tableTypes[0] : tableTypes[index + 1]; +} + +function getNextLeftTableType(state: RootState): FocusType { + const { + editor: { focusTable }, + } = state; + if (!focusTable) return FocusType.tableName; + + const tableTypes = getTableTypes(state); + const index = tableTypes.indexOf(focusTable.focusType); + return isFirstTable(state) + ? tableTypes[tableTypes.length - 1] + : tableTypes[index - 1]; +} + +export function arrowUp(state: RootState, payload: Payload) { + const { + collections, + editor: { focusTable }, + } = state; + if (!focusTable) return; + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return; + + if (isTableFocusType(focusTable.focusType)) { + if (isColumns(state)) { + const columnId = table.columnIds[table.columnIds.length - 1]; + + focusTable.focusType = getLastColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + focusTable.selectColumnIds = [columnId]; + } + } else { + if (isFirstRowColumn(state)) { + focusTable.focusType = FocusType.tableName; + focusTable.columnId = null; + focusTable.prevSelectColumnId = null; + focusTable.selectColumnIds = []; + } else if (focusTable.columnId) { + const index = table.columnIds.indexOf(focusTable.columnId); + const columnId = table.columnIds[index - 1]; + + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + if (payload.shiftKey && payload.moveKey !== MoveKey.Tab) { + focusTable.selectColumnIds = appendSelectColumns( + focusTable.selectColumnIds, + columnId + ); + } else { + focusTable.selectColumnIds = [columnId]; + } + } + } +} + +export function arrowDown(state: RootState, payload: Payload) { + const { + collections, + editor: { focusTable }, + } = state; + if (!focusTable) return; + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return; + + if (isTableFocusType(focusTable.focusType)) { + if (isColumns(state)) { + const columnId = table.columnIds[0]; + + focusTable.focusType = getFirstColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + focusTable.selectColumnIds = [columnId]; + } + } else { + if (isLastRowColumn(state)) { + focusTable.focusType = FocusType.tableName; + focusTable.columnId = null; + focusTable.prevSelectColumnId = null; + focusTable.selectColumnIds = []; + } else if (focusTable.columnId) { + const index = table.columnIds.indexOf(focusTable.columnId); + const columnId = table.columnIds[index + 1]; + + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + if (payload.shiftKey && payload.moveKey !== MoveKey.Tab) { + focusTable.selectColumnIds = appendSelectColumns( + focusTable.selectColumnIds, + columnId + ); + } else { + focusTable.selectColumnIds = [columnId]; + } + } + } +} + +export function arrowLeft(state: RootState, payload: Payload) { + const { + collections, + editor: { focusTable }, + } = state; + if (!focusTable) return; + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return; + + if (isTableFocusType(focusTable.focusType)) { + if (isFirstTable(state)) { + if (isColumns(state)) { + const columnId = table.columnIds[table.columnIds.length - 1]; + + focusTable.focusType = getLastColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + focusTable.selectColumnIds = [columnId]; + } else { + focusTable.focusType = getNextLeftTableType(state); + } + } else { + focusTable.focusType = getNextLeftTableType(state); + } + } else { + if (isFirstColumn(state)) { + if (isFirstRowColumn(state)) { + focusTable.focusType = bHas(state.settings.show, Show.tableComment) + ? FocusType.tableComment + : FocusType.tableName; + focusTable.columnId = null; + focusTable.prevSelectColumnId = null; + focusTable.selectColumnIds = []; + } else if (focusTable.columnId) { + const index = table.columnIds.indexOf(focusTable.columnId); + const columnId = table.columnIds[index - 1]; + + focusTable.focusType = getLastColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + if (payload.shiftKey && payload.moveKey !== MoveKey.Tab) { + focusTable.selectColumnIds = appendSelectColumns( + focusTable.selectColumnIds, + columnId + ); + } else { + focusTable.selectColumnIds = [columnId]; + } + } + } else { + focusTable.focusType = getNextLeftColumnType(state); + if (!payload.shiftKey && focusTable.columnId) { + focusTable.prevSelectColumnId = focusTable.columnId; + focusTable.selectColumnIds = [focusTable.columnId]; + } + } + } +} + +export function arrowRight(state: RootState, payload: Payload) { + const { + collections, + editor: { focusTable }, + } = state; + if (!focusTable) return; + + const table = query(collections) + .collection('tableEntities') + .selectById(focusTable.tableId); + if (!table) return; + + if (isTableFocusType(focusTable.focusType)) { + if (isLastTable(state)) { + if (isColumns(state)) { + const columnId = table.columnIds[0]; + + focusTable.focusType = getFirstColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + focusTable.selectColumnIds = [columnId]; + } else { + focusTable.focusType = getNextRightTableType(state); + } + } else { + focusTable.focusType = getNextRightTableType(state); + } + } else { + if (isLastColumn(state)) { + if (isLastRowColumn(state)) { + focusTable.focusType = FocusType.tableName; + focusTable.columnId = null; + focusTable.prevSelectColumnId = null; + focusTable.selectColumnIds = []; + } else if (focusTable.columnId) { + const index = table.columnIds.indexOf(focusTable.columnId); + const columnId = table.columnIds[index + 1]; + + focusTable.focusType = getFirstColumnType(state); + focusTable.columnId = columnId; + focusTable.prevSelectColumnId = columnId; + if (payload.shiftKey && payload.moveKey !== MoveKey.Tab) { + focusTable.selectColumnIds = appendSelectColumns( + focusTable.selectColumnIds, + columnId + ); + } else { + focusTable.selectColumnIds = [columnId]; + } + } + } else { + focusTable.focusType = getNextRightColumnType(state); + if (!payload.shiftKey && focusTable.columnId) { + focusTable.prevSelectColumnId = focusTable.columnId; + focusTable.selectColumnIds = [focusTable.columnId]; + } + } + } +} diff --git a/packages/erd-editor/src/engine/modules/editor/utils/selectRangeColumn.ts b/packages/erd-editor/src/engine/modules/editor/utils/selectRangeColumn.ts index 6e41b74c..a89c0cae 100644 --- a/packages/erd-editor/src/engine/modules/editor/utils/selectRangeColumn.ts +++ b/packages/erd-editor/src/engine/modules/editor/utils/selectRangeColumn.ts @@ -13,8 +13,8 @@ export function selectRangeColumns( ) { if (!fromColumnId || fromColumnId === toColumnId) return [toColumnId]; - const fromIndex = columnIds.findIndex(id => id === fromColumnId); - const toIndex = columnIds.findIndex(id => id === toColumnId); + const fromIndex = columnIds.indexOf(fromColumnId); + const toIndex = columnIds.indexOf(toColumnId); if (fromIndex === -1) return [toColumnId]; diff --git a/packages/erd-editor/src/engine/modules/tableColumn/generator.actions.ts b/packages/erd-editor/src/engine/modules/tableColumn/generator.actions.ts index 123770f3..c3d7e7a2 100644 --- a/packages/erd-editor/src/engine/modules/tableColumn/generator.actions.ts +++ b/packages/erd-editor/src/engine/modules/tableColumn/generator.actions.ts @@ -3,8 +3,12 @@ import { arrayHas } from '@dineug/shared'; import { last } from 'lodash-es'; import { GeneratorAction } from '@/engine/generator.actions'; -import { focusColumnAction } from '@/engine/modules/editor/atom.actions'; +import { + focusColumnAction, + focusTableAction, +} from '@/engine/modules/editor/atom.actions'; import { FocusType, SelectType } from '@/engine/modules/editor/state'; +import { getRemoveFirstColumnId } from '@/engine/modules/editor/utils/focus'; import { uuid } from '@/utils'; import { bHas } from '@/utils/bit'; import { query } from '@/utils/collection/query'; @@ -79,11 +83,31 @@ export const removeColumnAction$ = ( tableId: string, columnIds: string[] ): GeneratorAction => - function* () { - // TODO: valid index, relationship - - // getRemoveFirstColumnId - focusTable, focusColumn + function* (state) { + const { + editor: { focusTable }, + } = state; + + if (focusTable?.columnId) { + const columnId = getRemoveFirstColumnId(state, columnIds); + + if (columnId) { + yield focusColumnAction({ + tableId: focusTable.tableId, + columnId, + focusType: focusTable.focusType, + $mod: false, + shiftKey: false, + }); + } else { + yield focusTableAction({ + tableId: focusTable.tableId, + focusType: FocusType.tableName, + }); + } + } + // TODO: valid index, relationship for (const columnId of columnIds) { yield removeColumnAction({ id: columnId, diff --git a/packages/erd-editor/src/hooks/useKeyBindingMap.ts b/packages/erd-editor/src/hooks/useKeyBindingMap.ts index 852a5a59..ef38e928 100644 --- a/packages/erd-editor/src/hooks/useKeyBindingMap.ts +++ b/packages/erd-editor/src/hooks/useKeyBindingMap.ts @@ -17,9 +17,11 @@ export function useKeyBindingMap( const keyBinding = () => { const { keyBindingMap, shortcut$ } = app.value; + const $root = root.value; + unbinding(); unbinding = tinykeys( - root.value, + $root, Object.keys(keyBindingMap).reduce< Record void> >((acc, key) => { diff --git a/packages/erd-editor/src/utils/internalEvents.ts b/packages/erd-editor/src/utils/internalEvents.ts new file mode 100644 index 00000000..a22b9ae8 --- /dev/null +++ b/packages/erd-editor/src/utils/internalEvents.ts @@ -0,0 +1,24 @@ +import { ValuesType } from '@/internal-types'; + +export const InternalEventType = { + focus: 'erd-editor-internal-focus', +} as const; +export type InternalEventType = ValuesType; + +export type InternalEventMap = { + [InternalEventType.focus]: void; +}; + +function createInternalEvent

(type: string) { + function actionCreator(payload: P): CustomEvent

{ + return new CustomEvent(type, { detail: payload }); + } + + actionCreator.toString = () => `${type}`; + actionCreator.type = type; + return actionCreator; +} + +export const focusEvent = createInternalEvent< + InternalEventMap[typeof InternalEventType.focus] +>(InternalEventType.focus);