From df3a0ffd70bed49b5fec9d0d6ef9e8ab0ca7b170 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Fri, 25 Oct 2024 20:12:33 +0200 Subject: [PATCH 1/2] refactor: add auto highlights the entire content functionality to number input component --- src/component/elements/NumberInput2.tsx | 27 +++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/component/elements/NumberInput2.tsx b/src/component/elements/NumberInput2.tsx index a1a9a39ee..29d14cd64 100644 --- a/src/component/elements/NumberInput2.tsx +++ b/src/component/elements/NumberInput2.tsx @@ -8,20 +8,26 @@ import { useState, isValidElement, forwardRef, + useRef, } from 'react'; +import type { ForwardedRef } from 'react'; + +import useCombinedRefs from '../hooks/useCombinedRefs.js'; interface ValueProps extends Pick, 'name' | 'style'>, Pick { checkValue?: (element?: number) => boolean; debounceTime?: number; + autoSelect?: boolean; +} +interface UseInputProps extends Omit { + ref: ForwardedRef; } -type UseInputProps = Omit; export interface NumberInput2Props extends Omit, ValueProps { format?: () => (element: string) => number | string; - autoSelect?: boolean; } function useNumberInput(props: UseInputProps) { @@ -29,9 +35,13 @@ function useNumberInput(props: UseInputProps) { value: externalValue, debounceTime, onValueChange, + ref, + autoSelect, checkValue, } = props; const [internalValue, setValue] = useState(); + const localRef = useRef(); + const innerRef = useCombinedRefs([ref, localRef]); const value = debounceTime ? internalValue : externalValue; const [isDebounced, setDebouncedStatus] = useState(false); @@ -57,6 +67,12 @@ function useNumberInput(props: UseInputProps) { } }, [debounceTime, externalValue]); + useEffect(() => { + if (autoSelect) { + innerRef?.current?.select(); + } + }, [autoSelect, innerRef]); + function handleValueChange( valueAsNumber: number, valueAsString: string, @@ -79,6 +95,7 @@ function useNumberInput(props: UseInputProps) { debounceOnValueChange, isDebounced, value, + innerRef, }; } @@ -110,9 +127,11 @@ function InnerNumberInput(props: NumberInput2Props, ref) { ...otherInputProps } = props; - const { handleValueChange, isDebounced, value } = useNumberInput({ + const { handleValueChange, isDebounced, value, innerRef } = useNumberInput({ value: externalValue, debounceTime, + autoSelect, + ref, onValueChange, checkValue, }); @@ -124,7 +143,7 @@ function InnerNumberInput(props: NumberInput2Props, ref) { Date: Mon, 28 Oct 2024 11:23:37 +0100 Subject: [PATCH 2/2] refactor: improve editable input field component --- src/component/elements/EditableColumn.tsx | 247 ++++++++++++------ .../panels/IntegralsPanel/IntegralTable.tsx | 2 +- .../panels/MoleculesPanel/MoleculeHeader.tsx | 5 +- .../panels/PeaksPanel/PeaksTable.tsx | 2 +- .../TableColumns/RangeAssignmentColumn.tsx | 3 +- .../TableColumns/RelativeColumn.tsx | 2 +- .../TableColumns/SignalDeltaColumn.tsx | 2 +- .../CorrelationTable/CorrelationTableRow.tsx | 2 +- .../TableColumns/SignalDeltaColumn.tsx | 2 +- .../ZoneAssignmentLabelColumn.tsx | 3 +- test-e2e/panels/ranges.test.ts | 6 +- 11 files changed, 184 insertions(+), 92 deletions(-) diff --git a/src/component/elements/EditableColumn.tsx b/src/component/elements/EditableColumn.tsx index bf4e13eb2..f16ef1ab2 100644 --- a/src/component/elements/EditableColumn.tsx +++ b/src/component/elements/EditableColumn.tsx @@ -1,4 +1,6 @@ -import type { CSSProperties, ChangeEvent, KeyboardEvent } from 'react'; +import { Button } from '@blueprintjs/core'; +import styled from '@emotion/styled'; +import type { CSSProperties, KeyboardEvent } from 'react'; import { forwardRef, useCallback, @@ -7,8 +9,41 @@ import { useState, } from 'react'; -import type { InputProps } from './Input.js'; -import Input from './Input.js'; +import { Input2 } from './Input2.js'; +import { NumberInput2 } from './NumberInput2.js'; + +interface OverflowProps { + textOverflowEllipses: boolean; +} + +const Text = styled.span` + display: table-cell; + vertical-align: middle; + width: 100%; + height: 100%; + ${({ textOverflowEllipses }) => + textOverflowEllipses && + ` + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + `} +`; +const Container = styled.span` + display: table; + width: 100%; + min-height: 22px; + height: 100%; + ${({ textOverflowEllipses }) => + textOverflowEllipses && + ` + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + display: inline-flex; + align-items:end; + `} +`; function extractNumber(val: string | number, type: string) { if (type === 'number' && typeof val !== 'number') { @@ -18,37 +53,45 @@ function extractNumber(val: string | number, type: string) { return val; } -interface EditableColumnProps - extends Omit { +function handleMousedown(event) { + event.stopPropagation(); +} + +const style: CSSProperties = { minWidth: 60 }; +const className = 'editable-column'; + +interface BaseEditableColumnProps { + type: 'number' | 'text'; + value: number | string; + validate?: (value?: string | number) => boolean; +} + +interface EditableColumnProps extends BaseEditableColumnProps { onSave?: (element: KeyboardEvent) => void; onEditStart?: (element: boolean) => void; - type?: 'number' | 'text'; editStatus?: boolean; - value: string | number; style?: CSSProperties; - validate?: (value?: string | number) => boolean; - textOverFlowEllipses?: boolean; + textOverflowEllipses?: boolean; + clickType?: 'single' | 'double'; } -const EditableColumn = forwardRef(function EditableColumn( +export const EditableColumn = forwardRef(function EditableColumn( props: EditableColumnProps, ref: any, ) { const { - onSave = () => null, + onSave, value, - type = 'text', + type, style, - onEditStart = () => null, + onEditStart, editStatus = false, - validate = () => true, - textOverFlowEllipses = false, - ...InputProps + validate, + textOverflowEllipses = false, + clickType = 'single', } = props; const [enabled, enableEdit] = useState(); - const [isValid, setValid] = useState(true); - const [val, setVal] = useState(extractNumber(value, type)); useEffect(() => { enableEdit(editStatus); }, [editStatus]); @@ -71,85 +114,131 @@ const EditableColumn = forwardRef(function EditableColumn( function startEditHandler() { globalThis.addEventListener('mousedown', mouseClickCallback); - onEditStart(true); + onEditStart?.(true); enableEdit(true); } - function saveHandler(event: KeyboardEvent) { - const valid = validate(val); - setValid(valid); - // when press Enter or Tab - if (valid && ['Enter', 'Tab'].includes(event.key)) { - onSave(event); - enableEdit(false); - globalThis.removeEventListener('mousedown', mouseClickCallback); - } - // close edit mode if press Enter, Tab or Escape - if (['Escape'].includes(event.key)) { - enableEdit(false); - globalThis.removeEventListener('mousedown', mouseClickCallback); - } + function onConfirm(event: KeyboardEvent) { + onSave?.(event); + enableEdit(false); + globalThis.removeEventListener('mousedown', mouseClickCallback); + } + + function onCancel() { + enableEdit(false); + globalThis.removeEventListener('mousedown', mouseClickCallback); } - function handleChange(e: ChangeEvent) { - setVal(e.target.value); + let clickHandler = {}; + + if (clickType === 'single' && !enabled) { + clickHandler = { onClick: startEditHandler }; + } + + if (clickType === 'double' && !enabled) { + clickHandler = { onDoubleClick: startEditHandler }; } return ( -
{!enabled && ( - - {value}   - + + {value ?? ' '} + )} {enabled && (
- e.stopPropagation()} - {...InputProps} + onConfirm={onConfirm} + onCancel={onCancel} + validate={validate} />
)} -
+ ); }); -export default EditableColumn; +interface EditFiledProps extends BaseEditableColumnProps { + onConfirm: (event: KeyboardEvent) => void; + onCancel: (event?: KeyboardEvent) => void; +} + +function EditFiled(props: EditFiledProps) { + const { value: externalValue, type, onConfirm, onCancel, validate } = props; + + const [isValid, setValid] = useState(true); + const [value, setVal] = useState(extractNumber(externalValue, type)); + + function handleKeydown(event: KeyboardEvent) { + const valid = typeof validate === 'function' ? validate(value) : true; + setValid(valid); + // when press Enter or Tab + if (valid && ['Enter', 'Tab'].includes(event.key)) { + onConfirm(event); + } + // close edit mode if press Enter, Tab or Escape + if (['Escape'].includes(event.key)) { + onCancel(event); + } + } + + function handleChange(value: string | number) { + setVal(value); + } + + const intent = !isValid ? 'danger' : 'none'; + + const rightElement = ( +