What do I want to do:
I want to make React Konva Text editable. That is if I double-click on the Text... (I can move it to anywhere in the Konva Stage) ...I can show a text area to get the edits from the user, any changes he/she would like to make to the default text.
Conditions:
I tried to implement this HTML Text demo with React Konva, but in vain, because of limitations
The html Konva demo that I want to reproduce in React Konva
Things I did:
Things that I observed:
The HTML can take padding and margin (we can use normal html within tag.), but not top, left.
I did try to put X property of Text in the margin top of the root div and value of Text's Y property in the margin left attribute of the same div, but I had to revert as it was not close to the demo above.
Code:
import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";
/*
Konva warning: tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.
*/
const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
const shapeRef = useRef();
const trRef = useRef();
const [toggleEdit, setToggleEdit] = useState(false)
useEffect(() => {
if (isSelected) {
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
// console.log("KText", `properties: ${JSON.stringify(properties)}`)
// console.log("KText", ` properties.text: ${properties.text}`)
const EditTextField = () => {
var textProps
const updateText = (data) => {
textProps = data
// console.log("EditTextField", `textProps: ${JSON.stringify(textProps)}`)
}
// var mAreaPos = areaPosition()
const areaPosition = () => {
let stage1 = stage.current.getStage()
return ({
x: stage1.container().offsetLeft + properties.x,
y: stage1.container().offsetTop + properties.y,
})
};
return (
<Html >
<div style={{
margin: "200px", padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
top: areaPosition().x, left: areaPosition().y
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
<label htmlFor="inputText">Please enter some text below:</label><p>
<textarea onChange={(evt) => (updateText({ text: evt.target.value, id: id }))}
id="inputText" name="inputText" rows="4" cols="50" placeholder="Please enter here" />
<br />
<button type="text" onClick={() => {
setToggleEdit(!toggleEdit)
setActiveText(textProps)
}}>Close</button>
</p>
</div>{/* */}
</Html >
)
}
const MainText = () => {
return (
<>
<Fragment>
<Text
stroke={"black"}
strokeWidth={1}
onTap={onSelect}
onClick={onSelect}
onDblClick={() => setToggleEdit(!toggleEdit)}
ref={shapeRef}
// {...shapeProps}
name="text"
x={properties.x}
y={properties.y}
text={properties.text}
fontFamily={properties.fontFamily}//"Serif"
fontSize={properties.fontSize}//50
fontWeight={properties.fontWeight} //"bold"
fillLinearGradientStartPoint={{ x: 0, y: 0 }}
fillLinearGradientEndPoint={{ x: 100, y: 100 }}
fillLinearGradientColorStops={[
0,
"rgba(0,0,0,0.7)",
1,
"rgba(255,155,255,0.5)"
]}
fillPriority={"linear-gradient"}
draggable
onDragEnd={e => {
/* onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});*/
}}
onTransformEnd={e => {
// transformer is changing scale
/* const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: node.width() * scaleX,
height: node.height() * scaleY,
}); */
}}
/>
{isSelected && <Transformer ref={trRef} />}
</Fragment>
</>
)
}
const RenderThis = () => {
let inText = "" + properties.text
if (inText.trim().length === 0 || toggleEdit) {
return (
<EditTextField />
)
} else return (
<MainText />
)
}
// rendering function
return (
<RenderThis />
);
};
export default KText;
Since top and left properties were not working, I simply replaced the following code:
<div style={{
margin: "200px", padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
top: areaPosition().x, left: areaPosition().y
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
...
</div
with the following (it improved a bit):
I used marginTop
and marginLeft
and removed margin
as well.
Secondly, x
should go with marginLeft
and y
with marginTop
, so I corrected that as well.
<div style={{
padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
marginTop: areaPosition().y, marginLeft: areaPosition().x
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
...
</div