[fix] geo shape text label placement (#1927)
This PR fixes the text label placement for geo shapes. (It also fixes the way an ellipse renders when set to dash or dotted). There's still the slightest offset of the text label's outline when you begin editing. Maybe we should keep the indicator instead? ### Change Type - [x] `patch` — Bug fix ### Test Plan Create a hexagon shape hit enter to type indicator is offset, text label is no longer offset --------- Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This commit is contained in:
parent
398bd352ae
commit
9e4dbd1901
11 changed files with 141 additions and 68 deletions
|
@ -328,6 +328,9 @@ export function clamp(n: number, min: number, max: number): number;
|
||||||
// @public
|
// @public
|
||||||
export function clampRadians(r: number): number;
|
export function clampRadians(r: number): number;
|
||||||
|
|
||||||
|
// @public
|
||||||
|
export function clockwiseAngleDist(a0: number, a1: number): number;
|
||||||
|
|
||||||
export { computed }
|
export { computed }
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
|
|
@ -1029,22 +1029,17 @@ input,
|
||||||
/* ---------------- Geo shape ---------------- */
|
/* ---------------- Geo shape ---------------- */
|
||||||
|
|
||||||
.tl-text-label {
|
.tl-text-label {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-shadow: var(--tl-text-outline);
|
text-shadow: var(--tl-text-outline);
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
inset: 0px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-label[data-isediting='true'] {
|
|
||||||
outline: calc(var(--tl-scale) * 1.5px) solid var(--color-selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-label[data-isediting='true'] .tl-text-content {
|
.tl-text-label[data-isediting='true'] .tl-text-content {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -1168,7 +1163,6 @@ input,
|
||||||
|
|
||||||
.tl-arrow-label[data-isediting='true'] > .tl-arrow-label__inner {
|
.tl-arrow-label[data-isediting='true'] > .tl-arrow-label__inner {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
border: calc(var(--tl-scale) * 1.5px) solid var(--color-selected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-arrow-label__inner {
|
.tl-arrow-label__inner {
|
||||||
|
@ -1355,7 +1349,6 @@ input,
|
||||||
|
|
||||||
.tl-frame-label__editing {
|
.tl-frame-label__editing {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
outline: 1.5px solid var(--color-selection-stroke);
|
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
width: auto;
|
width: auto;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
|
@ -297,6 +297,7 @@ export {
|
||||||
canonicalizeRotation,
|
canonicalizeRotation,
|
||||||
clamp,
|
clamp,
|
||||||
clampRadians,
|
clampRadians,
|
||||||
|
clockwiseAngleDist,
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
getArcLength,
|
getArcLength,
|
||||||
getPointOnCircle,
|
getPointOnCircle,
|
||||||
|
|
|
@ -322,6 +322,7 @@ function SelectedIdIndicators() {
|
||||||
'select.idle',
|
'select.idle',
|
||||||
'select.brushing',
|
'select.brushing',
|
||||||
'select.scribble_brushing',
|
'select.scribble_brushing',
|
||||||
|
'select.editing_shape',
|
||||||
'select.pointing_shape',
|
'select.pointing_shape',
|
||||||
'select.pointing_selection',
|
'select.pointing_selection',
|
||||||
'select.pointing_handle'
|
'select.pointing_handle'
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { nearestMultiple } from '../hooks/useDPRMultiple'
|
||||||
import { useEditor } from '../hooks/useEditor'
|
import { useEditor } from '../hooks/useEditor'
|
||||||
import { useEditorComponents } from '../hooks/useEditorComponents'
|
import { useEditorComponents } from '../hooks/useEditorComponents'
|
||||||
import { Matrix2d } from '../primitives/Matrix2d'
|
import { Matrix2d } from '../primitives/Matrix2d'
|
||||||
|
import { toDomPrecision } from '../primitives/utils'
|
||||||
import { OptionalErrorBoundary } from './ErrorBoundary'
|
import { OptionalErrorBoundary } from './ErrorBoundary'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -170,9 +171,11 @@ const CulledShape = React.memo(
|
||||||
<div
|
<div
|
||||||
className="tl-shape__culled"
|
className="tl-shape__culled"
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${bounds.minX}px, ${bounds.minY}px)`,
|
transform: `translate(${toDomPrecision(bounds.minX)}px, ${toDomPrecision(
|
||||||
width: Math.max(1, bounds.width),
|
bounds.minY
|
||||||
height: Math.max(1, bounds.height),
|
)}px)`,
|
||||||
|
width: Math.max(1, toDomPrecision(bounds.width)),
|
||||||
|
height: Math.max(1, toDomPrecision(bounds.height)),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -109,6 +109,22 @@ export function canonicalizeRotation(a: number) {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the clockwise angle distance between two angles.
|
||||||
|
*
|
||||||
|
* @param a0 - The first angle.
|
||||||
|
* @param a1 - The second angle.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function clockwiseAngleDist(a0: number, a1: number): number {
|
||||||
|
a0 = canonicalizeRotation(a0)
|
||||||
|
a1 = canonicalizeRotation(a1)
|
||||||
|
if (a0 > a1) {
|
||||||
|
a1 += PI2
|
||||||
|
}
|
||||||
|
return a1 - a0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the short angle distance between two angles.
|
* Get the short angle distance between two angles.
|
||||||
*
|
*
|
||||||
|
@ -270,12 +286,27 @@ export function getPointOnCircle(cx: number, cy: number, r: number, a: number) {
|
||||||
export function getPolygonVertices(width: number, height: number, sides: number) {
|
export function getPolygonVertices(width: number, height: number, sides: number) {
|
||||||
const cx = width / 2
|
const cx = width / 2
|
||||||
const cy = height / 2
|
const cy = height / 2
|
||||||
const pointsOnPerimeter = []
|
const pointsOnPerimeter: Vec2d[] = []
|
||||||
|
let minX = Infinity
|
||||||
|
let minY = Infinity
|
||||||
for (let i = 0; i < sides; i++) {
|
for (let i = 0; i < sides; i++) {
|
||||||
const step = PI2 / sides
|
const step = PI2 / sides
|
||||||
const t = -TAU + i * step
|
const t = -TAU + i * step
|
||||||
pointsOnPerimeter.push(new Vec2d(cx + cx * Math.cos(t), cy + cy * Math.sin(t)))
|
const x = cx + cx * Math.cos(t)
|
||||||
|
const y = cy + cy * Math.sin(t)
|
||||||
|
if (x < minX) minX = x
|
||||||
|
if (y < minY) minY = y
|
||||||
|
pointsOnPerimeter.push(new Vec2d(x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minX !== 0 || minY !== 0) {
|
||||||
|
for (let i = 0; i < pointsOnPerimeter.length; i++) {
|
||||||
|
const pt = pointsOnPerimeter[i]
|
||||||
|
pt.x -= minX
|
||||||
|
pt.y -= minY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return pointsOnPerimeter
|
return pointsOnPerimeter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
getSolidStraightArrowPath,
|
getSolidStraightArrowPath,
|
||||||
getStraightArrowHandlePath,
|
getStraightArrowHandlePath,
|
||||||
toDomPrecision,
|
toDomPrecision,
|
||||||
|
useIsEditing,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ShapeFill, getShapeFillSvg, useDefaultColorTheme } from '../shared/ShapeFill'
|
import { ShapeFill, getShapeFillSvg, useDefaultColorTheme } from '../shared/ShapeFill'
|
||||||
|
@ -692,6 +693,22 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
|
|
||||||
const maskId = (shape.id + '_clip').replace(':', '_')
|
const maskId = (shape.id + '_clip').replace(':', '_')
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const isEditing = useIsEditing(shape.id)
|
||||||
|
|
||||||
|
if (isEditing && labelGeometry) {
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
x={toDomPrecision(labelGeometry.x)}
|
||||||
|
y={toDomPrecision(labelGeometry.y)}
|
||||||
|
width={labelGeometry.w}
|
||||||
|
height={labelGeometry.h}
|
||||||
|
rx={3.5}
|
||||||
|
ry={3.5}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
{includeMask && (
|
{includeMask && (
|
||||||
|
|
|
@ -532,6 +532,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
||||||
text={text}
|
text={text}
|
||||||
labelColor={labelColor}
|
labelColor={labelColor}
|
||||||
wrap
|
wrap
|
||||||
|
bounds={props.geo === 'cloud' ? this.getGeometry(shape).bounds : undefined}
|
||||||
/>
|
/>
|
||||||
{shape.props.url && (
|
{shape.props.url && (
|
||||||
<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.zoomLevel} />
|
<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.zoomLevel} />
|
||||||
|
@ -813,10 +814,12 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
||||||
|
|
||||||
override onResize: TLOnResizeHandler<TLGeoShape> = (
|
override onResize: TLOnResizeHandler<TLGeoShape> = (
|
||||||
shape,
|
shape,
|
||||||
{ initialBounds, handle, newPoint, scaleX, scaleY }
|
{ handle, newPoint, scaleX, scaleY, initialShape }
|
||||||
) => {
|
) => {
|
||||||
let w = initialBounds.width * scaleX
|
// use the w/h from props here instead of the initialBounds here,
|
||||||
let h = initialBounds.height * scaleY
|
// since cloud shapes calculated bounds can differ from the props w/h.
|
||||||
|
let w = initialShape.props.w * scaleX
|
||||||
|
let h = (initialShape.props.h + initialShape.props.growY) * scaleY
|
||||||
let overShrinkX = 0
|
let overShrinkX = 0
|
||||||
let overShrinkY = 0
|
let overShrinkY = 0
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@ import {
|
||||||
TLDefaultSizeStyle,
|
TLDefaultSizeStyle,
|
||||||
Vec2d,
|
Vec2d,
|
||||||
Vec2dModel,
|
Vec2dModel,
|
||||||
|
clockwiseAngleDist,
|
||||||
getPointOnCircle,
|
getPointOnCircle,
|
||||||
rng,
|
rng,
|
||||||
shortAngleDist,
|
toDomPrecision,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
|
|
||||||
function getPillCircumference(width: number, height: number) {
|
function getPillCircumference(width: number, height: number) {
|
||||||
|
@ -200,8 +201,8 @@ export function getCloudArcs(
|
||||||
const radius = Vec2d.Dist(center, leftWigglePoint)
|
const radius = Vec2d.Dist(center, leftWigglePoint)
|
||||||
|
|
||||||
arcs.push({
|
arcs.push({
|
||||||
leftPoint: leftWigglePoint,
|
leftPoint: leftWigglePoint.clone(),
|
||||||
rightPoint: rightWigglePoint,
|
rightPoint: rightWigglePoint.clone(),
|
||||||
arcPoint,
|
arcPoint,
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
|
@ -265,11 +266,13 @@ export function cloudSvgPath(
|
||||||
size: TLDefaultSizeStyle
|
size: TLDefaultSizeStyle
|
||||||
) {
|
) {
|
||||||
const arcs = getCloudArcs(width, height, seed, size)
|
const arcs = getCloudArcs(width, height, seed, size)
|
||||||
let path = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}`
|
let path = `M${toDomPrecision(arcs[0].leftPoint.x)},${toDomPrecision(arcs[0].leftPoint.y)}`
|
||||||
|
|
||||||
// now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle
|
// now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle
|
||||||
for (const { rightPoint, radius } of arcs) {
|
for (const { rightPoint, radius } of arcs) {
|
||||||
path += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}`
|
path += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 0,1 ${toDomPrecision(
|
||||||
|
rightPoint.x
|
||||||
|
)},${toDomPrecision(rightPoint.y)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
path += ' Z'
|
path += ' Z'
|
||||||
|
@ -289,18 +292,22 @@ export function inkyCloudSvgPath(
|
||||||
}
|
}
|
||||||
const mutPoint = (p: Vec2d) => new Vec2d(mut(p.x), mut(p.y))
|
const mutPoint = (p: Vec2d) => new Vec2d(mut(p.x), mut(p.y))
|
||||||
const arcs = getCloudArcs(width, height, seed, size)
|
const arcs = getCloudArcs(width, height, seed, size)
|
||||||
let pathA = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}`
|
let pathA = `M${toDomPrecision(arcs[0].leftPoint.x)},${toDomPrecision(arcs[0].leftPoint.y)}`
|
||||||
let leftMutPoint = mutPoint(arcs[0].leftPoint)
|
let leftMutPoint = mutPoint(arcs[0].leftPoint)
|
||||||
let pathB = `M${leftMutPoint.x},${leftMutPoint.y}`
|
let pathB = `M${toDomPrecision(leftMutPoint.x)},${toDomPrecision(leftMutPoint.y)}`
|
||||||
|
|
||||||
for (const { rightPoint, radius, arcPoint } of arcs) {
|
for (const { rightPoint, radius, arcPoint } of arcs) {
|
||||||
pathA += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}`
|
pathA += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 0,1 ${toDomPrecision(
|
||||||
|
rightPoint.x
|
||||||
|
)},${toDomPrecision(rightPoint.y)}`
|
||||||
const rightMutPoint = mutPoint(rightPoint)
|
const rightMutPoint = mutPoint(rightPoint)
|
||||||
const mutArcPoint = mutPoint(arcPoint)
|
const mutArcPoint = mutPoint(arcPoint)
|
||||||
const mutCenter = getCenterOfCircleGivenThreePoints(leftMutPoint, rightMutPoint, mutArcPoint)
|
const mutCenter = getCenterOfCircleGivenThreePoints(leftMutPoint, rightMutPoint, mutArcPoint)
|
||||||
const mutRadius = Math.abs(Vec2d.Dist(mutCenter, leftMutPoint))
|
const mutRadius = Math.abs(Vec2d.Dist(mutCenter, leftMutPoint))
|
||||||
|
|
||||||
pathB += ` A${mutRadius},${mutRadius} 0 0,1 ${rightMutPoint.x},${rightMutPoint.y}`
|
pathB += ` A${toDomPrecision(mutRadius)},${toDomPrecision(mutRadius)} 0 0,1 ${toDomPrecision(
|
||||||
|
rightMutPoint.x
|
||||||
|
)},${toDomPrecision(rightMutPoint.y)}`
|
||||||
leftMutPoint = rightMutPoint
|
leftMutPoint = rightMutPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +326,7 @@ export function pointsOnArc(
|
||||||
const startAngle = Vec2d.Angle(center, startPoint)
|
const startAngle = Vec2d.Angle(center, startPoint)
|
||||||
const endAngle = Vec2d.Angle(center, endPoint)
|
const endAngle = Vec2d.Angle(center, endPoint)
|
||||||
|
|
||||||
const l = shortAngleDist(startAngle, endAngle)
|
const l = clockwiseAngleDist(startAngle, endAngle)
|
||||||
|
|
||||||
for (let i = 0; i < numPoints; i++) {
|
for (let i = 0; i < numPoints; i++) {
|
||||||
const t = i / (numPoints - 1)
|
const t = i / (numPoints - 1)
|
||||||
|
|
|
@ -28,8 +28,8 @@ export const DashStyleEllipse = React.memo(function DashStyleEllipse({
|
||||||
const theme = useDefaultColorTheme()
|
const theme = useDefaultColorTheme()
|
||||||
const cx = w / 2
|
const cx = w / 2
|
||||||
const cy = h / 2
|
const cy = h / 2
|
||||||
const rx = Math.max(0, cx - sw / 2)
|
const rx = Math.max(0, cx)
|
||||||
const ry = Math.max(0, cy - sw / 2)
|
const ry = Math.max(0, cy)
|
||||||
|
|
||||||
const perimeter = perimeterOfEllipse(rx, ry)
|
const perimeter = perimeterOfEllipse(rx, ry)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Box2d,
|
||||||
TLDefaultColorStyle,
|
TLDefaultColorStyle,
|
||||||
TLDefaultFillStyle,
|
TLDefaultFillStyle,
|
||||||
TLDefaultFontStyle,
|
TLDefaultFontStyle,
|
||||||
|
@ -27,6 +28,7 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
align,
|
align,
|
||||||
verticalAlign,
|
verticalAlign,
|
||||||
wrap,
|
wrap,
|
||||||
|
bounds,
|
||||||
}: {
|
}: {
|
||||||
id: T['id']
|
id: T['id']
|
||||||
type: T['type']
|
type: T['type']
|
||||||
|
@ -38,6 +40,7 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
wrap?: boolean
|
wrap?: boolean
|
||||||
text: string
|
text: string
|
||||||
labelColor: TLDefaultColorStyle
|
labelColor: TLDefaultColorStyle
|
||||||
|
bounds?: Box2d
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
rInput,
|
rInput,
|
||||||
|
@ -71,48 +74,59 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
style={{
|
style={{
|
||||||
justifyContent: align === 'middle' || legacyAlign ? 'center' : align,
|
justifyContent: align === 'middle' || legacyAlign ? 'center' : align,
|
||||||
alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign,
|
alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign,
|
||||||
|
...(bounds
|
||||||
|
? {
|
||||||
|
top: bounds.minY,
|
||||||
|
left: bounds.minX,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
position: 'absolute',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{isEmpty && !isInteractive ? null : (
|
||||||
className="tl-text-label__inner"
|
<div
|
||||||
style={{
|
className="tl-text-label__inner"
|
||||||
fontSize: LABEL_FONT_SIZES[size],
|
style={{
|
||||||
lineHeight: LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 'px',
|
fontSize: LABEL_FONT_SIZES[size],
|
||||||
minHeight: isEmpty ? LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 32 : 0,
|
lineHeight: LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 'px',
|
||||||
minWidth: isEmpty ? 33 : 0,
|
minHeight: TEXT_PROPS.lineHeight + 32,
|
||||||
color: theme[labelColor].solid,
|
minWidth: 0,
|
||||||
}}
|
color: theme[labelColor].solid,
|
||||||
>
|
}}
|
||||||
<div className="tl-text tl-text-content" dir="ltr">
|
>
|
||||||
{finalText}
|
<div className="tl-text tl-text-content" dir="ltr">
|
||||||
|
{finalText}
|
||||||
|
</div>
|
||||||
|
{isInteractive && (
|
||||||
|
<textarea
|
||||||
|
ref={rInput}
|
||||||
|
className="tl-text tl-text-input"
|
||||||
|
name="text"
|
||||||
|
tabIndex={-1}
|
||||||
|
autoComplete="false"
|
||||||
|
autoCapitalize="false"
|
||||||
|
autoCorrect="false"
|
||||||
|
autoSave="false"
|
||||||
|
autoFocus={isEditing}
|
||||||
|
placeholder=""
|
||||||
|
spellCheck="true"
|
||||||
|
wrap="off"
|
||||||
|
dir="auto"
|
||||||
|
datatype="wysiwyg"
|
||||||
|
defaultValue={text}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onContextMenu={stopEventPropagation}
|
||||||
|
onPointerDown={handleInputPointerDown}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isInteractive && (
|
)}
|
||||||
<textarea
|
|
||||||
ref={rInput}
|
|
||||||
className="tl-text tl-text-input"
|
|
||||||
name="text"
|
|
||||||
tabIndex={-1}
|
|
||||||
autoComplete="false"
|
|
||||||
autoCapitalize="false"
|
|
||||||
autoCorrect="false"
|
|
||||||
autoSave="false"
|
|
||||||
autoFocus={isEditing}
|
|
||||||
placeholder=""
|
|
||||||
spellCheck="true"
|
|
||||||
wrap="off"
|
|
||||||
dir="auto"
|
|
||||||
datatype="wysiwyg"
|
|
||||||
defaultValue={text}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onContextMenu={stopEventPropagation}
|
|
||||||
onPointerDown={handleInputPointerDown}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue