Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ Derived properties allow the component to expose complex state that can be usefu

## RC8 - Improve props typing

Issue: https://github.com/plotly/dash-table/issues/143
Issue: https://github.com/plotly/dash-table/issues/143

## RC9 - Sort ascending on first click

Expand All @@ -417,4 +417,9 @@ Derived properties allow the component to expose complex state that can be usefu

## RC12 - Rename selected_cell -> selected_cells

Issue: https://github.com/plotly/dash-table/issues/177
Issue: https://github.com/plotly/dash-table/issues/177

## RC13 - Allow keyboard navigation on focused input

Issue: https://github.com/plotly/dash-table/issues/141
Issue: https://github.com/plotly/dash-table/issues/143
4 changes: 2 additions & 2 deletions dash_table/bundle.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dash_table/demo.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dash_table/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dash-table",
"version": "3.1.0rc12",
"version": "3.1.0rc13",
"description": "Dash table",
"main": "build/index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dash-table",
"version": "3.1.0rc12",
"version": "3.1.0rc13",
"description": "Dash table",
"main": "build/index.js",
"scripts": {
Expand Down
16 changes: 13 additions & 3 deletions src/dash-table/components/CellInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from 'dash-table/components/CellInput/props';

import {
KEY_CODES
KEY_CODES, isNavKey
} from 'dash-table/utils/unicode';
import { ColumnType } from 'dash-table/components/Table/props';
import dropdownHelper from 'dash-table/components/dropdownHelper';
Expand Down Expand Up @@ -93,6 +93,7 @@ export default class CellInput extends PureComponent<ICellProps, ICellState> {
focused,
onClick,
onDoubleClick,
onMouseUp,
onPaste
} = this.propsWithDefaults;

Expand All @@ -105,7 +106,8 @@ export default class CellInput extends PureComponent<ICellProps, ICellState> {
const attributes = {
className: classes.join(' '),
onClick: onClick,
onDoubleClick: onDoubleClick
onDoubleClick: onDoubleClick,
onMouseUp: onMouseUp
};

const readonly = (!active && this.state.value === this.props.value) || !editable;
Expand Down Expand Up @@ -166,7 +168,15 @@ export default class CellInput extends PureComponent<ICellProps, ICellState> {
}

handleKeyDown = (e: KeyboardEvent) => {
if (e.keyCode !== KEY_CODES.ENTER && e.keyCode !== KEY_CODES.TAB) {
const is_focused = this.props.focused;

if (is_focused &&
(e.keyCode !== KEY_CODES.TAB && e.keyCode !== KEY_CODES.ENTER)
) {
return;
}

if(!is_focused && !isNavKey(e.keyCode)) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions src/dash-table/components/CellInput/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ICellHandlerProps {
onChange: (e: ChangeEvent) => void;
onClick: (e: React.MouseEvent) => void;
onDoubleClick: (e: React.MouseEvent) => void;
onMouseUp: (e: React.MouseEvent) => void;
onPaste: (e: React.ClipboardEvent<Element>) => void;
}

Expand Down
17 changes: 7 additions & 10 deletions src/dash-table/components/ControlledTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,20 @@ export default class ControlledTable extends PureComponent<ControlledTableProps,
return;
}

if (
e.keyCode === KEY_CODES.ENTER &&
!is_focused &&
isEditable(editable, columns[active_cell[1]])
if(!is_focused &&
isNavKey(e.keyCode)
) {
setProps({ is_focused: true });
return;
this.switchCell(e);
}

if (
is_focused &&
(e.keyCode !== KEY_CODES.TAB && e.keyCode !== KEY_CODES.ENTER)
!isNavKey(e.keyCode)
) {
return;
}

if (isNavKey(e.keyCode)) {
if (e.keyCode === KEY_CODES.TAB || e.keyCode === KEY_CODES.ENTER) {
this.switchCell(e);
return;
}
Expand All @@ -236,11 +233,11 @@ export default class ControlledTable extends PureComponent<ControlledTableProps,
} else if (
// if we have any non-meta key enter editable mode

!this.props.is_focused &&
!is_focused &&
isEditable(editable, columns[active_cell[1]]) &&
!isMetaKey(e.keyCode)
) {
setProps({ is_focused: true });
// setProps({ is_focused: true });
}

return;
Expand Down
6 changes: 6 additions & 0 deletions src/dash-table/components/Table/Table.less
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@
position: absolute;
left: 0;
top: 0;
&.unfocused::selection {
background-color: transparent;
}
&.unfocused {
caret-color: transparent;
}
}

div.dash-cell-value {
Expand Down
4 changes: 3 additions & 1 deletion src/dash-table/derived/cell/eventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { memoizeOneFactory } from 'core/memoizer';
import memoizerCache from 'core/memoizerCache';
import { ICellFactoryOptions } from 'dash-table/components/Table/props';
import { handleChange, handleClick, handleDoubleClick, handlePaste } from 'dash-table/handlers/cellEvents';
import { handleChange, handleClick, handleDoubleClick, handleOnMouseUp, handlePaste } from 'dash-table/handlers/cellEvents';

type CacheArgs = [Handler, number, number];
type GetterArgs = [HandlerFn, number, number];
Expand All @@ -11,6 +11,7 @@ export enum Handler {
Change = 'change',
Click = 'click',
DoubleClick = 'doubleclick',
MouseUp = 'mouseup',
Paste = 'paste'
}

Expand All @@ -32,6 +33,7 @@ const getter = (propsFn: () => ICellFactoryOptions): CacheFn => {
[Handler.Change, handleChange.bind(undefined, propsFn)],
[Handler.Click, handleClick.bind(undefined, propsFn)],
[Handler.DoubleClick, handleDoubleClick.bind(undefined, propsFn)],
[Handler.MouseUp, handleOnMouseUp.bind(undefined, propsFn)],
[Handler.Paste, handlePaste.bind(undefined, propsFn)]
]);

Expand Down
1 change: 1 addition & 0 deletions src/dash-table/derived/cell/eventHandlerProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const getter = (propsFn: () => ICellFactoryOptions): CacheFn => {
onChange: derivedHandlers(Handler.Change, rowIndex, columnIndex),
onClick: derivedHandlers(Handler.Click, rowIndex, columnIndex),
onDoubleClick: derivedHandlers(Handler.DoubleClick, rowIndex, columnIndex),
onMouseUp: derivedHandlers(Handler.MouseUp, rowIndex, columnIndex),
onPaste: derivedHandlers(Handler.Paste, rowIndex, columnIndex)
} as ICellHandlerProps;
};
Expand Down
27 changes: 21 additions & 6 deletions src/dash-table/handlers/cellEvents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as R from 'ramda';
import { SelectedCells, ICellFactoryOptions } from 'dash-table/components/Table/props';
import isActive from 'dash-table/derived/cell/isActive';

function isCellSelected(selectedCells: SelectedCells, idx: number, i: number) {
return selectedCells && R.contains([idx, i], selectedCells);
Expand All @@ -8,19 +9,15 @@ function isCellSelected(selectedCells: SelectedCells, idx: number, i: number) {
export const handleClick = (propsFn: () => ICellFactoryOptions, idx: number, i: number, e: any) => {
const {
editable,
is_focused,
selected_cells,
setProps
} = propsFn();

const selected = isCellSelected(selected_cells, idx, i);

if (!editable) {
return;
}
if (!is_focused) {
e.preventDefault();
}

const selected = isCellSelected(selected_cells, idx, i);

// don't update if already selected
if (selected) {
Expand Down Expand Up @@ -105,6 +102,24 @@ export const handleChange = (propsFn: () => ICellFactoryOptions, idx: number, i:
setProps({
data: newData
});

};

export const handleOnMouseUp = (propsFn: () => ICellFactoryOptions, idx: number, i: number, e: any) => {
const {
active_cell,
is_focused,
} = propsFn();

const active = isActive(active_cell, idx, i);

if(!is_focused && active) {
e.preventDefault();
// We do this because mouseMove can change the selection, we don't want
// to check for all mouse movements, for performance reasons.
const input = e.target;
input.setSelectionRange(0, input.value ? input.value.length : 0);
}
};

export const handlePaste = (_propsFn: () => ICellFactoryOptions, _idx: number, _i: number, e: ClipboardEvent) => {
Expand Down
72 changes: 72 additions & 0 deletions tests/cypress/tests/server/dash_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,78 @@ describe('dash basic', () => {
});
});
});
describe('ArrowKeys navigation', () => {
describe('When active, but not focused', () => {
// https://github.com/plotly/dash-table/issues/141
it('can edit last, update data on "arrowleft", and move one cell to the left', () => {
const startingCell = [249,1]
const targetCell = [249, 0]
DashTable.getCell(startingCell[0], startingCell[1]).click();
DOM.focused.then($input => {
const initialValue = $input.val();

DOM.focused.type(`abc${Key.ArrowLeft}`);

cy.get('#container').should($container => {
expect($container.first()[0].innerText).to.equal(`[249][1] = ${initialValue} -> abc`);
});
});
DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused');
});

// https://github.com/plotly/dash-table/issues/141
it('can edit last, update data on "arrowup", and move one cell up', () => {
const startingCell = [249,0]
const targetCell = [248, 0]
DashTable.getCell(startingCell[0], startingCell[1]).click();
DOM.focused.then($input => {
const initialValue = $input.val();

DOM.focused.type(`abc${Key.ArrowUp}`);

cy.get('#container').should($container => {
expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`);
});
});
DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused');
});

// https://github.com/plotly/dash-table/issues/141
it('can edit last, update data on "arrowright", and move one cell to the right', () => {
const startingCell = [249,0]
const targetCell = [249, 1]
DashTable.getCell(startingCell[0], startingCell[1]).click();
DOM.focused.then($input => {
const initialValue = $input.val();

DOM.focused.type(`abc${Key.ArrowRight}`);

cy.get('#container').should($container => {
expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`);
});
});
DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused');
});


// https://github.com/plotly/dash-table/issues/141
it('can edit last, update data on "arrowdown", and move one cell down', () => {
const startingCell = [249,0]
const targetCell = [249, 1]
DashTable.getCell(startingCell[0], startingCell[1]).click();
DOM.focused.then($input => {
const initialValue = $input.val();

DOM.focused.type(`abc${Key.ArrowRight}`);

cy.get('#container').should($container => {
expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`);
});
});
DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused');
});
})
})

it('can edit last and update data when clicking outside of cell', () => {
DashTable.getCell(249, 0).click();
Expand Down
Loading