Skip to content
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
54 changes: 54 additions & 0 deletions src/firefly/js/tables/__tests__/InputCell-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
*/
/*eslint-env node, jest */

import React from 'react';
import '@testing-library/jest-dom';
import {act, fireEvent, render, screen} from '@testing-library/react';

import {createInputCell, inputColumnRenderer} from '../ui/TableRenderer.js';

const TIP = 'syntax help';

const cellBuilders = {
createInputCell: () => createInputCell(TIP, 10, () => ({valid: true})),
inputColumnRenderer: () => inputColumnRenderer({tbl_id: 't1', cname: 'c', tooltips: TIP}),
};

const hover = (el) => {
fireEvent.mouseOver(el);
act(() => jest.advanceTimersByTime(1000));
};

describe.each(Object.keys(cellBuilders))('%s syntax tooltip', (name) => {
beforeEach(() => jest.useFakeTimers());
afterEach(() => jest.useRealTimers());

const data = [['a'], ['b']];

test('hover alone shows no tooltip', () => {
const Cell = cellBuilders[name]();
render(<Cell rowIndex={0} data={data} colIdx={0}/>);
hover(screen.getByRole('textbox'));
expect(screen.queryByRole('tooltip')).toBeNull();
});

test('focus + hover shows the tooltip', () => {
const Cell = cellBuilders[name]();
render(<Cell rowIndex={0} data={data} colIdx={0}/>);
const input = screen.getByRole('textbox');
act(() => input.focus());
hover(input);
expect(screen.getByRole('tooltip')).toHaveTextContent(TIP);
});

test('a cell reused for another row forgets focus that was never blurred', () => {
const Cell = cellBuilders[name]();
const {rerender} = render(<Cell rowIndex={0} data={data} colIdx={0}/>);
act(() => screen.getByRole('textbox').focus());
rerender(<Cell rowIndex={1} data={data} colIdx={0}/>); // table swaps the row; the focused input is removed, no blur
hover(screen.getByRole('textbox'));
expect(screen.queryByRole('tooltip')).toBeNull();
});
});
36 changes: 29 additions & 7 deletions src/firefly/js/tables/ui/TableRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,14 @@ export function HeaderText({val, level='body-sm', sx, ...rest}) {
}

const blurEnter = ['blur','enter'];
const syntaxTipProps = {enterDelay: 300}; // filter boxes and constraint cells can open their syntax tooltip quickly

function Filter({cname, onFilter, filterInfo, tbl_id}) {

const colGetter= () => getColumn(getTblById((tbl_id)), cname) ?? {};
const col = useStoreConnector(colGetter);
const [showTooltip, setShowTooltip] = useState(true);
const [editing, setEditing] = useState(false); // the syntax tooltip shows only while this box has focus
const dropdownEl = useRef(null);

useEffect(() => {
Expand Down Expand Up @@ -191,13 +193,19 @@ function Filter({cname, onFilter, filterInfo, tbl_id}) {
validator={validator}
fieldKey={name}
sx={{width: 1, '--Input-radius': ''}}
tooltip={showTooltip && FILTER_CONDITION_TTIPS}
tooltip={editing && showTooltip && FILTER_CONDITION_TTIPS}
value={filterInfoCls.getFilter(name)}
onChange={onFilter}
actOn={blurEnter}
showWarning={false}
slotProps={{
input: {size:'sm', endDecorator }
input: {size:'sm', endDecorator, autoComplete: 'off'},
// Handle focus on the dropdown or checkboxes bubbling up and triggering the tooltip
control: {
onFocus: (e) => setEditing(e.target.tagName === 'INPUT' && e.currentTarget.contains(e.target)),
onBlur: () => setEditing(false),
},
tooltip: syntaxTipProps,
}}
/>
);
Expand Down Expand Up @@ -530,8 +538,14 @@ export const createInputCell = (tooltips, size = 10, validator, onChange, style)
onChange && onChange(v, data, rowIndex, colIdx);
};

return ({rowIndex, data, colIdx}) => {
const val = get(data, [rowIndex, colIdx]);
return function InputCell({rowIndex, data, colIdx}) {
const [editing, setEditing] = useState(false); // the syntax tooltip shows only while this box has focus
const [shownRow, setShownRow] = useState(rowIndex);
if (shownRow !== rowIndex) { // clear editing when a scroll moves the cell to another row
setShownRow(rowIndex);
setEditing(false);
}
const val = data?.[rowIndex]?.[colIdx];


if (val === NOT_CELL_DATA) {
Expand All @@ -543,13 +557,14 @@ export const createInputCell = (tooltips, size = 10, validator, onChange, style)
key={rowIndex + '-' +colIdx}
validator={(v) => validator(v, data, rowIndex, colIdx)}
sx={{'.MuiInput-root':{'minHeight':'3px', 'borderRadius':4}}}
tooltip={tooltips}
tooltip={editing && tooltips}
size={size}
style={style}
value={val}
onChange={(v) => changeHandler(rowIndex, data, colIdx, v) }
actOn={['blur','enter']}
showWarning={false}
slotProps={{input: {autoComplete: 'off'}, control: {onFocus: () => setEditing(true), onBlur: () => setEditing(false)}, tooltip: syntaxTipProps}}
/>
</Box>
);
Expand All @@ -571,7 +586,13 @@ export const createInputCell = (tooltips, size = 10, validator, onChange, style)
*/
export const inputColumnRenderer = ({tbl_id, cname, tooltips, style={}, isReadOnly, validator, onChange, ...props}) => {

return ({rowIndex, data, colIdx}) => {
return function InputColumnCell({rowIndex, data, colIdx}) {
const [editing, setEditing] = useState(false); // the tooltip shows only while this box has focus
const [shownRow, setShownRow] = useState(rowIndex);
if (shownRow !== rowIndex) { // clear editing when a scroll moves the cell to another row
setShownRow(rowIndex);
setEditing(false);
}
const val = get(data, [rowIndex, colIdx]);
if (isReadOnly && isReadOnly(rowIndex, data, colIdx)) {
return <div style={{width: '100%', height: '100%', ...style}}>{val}</div>;
Expand All @@ -582,11 +603,12 @@ export const inputColumnRenderer = ({tbl_id, cname, tooltips, style={}, isReadOn
key={rowIndex + '-' +colIdx}
sx={{'.MuiInput-root':{'minHeight':'3px', 'borderRadius':4}}}
validator={ validator && ((v) => validator(v, data, rowIndex, colIdx))}
tooltip={tooltips}
tooltip={editing && tooltips}
style={{width: '100%', boxSizing: 'border-box', ...style}}
value={val}
onChange={makeChangeHandler(rowIndex, data, colIdx, tbl_id, cname, validator, onChange)}
actOn={['blur','enter']}
slotProps={{input: {autoComplete: 'off'}, control: {onFocus: () => setEditing(true), onBlur: () => setEditing(false)}, tooltip: syntaxTipProps}}
/>
</Box>
);
Expand Down
7 changes: 5 additions & 2 deletions src/firefly/js/ui/tap/TapViewType.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,11 @@ function OpRender({ops, value, label='', sx, lineClamp, rowDesc='rows'}) {
const op = ops.find((t) => t.value === value);
if (!op) return 'none';
const details= cleanUp(op.label,0);
const text = op.displayLabel ?? op.label;
const shown = cleanUp(text);
const isCut = shown !== cleanUp(text, 0); // the tooltip only adds something when the shown text is cut short
return (
<Tooltip title={
<Tooltip title={isCut &&
<Typography level='body-sm' component='div' width='30rem'>
<div dangerouslySetInnerHTML={{__html: `${details}`}}/>
</Typography>
Expand Down Expand Up @@ -580,7 +583,7 @@ function OpRender({ops, value, label='', sx, lineClamp, rowDesc='rows'}) {
WebkitBoxOrient: 'vertical',
} : {}}
sx={{whiteSpace:'normal', textAlign:'left'}}>
<div dangerouslySetInnerHTML={{__html: `${cleanUp(op.displayLabel??op.label)}`}}/>
<div dangerouslySetInnerHTML={{__html: `${shown}`}}/>
</Typography>
</Stack>
</Tooltip>
Expand Down
Loading