diff --git a/client/packages/lowcoder/src/components/table/EditableCell.tsx b/client/packages/lowcoder/src/components/table/EditableCell.tsx index b88616744..4bc15fedc 100644 --- a/client/packages/lowcoder/src/components/table/EditableCell.tsx +++ b/client/packages/lowcoder/src/components/table/EditableCell.tsx @@ -54,6 +54,8 @@ export type EditViewFn = (props: { value: T; onChange: (value: T) => void; onChangeEnd: () => void; + onCommit?: (value: T) => void; + onCancel?: () => void; onImmediateSave?: (value: T) => void; otherProps?: Record; }) => ReactNode; @@ -152,11 +154,11 @@ function EditableCellComp(props: EditableCellProps) { [] ); - const onChangeEnd = useCallback(() => { + const commitValue = useCallback((finalValue: T | null) => { if (!mountedRef.current) return; - + setIsEditing(false); - const newValue = _.isNil(tmpValue) || _.isEqual(tmpValue, baseValue) ? null : tmpValue; + const newValue = _.isNil(finalValue) || _.isEqual(finalValue, baseValue) ? null : finalValue; dispatch( changeChildAction( "changeValue", @@ -164,10 +166,26 @@ function EditableCellComp(props: EditableCellProps) { false ) ); - if(!_.isEqual(tmpValue, value)) { + if(!_.isEqual(finalValue, value)) { onTableEvent?.('columnEdited'); } - }, [dispatch, tmpValue, baseValue, value, onTableEvent, setIsEditing]); + }, [dispatch, baseValue, value, onTableEvent, setIsEditing]); + + const onChangeEnd = useCallback(() => { + commitValue(tmpValue); + }, [commitValue, tmpValue]); + + const onCommit = useCallback((nextValue: T) => { + if (!mountedRef.current) return; + setTmpValue(nextValue); + commitValue(nextValue); + }, [commitValue]); + + const onCancel = useCallback(() => { + if (!mountedRef.current) return; + setIsEditing(false); + setTmpValue(value); + }, [setIsEditing, value]); const onImmediateSave = useCallback((newValue: T) => { if (!mountedRef.current) return; @@ -187,8 +205,8 @@ function EditableCellComp(props: EditableCellProps) { }, [dispatch, baseValue, value, onTableEvent]); const editView = useMemo( - () => editViewFn?.({ value, onChange, onChangeEnd, onImmediateSave, otherProps }) ?? <>, - [editViewFn, value, onChange, onChangeEnd, onImmediateSave, otherProps] + () => editViewFn?.({ value, onChange, onChangeEnd, onCommit, onCancel, onImmediateSave, otherProps }) ?? <>, + [editViewFn, value, onChange, onChangeEnd, onCommit, onCancel, onImmediateSave, otherProps] ); const enterEditFn = useCallback(() => { @@ -243,4 +261,4 @@ function EditableCellComp(props: EditableCellProps) { ); } -export const EditableCell = React.memo(EditableCellComp) as typeof EditableCellComp; \ No newline at end of file +export const EditableCell = React.memo(EditableCellComp) as typeof EditableCellComp; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComp.tsx index e2e82f58d..07c31de47 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComp.tsx @@ -23,6 +23,7 @@ import { ColumnNumberComp } from "./columnTypeComps/ColumnNumberComp"; import { ColumnAvatarsComp } from "./columnTypeComps/columnAvatarsComp"; import { ColumnDropdownComp } from "./columnTypeComps/columnDropdownComp"; import { ColumnPasswordComp } from "./columnTypeComps/columnPasswordComp"; +import { ColumnMultilineTextComp } from "./columnTypeComps/columnMultilineTextComp"; const actionOptions = [ { @@ -106,6 +107,10 @@ const actionOptions = [ label: "Password", value: "password", }, + { + label: trans("table.multilineText"), + value: "multilineText", + }, ] as const; export const ColumnTypeCompMap = { @@ -129,6 +134,7 @@ export const ColumnTypeCompMap = { date: DateComp, time: TimeComp, password: ColumnPasswordComp, + multilineText: ColumnMultilineTextComp, }; type ColumnTypeMapType = typeof ColumnTypeCompMap; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMultilineTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMultilineTextComp.tsx new file mode 100644 index 000000000..5006e86f3 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMultilineTextComp.tsx @@ -0,0 +1,105 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { default as AntdModal } from "antd/es/modal"; +import { default as Input } from "antd/es/input"; +import { StringOrNumberControl } from "comps/controls/codeControl"; +import { trans } from "i18n"; +import { ColumnTypeCompBuilder, ColumnTypeViewFn } from "../columnTypeCompBuilder"; +import { ColumnValueTooltip } from "../simpleColumnTypeComps"; +import { RecordConstructorToComp } from "lowcoder-core"; +import styled from "styled-components"; + +const { TextArea } = Input; + +const TextView = styled.div` + white-space: pre-wrap; + word-break: break-word; + cursor: pointer; +`; + +const childrenMap = { + text: StringOrNumberControl, +}; + +const getBaseValue: ColumnTypeViewFn = (props) => + typeof props.text === "string" ? props.text : String(props.text); + +const MultilineContent = React.memo(({ value }: { value: string }) => {value}); + +const MultilineEditModal = React.memo((props: { + value: string; + onCommit: (value: string) => void; + onCancel: () => void; +}) => { + const { value, onCommit, onCancel } = props; + const [localValue, setLocalValue] = useState(value); + const textAreaRef = useRef(null); + + useEffect(() => { + setLocalValue(value); + }, [value]); + + useEffect(() => { + const timeout = setTimeout(() => textAreaRef.current?.focus({ cursor: "end" }), 0); + return () => clearTimeout(timeout); + }, []); + + const handleSave = useCallback(() => { + onCommit(localValue); + }, [localValue, onCommit]); + + const handleCancel = useCallback(() => { + onCancel(); + }, [onCancel]); + + return ( + +