textfields [1 of 3]: add text into speech bubble; also add rich text example (#3050)
This is the first of three textfield changes. This starts with making the speech bubble actually have text. Also, it creates a TipTap example and how that would be wired up. 🎵 this is dangerous, I walk through textfields so watch your head rock 🎵 ### Change Type - [x] `minor` — New feature ### Release Notes - Refactor textfields be composable/swappable.
This commit is contained in:
parent
3593799d9e
commit
d76d53db95
17 changed files with 616 additions and 346 deletions
|
@ -2,7 +2,7 @@
|
||||||
title: Speech bubble
|
title: Speech bubble
|
||||||
component: ./CustomShapeWithHandles.tsx
|
component: ./CustomShapeWithHandles.tsx
|
||||||
category: shapes/tools
|
category: shapes/tools
|
||||||
priority: 2
|
priority: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
A custom shape with handles
|
A custom shape with handles
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
|
import { ShapePropsType } from '@tldraw/tlschema/src/shapes/TLBaseShape'
|
||||||
import {
|
import {
|
||||||
DefaultColorStyle,
|
DefaultColorStyle,
|
||||||
|
DefaultFontStyle,
|
||||||
|
DefaultHorizontalAlignStyle,
|
||||||
DefaultSizeStyle,
|
DefaultSizeStyle,
|
||||||
|
DefaultVerticalAlignStyle,
|
||||||
|
FONT_FAMILIES,
|
||||||
Geometry2d,
|
Geometry2d,
|
||||||
|
LABEL_FONT_SIZES,
|
||||||
Polygon2d,
|
Polygon2d,
|
||||||
ShapeUtil,
|
ShapeUtil,
|
||||||
T,
|
T,
|
||||||
|
TEXT_PROPS,
|
||||||
TLBaseShape,
|
TLBaseShape,
|
||||||
TLDefaultColorStyle,
|
|
||||||
TLDefaultSizeStyle,
|
|
||||||
TLHandle,
|
TLHandle,
|
||||||
TLOnBeforeUpdateHandler,
|
TLOnBeforeUpdateHandler,
|
||||||
TLOnHandleDragHandler,
|
TLOnHandleDragHandler,
|
||||||
TLOnResizeHandler,
|
TLOnResizeHandler,
|
||||||
|
TextLabel,
|
||||||
Vec,
|
Vec,
|
||||||
VecModel,
|
|
||||||
ZERO_INDEX_KEY,
|
ZERO_INDEX_KEY,
|
||||||
getDefaultColorTheme,
|
getDefaultColorTheme,
|
||||||
resizeBox,
|
resizeBox,
|
||||||
|
@ -33,28 +38,28 @@ export const STROKE_SIZES = {
|
||||||
// There's a guide at the bottom of this file!
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
// [1]
|
// [1]
|
||||||
export type SpeechBubbleShape = TLBaseShape<
|
|
||||||
'speech-bubble',
|
export const speechBubbleShapeProps = {
|
||||||
{
|
w: T.number,
|
||||||
w: number
|
h: T.number,
|
||||||
h: number
|
size: DefaultSizeStyle,
|
||||||
size: TLDefaultSizeStyle
|
color: DefaultColorStyle,
|
||||||
color: TLDefaultColorStyle
|
font: DefaultFontStyle,
|
||||||
tail: VecModel
|
align: DefaultHorizontalAlignStyle,
|
||||||
|
verticalAlign: DefaultVerticalAlignStyle,
|
||||||
|
growY: T.positiveNumber,
|
||||||
|
text: T.string,
|
||||||
|
tail: vecModelValidator,
|
||||||
}
|
}
|
||||||
>
|
|
||||||
|
export type SpeechBubbleShapeProps = ShapePropsType<typeof speechBubbleShapeProps>
|
||||||
|
export type SpeechBubbleShape = TLBaseShape<'speech-bubble', SpeechBubbleShapeProps>
|
||||||
|
|
||||||
export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
static override type = 'speech-bubble' as const
|
static override type = 'speech-bubble' as const
|
||||||
|
|
||||||
// [2]
|
// [2]
|
||||||
static override props = {
|
static override props = speechBubbleShapeProps
|
||||||
w: T.number,
|
|
||||||
h: T.number,
|
|
||||||
size: DefaultSizeStyle,
|
|
||||||
color: DefaultColorStyle,
|
|
||||||
tail: vecModelValidator,
|
|
||||||
}
|
|
||||||
|
|
||||||
override isAspectRatioLocked = (_shape: SpeechBubbleShape) => false
|
override isAspectRatioLocked = (_shape: SpeechBubbleShape) => false
|
||||||
|
|
||||||
|
@ -62,17 +67,28 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
|
|
||||||
override canBind = (_shape: SpeechBubbleShape) => true
|
override canBind = (_shape: SpeechBubbleShape) => true
|
||||||
|
|
||||||
|
override canEdit = () => true
|
||||||
|
|
||||||
// [3]
|
// [3]
|
||||||
getDefaultProps(): SpeechBubbleShape['props'] {
|
getDefaultProps(): SpeechBubbleShapeProps {
|
||||||
return {
|
return {
|
||||||
w: 200,
|
w: 200,
|
||||||
h: 130,
|
h: 130,
|
||||||
color: 'black',
|
color: 'black',
|
||||||
size: 'm',
|
size: 'm',
|
||||||
|
font: 'draw',
|
||||||
|
align: 'middle',
|
||||||
|
verticalAlign: 'start',
|
||||||
|
growY: 0,
|
||||||
|
text: '',
|
||||||
tail: { x: 0.5, y: 1.5 },
|
tail: { x: 0.5, y: 1.5 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHeight(shape: SpeechBubbleShape) {
|
||||||
|
return shape.props.h + shape.props.growY
|
||||||
|
}
|
||||||
|
|
||||||
getGeometry(shape: SpeechBubbleShape): Geometry2d {
|
getGeometry(shape: SpeechBubbleShape): Geometry2d {
|
||||||
const speechBubbleGeometry = getSpeechBubbleVertices(shape)
|
const speechBubbleGeometry = getSpeechBubbleVertices(shape)
|
||||||
const body = new Polygon2d({
|
const body = new Polygon2d({
|
||||||
|
@ -84,7 +100,7 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
|
|
||||||
// [4]
|
// [4]
|
||||||
override getHandles(shape: SpeechBubbleShape): TLHandle[] {
|
override getHandles(shape: SpeechBubbleShape): TLHandle[] {
|
||||||
const { tail, w, h } = shape.props
|
const { tail, w } = shape.props
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -94,7 +110,7 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
// props.tail coordinates are normalized
|
// props.tail coordinates are normalized
|
||||||
// but here we need them in shape space
|
// but here we need them in shape space
|
||||||
x: tail.x * w,
|
x: tail.x * w,
|
||||||
y: tail.y * h,
|
y: tail.y * this.getHeight(shape),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -105,29 +121,34 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
props: {
|
props: {
|
||||||
tail: {
|
tail: {
|
||||||
x: handle.x / shape.props.w,
|
x: handle.x / shape.props.w,
|
||||||
y: handle.y / shape.props.h,
|
y: handle.y / this.getHeight(shape),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onBeforeCreate = (next: SpeechBubbleShape) => {
|
||||||
|
return this.getGrowY(next, next.props.growY)
|
||||||
|
}
|
||||||
|
|
||||||
// [5]
|
// [5]
|
||||||
override onBeforeUpdate: TLOnBeforeUpdateHandler<SpeechBubbleShape> | undefined = (
|
override onBeforeUpdate: TLOnBeforeUpdateHandler<SpeechBubbleShape> | undefined = (
|
||||||
_: SpeechBubbleShape,
|
prev: SpeechBubbleShape,
|
||||||
shape: SpeechBubbleShape
|
shape: SpeechBubbleShape
|
||||||
) => {
|
) => {
|
||||||
const { w, h, tail } = shape.props
|
const { w, tail } = shape.props
|
||||||
|
const fullHeight = this.getHeight(shape)
|
||||||
|
|
||||||
const { segmentsIntersection, insideShape } = getTailIntersectionPoint(shape)
|
const { segmentsIntersection, insideShape } = getTailIntersectionPoint(shape)
|
||||||
|
|
||||||
const slantedLength = Math.hypot(w, h)
|
const slantedLength = Math.hypot(w, fullHeight)
|
||||||
const MIN_DISTANCE = slantedLength / 5
|
const MIN_DISTANCE = slantedLength / 5
|
||||||
const MAX_DISTANCE = slantedLength / 1.5
|
const MAX_DISTANCE = slantedLength / 1.5
|
||||||
|
|
||||||
const tailInShapeSpace = new Vec(tail.x * w, tail.y * h)
|
const tailInShapeSpace = new Vec(tail.x * w, tail.y * fullHeight)
|
||||||
|
|
||||||
const distanceToIntersection = tailInShapeSpace.dist(segmentsIntersection)
|
const distanceToIntersection = tailInShapeSpace.dist(segmentsIntersection)
|
||||||
const center = new Vec(w / 2, h / 2)
|
const center = new Vec(w / 2, fullHeight / 2)
|
||||||
const tailDirection = Vec.Sub(tailInShapeSpace, center).uni()
|
const tailDirection = Vec.Sub(tailInShapeSpace, center).uni()
|
||||||
|
|
||||||
let newPoint = tailInShapeSpace
|
let newPoint = tailInShapeSpace
|
||||||
|
@ -144,12 +165,17 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
|
|
||||||
const next = structuredClone(shape)
|
const next = structuredClone(shape)
|
||||||
next.props.tail.x = newPoint.x / w
|
next.props.tail.x = newPoint.x / w
|
||||||
next.props.tail.y = newPoint.y / h
|
next.props.tail.y = newPoint.y / fullHeight
|
||||||
|
|
||||||
return next
|
return this.getGrowY(next, prev.props.growY)
|
||||||
}
|
}
|
||||||
|
|
||||||
component(shape: SpeechBubbleShape) {
|
component(shape: SpeechBubbleShape) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
props: { color, font, size, align, text },
|
||||||
|
} = shape
|
||||||
const theme = getDefaultColorTheme({
|
const theme = getDefaultColorTheme({
|
||||||
isDarkMode: this.editor.user.getIsDarkMode(),
|
isDarkMode: this.editor.user.getIsDarkMode(),
|
||||||
})
|
})
|
||||||
|
@ -161,11 +187,24 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
<svg className="tl-svg-container">
|
<svg className="tl-svg-container">
|
||||||
<path
|
<path
|
||||||
d={pathData}
|
d={pathData}
|
||||||
strokeWidth={STROKE_SIZES[shape.props.size]}
|
strokeWidth={STROKE_SIZES[size]}
|
||||||
stroke={theme[shape.props.color].solid}
|
stroke={theme[color].solid}
|
||||||
fill={'none'}
|
fill={'none'}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
<TextLabel
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
font={font}
|
||||||
|
fontSize={LABEL_FONT_SIZES[size]}
|
||||||
|
lineHeight={TEXT_PROPS.lineHeight}
|
||||||
|
align={align}
|
||||||
|
verticalAlign="start"
|
||||||
|
text={text}
|
||||||
|
labelColor={color}
|
||||||
|
wrap
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -185,6 +224,37 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
||||||
next.props.h = resized.props.h
|
next.props.h = resized.props.h
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGrowY(shape: SpeechBubbleShape, prevGrowY = 0) {
|
||||||
|
const PADDING = 17
|
||||||
|
|
||||||
|
const nextTextSize = this.editor.textMeasure.measureText(shape.props.text, {
|
||||||
|
...TEXT_PROPS,
|
||||||
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
|
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
||||||
|
maxWidth: shape.props.w - PADDING * 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
const nextHeight = nextTextSize.h + PADDING * 2
|
||||||
|
|
||||||
|
let growY = 0
|
||||||
|
|
||||||
|
if (nextHeight > shape.props.h) {
|
||||||
|
growY = nextHeight - shape.props.h
|
||||||
|
} else {
|
||||||
|
if (prevGrowY) {
|
||||||
|
growY = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...shape,
|
||||||
|
props: {
|
||||||
|
...shape.props,
|
||||||
|
growY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -2,14 +2,20 @@ import { Vec, VecLike, lerp, pointInPolygon } from 'tldraw'
|
||||||
import { SpeechBubbleShape } from './SpeechBubbleUtil'
|
import { SpeechBubbleShape } from './SpeechBubbleUtil'
|
||||||
|
|
||||||
export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec[] => {
|
export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec[] => {
|
||||||
const { w, h, tail } = shape.props
|
const { w, tail } = shape.props
|
||||||
|
|
||||||
const tailInShapeSpace = new Vec(tail.x * w, tail.y * h)
|
const fullHeight = shape.props.h + shape.props.growY
|
||||||
|
const tailInShapeSpace = new Vec(tail.x * w, tail.y * fullHeight)
|
||||||
|
|
||||||
const [tl, tr, br, bl] = [new Vec(0, 0), new Vec(w, 0), new Vec(w, h), new Vec(0, h)]
|
const [tl, tr, br, bl] = [
|
||||||
|
new Vec(0, 0),
|
||||||
|
new Vec(w, 0),
|
||||||
|
new Vec(w, fullHeight),
|
||||||
|
new Vec(0, fullHeight),
|
||||||
|
]
|
||||||
|
|
||||||
const offsetH = w / 10
|
const offsetH = w / 10
|
||||||
const offsetV = h / 10
|
const offsetV = fullHeight / 10
|
||||||
|
|
||||||
const { adjustedIntersection, intersectionSegmentIndex } = getTailIntersectionPoint(shape)
|
const { adjustedIntersection, intersectionSegmentIndex } = getTailIntersectionPoint(shape)
|
||||||
|
|
||||||
|
@ -73,11 +79,12 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec[] => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTailIntersectionPoint(shape: SpeechBubbleShape) {
|
export function getTailIntersectionPoint(shape: SpeechBubbleShape) {
|
||||||
const { w, h, tail } = shape.props
|
const { w, tail } = shape.props
|
||||||
const tailInShapeSpace = new Vec(tail.x * w, tail.y * h)
|
const fullHeight = shape.props.h + shape.props.growY
|
||||||
|
const tailInShapeSpace = new Vec(tail.x * w, tail.y * fullHeight)
|
||||||
|
|
||||||
const center = new Vec(w / 2, h / 2)
|
const center = new Vec(w / 2, fullHeight / 2)
|
||||||
const corners = [new Vec(0, 0), new Vec(w, 0), new Vec(w, h), new Vec(0, h)]
|
const corners = [new Vec(0, 0), new Vec(w, 0), new Vec(w, fullHeight), new Vec(0, fullHeight)]
|
||||||
const segments = [
|
const segments = [
|
||||||
[corners[0], corners[1]],
|
[corners[0], corners[1]],
|
||||||
[corners[1], corners[2]],
|
[corners[1], corners[2]],
|
||||||
|
@ -132,7 +139,7 @@ export function getTailIntersectionPoint(shape: SpeechBubbleShape) {
|
||||||
const squared = mapRange(-1, 1, 0, totalDistance, squaredRelative) // -1 to 1 -> absolute
|
const squared = mapRange(-1, 1, 0, totalDistance, squaredRelative) // -1 to 1 -> absolute
|
||||||
|
|
||||||
//keep it away from the edges
|
//keep it away from the edges
|
||||||
const offset = (segments.indexOf(intersectionSegment) % 2 === 0 ? w / 10 : h / 10) * 3
|
const offset = (segments.indexOf(intersectionSegment) % 2 === 0 ? w / 10 : fullHeight / 10) * 3
|
||||||
const constrained = mapRange(0, totalDistance, offset, totalDistance - offset, distance)
|
const constrained = mapRange(0, totalDistance, offset, totalDistance - offset, distance)
|
||||||
|
|
||||||
// combine the two
|
// combine the two
|
||||||
|
|
|
@ -2,3 +2,9 @@
|
||||||
.tl-user-handles {
|
.tl-user-handles {
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The text label doesn't normally deal with text that goes sideways,
|
||||||
|
* so this accounts for that */
|
||||||
|
.tl-shape[data-shape-type='speech-bubble'] .tl-text-label {
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
}
|
||||||
|
|
|
@ -682,7 +682,7 @@ input,
|
||||||
|
|
||||||
/* ------------------- Text Shape ------------------- */
|
/* ------------------- Text Shape ------------------- */
|
||||||
|
|
||||||
.tl-text-shape__wrapper {
|
.tl-text-shape-label {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
|
@ -698,35 +698,38 @@ input,
|
||||||
text-shadow: var(--tl-text-outline);
|
text-shadow: var(--tl-text-outline);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-align='start'] {
|
.tl-text-wrapper[data-font='draw'] {
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-align='middle'] {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-align='end'] {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-font='draw'] {
|
|
||||||
font-family: var(--tl-font-draw);
|
font-family: var(--tl-font-draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-font='sans'] {
|
.tl-text-wrapper[data-font='sans'] {
|
||||||
font-family: var(--tl-font-sans);
|
font-family: var(--tl-font-sans);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-font='serif'] {
|
.tl-text-wrapper[data-font='serif'] {
|
||||||
font-family: var(--tl-font-serif);
|
font-family: var(--tl-font-serif);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-font='mono'] {
|
.tl-text-wrapper[data-font='mono'] {
|
||||||
font-family: var(--tl-font-mono);
|
font-family: var(--tl-font-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-shape__wrapper[data-isediting='true'] .tl-text-content {
|
.tl-text-wrapper[data-align='start'],
|
||||||
|
.tl-text-wrapper[data-align='start-legacy'] {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tl-text-wrapper[data-align='middle'],
|
||||||
|
.tl-text-wrapper[data-align='middle-legacy'] {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tl-text-wrapper[data-align='end'],
|
||||||
|
.tl-text-wrapper[data-align='end-legacy'] {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tl-text-wrapper[data-isediting='true'] .tl-text-content {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,10 +998,6 @@ input,
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-label[data-isediting='true'] .tl-text-content {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-label[data-hastext='false'][data-isediting='false'] > .tl-text-label__inner {
|
.tl-text-label[data-hastext='false'][data-isediting='false'] > .tl-text-label__inner {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -1053,21 +1052,6 @@ input,
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-text-label[data-align='start'],
|
|
||||||
.tl-text-label[data-align='start-legacy'] {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-label[data-align='middle'],
|
|
||||||
.tl-text-label[data-align='middle-legacy'] {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-text-label[data-align='end'],
|
|
||||||
.tl-text-label[data-align='end-legacy'] {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-hint {
|
.tl-arrow-hint {
|
||||||
stroke: var(--color-text-1);
|
stroke: var(--color-text-1);
|
||||||
fill: none;
|
fill: none;
|
||||||
|
@ -1075,26 +1059,6 @@ input,
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-arrow-label[data-font='draw'],
|
|
||||||
.tl-text-label[data-font='draw'] {
|
|
||||||
font-family: var(--tl-font-draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-label[data-font='sans'],
|
|
||||||
.tl-text-label[data-font='sans'] {
|
|
||||||
font-family: var(--tl-font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-label[data-font='serif'],
|
|
||||||
.tl-text-label[data-font='serif'] {
|
|
||||||
font-family: var(--tl-font-serif);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-label[data-font='mono'],
|
|
||||||
.tl-text-label[data-font='mono'] {
|
|
||||||
font-family: var(--tl-font-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------- Arrow Shape ------------------ */
|
/* ------------------- Arrow Shape ------------------ */
|
||||||
|
|
||||||
.tl-arrow-label {
|
.tl-arrow-label {
|
||||||
|
@ -1107,6 +1071,7 @@ input,
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-shadow: var(--tl-text-outline);
|
text-shadow: var(--tl-text-outline);
|
||||||
}
|
}
|
||||||
|
@ -1131,40 +1096,7 @@ input,
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-arrow-label p,
|
.tl-arrow-label .tl-arrow {
|
||||||
.tl-arrow-label textarea {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
border: 0px;
|
|
||||||
color: inherit;
|
|
||||||
caret-color: var(--color-text);
|
|
||||||
background: none;
|
|
||||||
border-image: none;
|
|
||||||
font-size: inherit;
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
font-variant: inherit;
|
|
||||||
font-style: inherit;
|
|
||||||
text-align: inherit;
|
|
||||||
letter-spacing: inherit;
|
|
||||||
text-shadow: inherit;
|
|
||||||
outline: none;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
pointer-events: all;
|
|
||||||
text-rendering: auto;
|
|
||||||
text-transform: none;
|
|
||||||
text-indent: 0px;
|
|
||||||
display: inline-block;
|
|
||||||
appearance: auto;
|
|
||||||
column-count: initial !important;
|
|
||||||
writing-mode: horizontal-tb !important;
|
|
||||||
word-spacing: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-label p {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
|
@ -62,8 +62,13 @@ import { TLBookmarkShape } from '@tldraw/editor';
|
||||||
import { TLCancelEvent } from '@tldraw/editor';
|
import { TLCancelEvent } from '@tldraw/editor';
|
||||||
import { TLClickEvent } from '@tldraw/editor';
|
import { TLClickEvent } from '@tldraw/editor';
|
||||||
import { TLClickEventInfo } from '@tldraw/editor';
|
import { TLClickEventInfo } from '@tldraw/editor';
|
||||||
|
import { TLDefaultColorStyle } from '@tldraw/editor';
|
||||||
import { TLDefaultColorTheme } from '@tldraw/editor';
|
import { TLDefaultColorTheme } from '@tldraw/editor';
|
||||||
|
import { TLDefaultFillStyle } from '@tldraw/editor';
|
||||||
|
import { TLDefaultFontStyle } from '@tldraw/editor';
|
||||||
|
import { TLDefaultHorizontalAlignStyle } from '@tldraw/editor';
|
||||||
import { TLDefaultSizeStyle } from '@tldraw/editor';
|
import { TLDefaultSizeStyle } from '@tldraw/editor';
|
||||||
|
import { TLDefaultVerticalAlignStyle } from '@tldraw/editor';
|
||||||
import { TldrawEditorBaseProps } from '@tldraw/editor';
|
import { TldrawEditorBaseProps } from '@tldraw/editor';
|
||||||
import { TLDrawShape } from '@tldraw/editor';
|
import { TLDrawShape } from '@tldraw/editor';
|
||||||
import { TLDrawShapeSegment } from '@tldraw/editor';
|
import { TLDrawShapeSegment } from '@tldraw/editor';
|
||||||
|
@ -613,6 +618,9 @@ export function fitFrameToContent(editor: Editor, id: TLShapeId, opts?: {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function FitFrameToContentMenuItem(): JSX_2.Element | null;
|
export function FitFrameToContentMenuItem(): JSX_2.Element | null;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const FONT_FAMILIES: Record<TLDefaultFontStyle, string>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export class FrameShapeTool extends BaseBoxShapeTool {
|
export class FrameShapeTool extends BaseBoxShapeTool {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -966,6 +974,9 @@ export function isGifAnimated(file: Blob): Promise<boolean>;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function KeyboardShortcutsMenuItem(): JSX_2.Element | null;
|
export function KeyboardShortcutsMenuItem(): JSX_2.Element | null;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const LABEL_FONT_SIZES: Record<TLDefaultSizeStyle, number>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function LanguageMenu(): JSX_2.Element;
|
export function LanguageMenu(): JSX_2.Element;
|
||||||
|
|
||||||
|
@ -1279,6 +1290,18 @@ export function StackMenuItems(): JSX_2.Element;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function StarToolbarItem(): JSX_2.Element;
|
export function StarToolbarItem(): JSX_2.Element;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const TEXT_PROPS: {
|
||||||
|
lineHeight: number;
|
||||||
|
fontWeight: string;
|
||||||
|
fontVariant: string;
|
||||||
|
fontStyle: string;
|
||||||
|
padding: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const TextLabel: React_2.NamedExoticComponent<TextLabelProps>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export class TextShapeTool extends StateNode {
|
export class TextShapeTool extends StateNode {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -2475,6 +2498,19 @@ export function useDefaultHelpers(): {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useDialogs(): TLUiDialogsContextType;
|
export function useDialogs(): TLUiDialogsContextType;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export function useEditableText(id: TLShapeId, type: string, text: string): {
|
||||||
|
rInput: React_2.RefObject<HTMLTextAreaElement>;
|
||||||
|
isEditing: boolean;
|
||||||
|
handleFocus: () => void;
|
||||||
|
handleBlur: () => void;
|
||||||
|
handleKeyDown: (e: React_2.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
|
handleChange: (e: React_2.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
handleInputPointerDown: (e: React_2.PointerEvent) => void;
|
||||||
|
handleDoubleClick: (e: any) => any;
|
||||||
|
isEmpty: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useExportAs(): (ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined) => void;
|
export function useExportAs(): (ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined) => void;
|
||||||
|
|
||||||
|
|
|
@ -6887,6 +6887,43 @@
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"name": "FitFrameToContentMenuItem"
|
"name": "FitFrameToContentMenuItem"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"canonicalReference": "tldraw!FONT_FAMILIES:var",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "FONT_FAMILIES: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Record",
|
||||||
|
"canonicalReference": "!Record:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "TLDefaultFontStyle",
|
||||||
|
"canonicalReference": "@tldraw/tlschema!TLDefaultFontStyle:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ", string>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts",
|
||||||
|
"isReadonly": true,
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "FONT_FAMILIES",
|
||||||
|
"variableTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Class",
|
"kind": "Class",
|
||||||
"canonicalReference": "tldraw!FrameShapeTool:class",
|
"canonicalReference": "tldraw!FrameShapeTool:class",
|
||||||
|
@ -11244,6 +11281,43 @@
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"name": "KeyboardShortcutsMenuItem"
|
"name": "KeyboardShortcutsMenuItem"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"canonicalReference": "tldraw!LABEL_FONT_SIZES:var",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "LABEL_FONT_SIZES: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Record",
|
||||||
|
"canonicalReference": "!Record:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "TLDefaultSizeStyle",
|
||||||
|
"canonicalReference": "@tldraw/tlschema!TLDefaultSizeStyle:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ", number>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts",
|
||||||
|
"isReadonly": true,
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "LABEL_FONT_SIZES",
|
||||||
|
"variableTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Function",
|
"kind": "Function",
|
||||||
"canonicalReference": "tldraw!LanguageMenu:function(1)",
|
"canonicalReference": "tldraw!LanguageMenu:function(1)",
|
||||||
|
@ -14997,6 +15071,66 @@
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"name": "StarToolbarItem"
|
"name": "StarToolbarItem"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"canonicalReference": "tldraw!TEXT_PROPS:var",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "TEXT_PROPS: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "{\n lineHeight: number;\n fontWeight: string;\n fontVariant: string;\n fontStyle: string;\n padding: string;\n}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts",
|
||||||
|
"isReadonly": true,
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "TEXT_PROPS",
|
||||||
|
"variableTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"canonicalReference": "tldraw!TextLabel:var",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "TextLabel: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.NamedExoticComponent",
|
||||||
|
"canonicalReference": "@types/react!React.NamedExoticComponent:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "TextLabelProps",
|
||||||
|
"canonicalReference": "tldraw!~TextLabelProps:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/tldraw/src/lib/shapes/shared/TextLabel.tsx",
|
||||||
|
"isReadonly": true,
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "TextLabel",
|
||||||
|
"variableTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Class",
|
"kind": "Class",
|
||||||
"canonicalReference": "tldraw!TextShapeTool:class",
|
"canonicalReference": "tldraw!TextShapeTool:class",
|
||||||
|
@ -27289,6 +27423,147 @@
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"name": "useDialogs"
|
"name": "useDialogs"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "Function",
|
||||||
|
"canonicalReference": "tldraw!useEditableText:function(1)",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "export declare function useEditableText(id: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "TLShapeId",
|
||||||
|
"canonicalReference": "@tldraw/tlschema!TLShapeId:type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ", type: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ", text: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "): "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "{\n rInput: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.RefObject",
|
||||||
|
"canonicalReference": "@types/react!React.RefObject:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "HTMLTextAreaElement",
|
||||||
|
"canonicalReference": "!HTMLTextAreaElement:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">;\n isEditing: boolean;\n handleFocus: () => void;\n handleBlur: () => void;\n handleKeyDown: (e: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.KeyboardEvent",
|
||||||
|
"canonicalReference": "@types/react!React.KeyboardEvent:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "HTMLTextAreaElement",
|
||||||
|
"canonicalReference": "!HTMLTextAreaElement:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">) => void;\n handleChange: (e: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.ChangeEvent",
|
||||||
|
"canonicalReference": "@types/react!React.ChangeEvent:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "HTMLTextAreaElement",
|
||||||
|
"canonicalReference": "!HTMLTextAreaElement:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">) => void;\n handleInputPointerDown: (e: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.PointerEvent",
|
||||||
|
"canonicalReference": "@types/react!React.PointerEvent:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ") => void;\n handleDoubleClick: (e: any) => any;\n isEmpty: boolean;\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/tldraw/src/lib/shapes/shared/useEditableText.ts",
|
||||||
|
"returnTypeTokenRange": {
|
||||||
|
"startIndex": 7,
|
||||||
|
"endIndex": 22
|
||||||
|
},
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"overloadIndex": 1,
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"parameterName": "id",
|
||||||
|
"parameterTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
},
|
||||||
|
"isOptional": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameterName": "type",
|
||||||
|
"parameterTypeTokenRange": {
|
||||||
|
"startIndex": 3,
|
||||||
|
"endIndex": 4
|
||||||
|
},
|
||||||
|
"isOptional": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameterName": "text",
|
||||||
|
"parameterTypeTokenRange": {
|
||||||
|
"startIndex": 5,
|
||||||
|
"endIndex": 6
|
||||||
|
},
|
||||||
|
"isOptional": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "useEditableText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Function",
|
"kind": "Function",
|
||||||
"canonicalReference": "tldraw!useExportAs:function(1)",
|
"canonicalReference": "tldraw!useExportAs:function(1)",
|
||||||
|
|
|
@ -33,6 +33,7 @@ export { LineShapeTool } from './lib/shapes/line/LineShapeTool'
|
||||||
export { LineShapeUtil } from './lib/shapes/line/LineShapeUtil'
|
export { LineShapeUtil } from './lib/shapes/line/LineShapeUtil'
|
||||||
export { NoteShapeTool } from './lib/shapes/note/NoteShapeTool'
|
export { NoteShapeTool } from './lib/shapes/note/NoteShapeTool'
|
||||||
export { NoteShapeUtil } from './lib/shapes/note/NoteShapeUtil'
|
export { NoteShapeUtil } from './lib/shapes/note/NoteShapeUtil'
|
||||||
|
export { TextLabel } from './lib/shapes/shared/TextLabel'
|
||||||
export { TextShapeTool } from './lib/shapes/text/TextShapeTool'
|
export { TextShapeTool } from './lib/shapes/text/TextShapeTool'
|
||||||
export { TextShapeUtil } from './lib/shapes/text/TextShapeUtil'
|
export { TextShapeUtil } from './lib/shapes/text/TextShapeUtil'
|
||||||
export { VideoShapeUtil } from './lib/shapes/video/VideoShapeUtil'
|
export { VideoShapeUtil } from './lib/shapes/video/VideoShapeUtil'
|
||||||
|
@ -42,6 +43,7 @@ export { LaserTool } from './lib/tools/LaserTool/LaserTool'
|
||||||
export { SelectTool } from './lib/tools/SelectTool/SelectTool'
|
export { SelectTool } from './lib/tools/SelectTool/SelectTool'
|
||||||
export { ZoomTool } from './lib/tools/ZoomTool/ZoomTool'
|
export { ZoomTool } from './lib/tools/ZoomTool/ZoomTool'
|
||||||
// UI
|
// UI
|
||||||
|
export { useEditableText } from './lib/shapes/shared/useEditableText'
|
||||||
export { TldrawUi, type TldrawUiBaseProps, type TldrawUiProps } from './lib/ui/TldrawUi'
|
export { TldrawUi, type TldrawUiBaseProps, type TldrawUiProps } from './lib/ui/TldrawUi'
|
||||||
export { setDefaultUiAssetUrls, type TLUiAssetUrlOverrides } from './lib/ui/assetUrls'
|
export { setDefaultUiAssetUrls, type TLUiAssetUrlOverrides } from './lib/ui/assetUrls'
|
||||||
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
|
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
|
||||||
|
@ -422,3 +424,11 @@ export {
|
||||||
TldrawUiMenuSubmenu,
|
TldrawUiMenuSubmenu,
|
||||||
type TLUiMenuSubmenuProps,
|
type TLUiMenuSubmenuProps,
|
||||||
} from './lib/ui/components/primitives/menus/TldrawUiMenuSubmenu'
|
} from './lib/ui/components/primitives/menus/TldrawUiMenuSubmenu'
|
||||||
|
|
||||||
|
/* ----------------- Constants ---------------- */
|
||||||
|
|
||||||
|
export {
|
||||||
|
FONT_FAMILIES,
|
||||||
|
LABEL_FONT_SIZES,
|
||||||
|
TEXT_PROPS,
|
||||||
|
} from './lib/shapes/shared/default-shape-constants'
|
||||||
|
|
|
@ -516,9 +516,6 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
}
|
}
|
||||||
|
|
||||||
component(shape: TLArrowShape) {
|
component(shape: TLArrowShape) {
|
||||||
// Not a class component, but eslint can't tell that :(
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const theme = useDefaultColorTheme()
|
|
||||||
const onlySelectedShape = this.editor.getOnlySelectedShape()
|
const onlySelectedShape = this.editor.getOnlySelectedShape()
|
||||||
const shouldDisplayHandles =
|
const shouldDisplayHandles =
|
||||||
this.editor.isInAny(
|
this.editor.isInAny(
|
||||||
|
@ -549,7 +546,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
size={shape.props.size}
|
size={shape.props.size}
|
||||||
position={labelPosition.box.center}
|
position={labelPosition.box.center}
|
||||||
width={labelPosition.box.w}
|
width={labelPosition.box.w}
|
||||||
labelColor={theme[shape.props.labelColor].solid}
|
labelColor={shape.props.labelColor}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { TLArrowShape, TLShapeId, VecLike, stopEventPropagation } from '@tldraw/editor'
|
import { TLArrowShape, TLDefaultColorStyle, TLShapeId, VecLike } from '@tldraw/editor'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { TextHelpers } from '../../shared/TextHelpers'
|
import { TextLabel } from '../../shared/TextLabel'
|
||||||
import { ARROW_LABEL_FONT_SIZES, TEXT_PROPS } from '../../shared/default-shape-constants'
|
import { ARROW_LABEL_FONT_SIZES, TEXT_PROPS } from '../../shared/default-shape-constants'
|
||||||
import { useEditableText } from '../../shared/useEditableText'
|
|
||||||
|
|
||||||
export const ArrowTextLabel = React.memo(function ArrowTextLabel({
|
export const ArrowTextLabel = React.memo(function ArrowTextLabel({
|
||||||
id,
|
id,
|
||||||
|
@ -12,77 +11,26 @@ export const ArrowTextLabel = React.memo(function ArrowTextLabel({
|
||||||
position,
|
position,
|
||||||
width,
|
width,
|
||||||
labelColor,
|
labelColor,
|
||||||
}: { id: TLShapeId; position: VecLike; width?: number; labelColor: string } & Pick<
|
}: { id: TLShapeId; position: VecLike; width?: number; labelColor: TLDefaultColorStyle } & Pick<
|
||||||
TLArrowShape['props'],
|
TLArrowShape['props'],
|
||||||
'text' | 'size' | 'font'
|
'text' | 'size' | 'font'
|
||||||
>) {
|
>) {
|
||||||
const {
|
|
||||||
rInput,
|
|
||||||
isEditing,
|
|
||||||
handleFocus,
|
|
||||||
handleBlur,
|
|
||||||
handleKeyDown,
|
|
||||||
handleChange,
|
|
||||||
isEmpty,
|
|
||||||
handleInputPointerDown,
|
|
||||||
handleDoubleClick,
|
|
||||||
} = useEditableText(id, 'arrow', text)
|
|
||||||
|
|
||||||
const finalText = TextHelpers.normalizeTextForDom(text)
|
|
||||||
const hasText = finalText.trim().length > 0
|
|
||||||
|
|
||||||
if (!isEditing && !hasText) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TextLabel
|
||||||
className="tl-arrow-label"
|
id={id}
|
||||||
data-font={font}
|
classNamePrefix="tl-arrow"
|
||||||
data-align={'center'}
|
type="arrow"
|
||||||
data-hastext={!isEmpty}
|
font={font}
|
||||||
data-isediting={isEditing}
|
fontSize={ARROW_LABEL_FONT_SIZES[size]}
|
||||||
|
lineHeight={TEXT_PROPS.lineHeight}
|
||||||
|
align="middle"
|
||||||
|
verticalAlign="middle"
|
||||||
|
text={text}
|
||||||
|
labelColor={labelColor}
|
||||||
|
textWidth={width}
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: ARROW_LABEL_FONT_SIZES[size],
|
|
||||||
lineHeight: ARROW_LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 'px',
|
|
||||||
transform: `translate(${position.x}px, ${position.y}px)`,
|
transform: `translate(${position.x}px, ${position.y}px)`,
|
||||||
color: labelColor,
|
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<div className="tl-arrow-label__inner">
|
|
||||||
<p style={{ width: width ? width : '9px' }}>
|
|
||||||
{text ? TextHelpers.normalizeTextForDom(text) : ' '}
|
|
||||||
</p>
|
|
||||||
{isEditing && (
|
|
||||||
// Consider replacing with content-editable
|
|
||||||
<textarea
|
|
||||||
ref={rInput}
|
|
||||||
className="tl-text tl-text-input"
|
|
||||||
name="text"
|
|
||||||
tabIndex={-1}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoSave="off"
|
|
||||||
autoFocus
|
|
||||||
placeholder=""
|
|
||||||
spellCheck="true"
|
|
||||||
wrap="off"
|
|
||||||
dir="auto"
|
|
||||||
datatype="wysiwyg"
|
|
||||||
defaultValue={text}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onTouchEnd={stopEventPropagation}
|
|
||||||
onContextMenu={stopEventPropagation}
|
|
||||||
onPointerDown={handleInputPointerDown}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { TextLabel } from '../shared/TextLabel'
|
||||||
import {
|
import {
|
||||||
FONT_FAMILIES,
|
FONT_FAMILIES,
|
||||||
LABEL_FONT_SIZES,
|
LABEL_FONT_SIZES,
|
||||||
|
LABEL_PADDING,
|
||||||
STROKE_SIZES,
|
STROKE_SIZES,
|
||||||
TEXT_PROPS,
|
TEXT_PROPS,
|
||||||
} from '../shared/default-shape-constants'
|
} from '../shared/default-shape-constants'
|
||||||
|
@ -46,7 +47,6 @@ import { GeoShapeBody } from './components/GeoShapeBody'
|
||||||
import { getOvalIndicatorPath } from './components/SolidStyleOval'
|
import { getOvalIndicatorPath } from './components/SolidStyleOval'
|
||||||
import { getLines } from './getLines'
|
import { getLines } from './getLines'
|
||||||
|
|
||||||
const LABEL_PADDING = 16
|
|
||||||
const MIN_SIZE_WITH_LABEL = 17 * 3
|
const MIN_SIZE_WITH_LABEL = 17 * 3
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -396,8 +396,9 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
||||||
id={id}
|
id={id}
|
||||||
type={type}
|
type={type}
|
||||||
font={font}
|
font={font}
|
||||||
|
fontSize={LABEL_FONT_SIZES[size]}
|
||||||
|
lineHeight={TEXT_PROPS.lineHeight}
|
||||||
fill={fill}
|
fill={fill}
|
||||||
size={size}
|
|
||||||
align={align}
|
align={align}
|
||||||
verticalAlign={verticalAlign}
|
verticalAlign={verticalAlign}
|
||||||
text={text}
|
text={text}
|
||||||
|
|
|
@ -83,7 +83,8 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
||||||
id={id}
|
id={id}
|
||||||
type={type}
|
type={type}
|
||||||
font={font}
|
font={font}
|
||||||
size={size}
|
fontSize={LABEL_FONT_SIZES[size]}
|
||||||
|
lineHeight={TEXT_PROPS.lineHeight}
|
||||||
align={align}
|
align={align}
|
||||||
verticalAlign={verticalAlign}
|
verticalAlign={verticalAlign}
|
||||||
text={text}
|
text={text}
|
||||||
|
|
|
@ -4,36 +4,23 @@ import {
|
||||||
TLDefaultFillStyle,
|
TLDefaultFillStyle,
|
||||||
TLDefaultFontStyle,
|
TLDefaultFontStyle,
|
||||||
TLDefaultHorizontalAlignStyle,
|
TLDefaultHorizontalAlignStyle,
|
||||||
TLDefaultSizeStyle,
|
|
||||||
TLDefaultVerticalAlignStyle,
|
TLDefaultVerticalAlignStyle,
|
||||||
TLShape,
|
TLShapeId,
|
||||||
stopEventPropagation,
|
getDefaultColorTheme,
|
||||||
|
useIsDarkMode,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useDefaultColorTheme } from './ShapeFill'
|
import { TextArea } from '../text/TextArea'
|
||||||
import { TextHelpers } from './TextHelpers'
|
import { TextHelpers } from './TextHelpers'
|
||||||
import { LABEL_FONT_SIZES, TEXT_PROPS } from './default-shape-constants'
|
|
||||||
import { isLegacyAlign } from './legacyProps'
|
import { isLegacyAlign } from './legacyProps'
|
||||||
import { useEditableText } from './useEditableText'
|
import { useEditableText } from './useEditableText'
|
||||||
|
|
||||||
export const TextLabel = React.memo(function TextLabel<
|
type TextLabelProps = {
|
||||||
T extends Extract<TLShape, { props: { text: string } }>,
|
id: TLShapeId
|
||||||
>({
|
type: string
|
||||||
id,
|
|
||||||
type,
|
|
||||||
text,
|
|
||||||
size,
|
|
||||||
labelColor,
|
|
||||||
font,
|
|
||||||
align,
|
|
||||||
verticalAlign,
|
|
||||||
wrap,
|
|
||||||
bounds,
|
|
||||||
}: {
|
|
||||||
id: T['id']
|
|
||||||
type: T['type']
|
|
||||||
size: TLDefaultSizeStyle
|
|
||||||
font: TLDefaultFontStyle
|
font: TLDefaultFontStyle
|
||||||
|
fontSize: number
|
||||||
|
lineHeight: number
|
||||||
fill?: TLDefaultFillStyle
|
fill?: TLDefaultFillStyle
|
||||||
align: TLDefaultHorizontalAlignStyle
|
align: TLDefaultHorizontalAlignStyle
|
||||||
verticalAlign: TLDefaultVerticalAlignStyle
|
verticalAlign: TLDefaultVerticalAlignStyle
|
||||||
|
@ -41,32 +28,47 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
text: string
|
text: string
|
||||||
labelColor: TLDefaultColorStyle
|
labelColor: TLDefaultColorStyle
|
||||||
bounds?: Box
|
bounds?: Box
|
||||||
}) {
|
classNamePrefix?: string
|
||||||
const {
|
style?: React.CSSProperties
|
||||||
rInput,
|
textWidth?: number
|
||||||
isEmpty,
|
textHeight?: number
|
||||||
isEditing,
|
}
|
||||||
handleFocus,
|
|
||||||
handleChange,
|
/** @public */
|
||||||
handleKeyDown,
|
export const TextLabel = React.memo(function TextLabel({
|
||||||
handleBlur,
|
id,
|
||||||
handleInputPointerDown,
|
type,
|
||||||
handleDoubleClick,
|
text,
|
||||||
} = useEditableText(id, type, text)
|
labelColor,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
lineHeight,
|
||||||
|
align,
|
||||||
|
verticalAlign,
|
||||||
|
wrap,
|
||||||
|
bounds,
|
||||||
|
classNamePrefix,
|
||||||
|
style,
|
||||||
|
textWidth,
|
||||||
|
textHeight,
|
||||||
|
}: TextLabelProps) {
|
||||||
|
const { rInput, isEmpty, isEditing, ...editableTextRest } = useEditableText(id, type, text)
|
||||||
|
|
||||||
const finalText = TextHelpers.normalizeTextForDom(text)
|
const finalText = TextHelpers.normalizeTextForDom(text)
|
||||||
const hasText = finalText.length > 0
|
const hasText = finalText.length > 0
|
||||||
|
|
||||||
const legacyAlign = isLegacyAlign(align)
|
const legacyAlign = isLegacyAlign(align)
|
||||||
const theme = useDefaultColorTheme()
|
const theme = getDefaultColorTheme({ isDarkMode: useIsDarkMode() })
|
||||||
|
|
||||||
if (!isEditing && !hasText) {
|
if (!isEditing && !hasText) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: probably combine tl-text and tl-arrow eventually
|
||||||
|
const cssPrefix = classNamePrefix || 'tl-text'
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="tl-text-label"
|
className={`${cssPrefix}-label tl-text-wrapper`}
|
||||||
data-font={font}
|
data-font={font}
|
||||||
data-align={align}
|
data-align={align}
|
||||||
data-hastext={!isEmpty}
|
data-hastext={!isEmpty}
|
||||||
|
@ -84,48 +86,25 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="tl-text-label__inner"
|
className={`${cssPrefix}-label__inner`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: LABEL_FONT_SIZES[size],
|
fontSize,
|
||||||
lineHeight: LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 'px',
|
lineHeight: fontSize * lineHeight + 'px',
|
||||||
minHeight: TEXT_PROPS.lineHeight + 32,
|
minHeight: lineHeight + 32,
|
||||||
minWidth: 0,
|
minWidth: textWidth || 0,
|
||||||
color: theme[labelColor].solid,
|
color: theme[labelColor].solid,
|
||||||
|
width: textWidth,
|
||||||
|
height: textHeight,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="tl-text tl-text-content" dir="ltr">
|
<div className={`${cssPrefix} tl-text tl-text-content`} dir="ltr">
|
||||||
{finalText}
|
{finalText}
|
||||||
</div>
|
</div>
|
||||||
{isEditing && (
|
{isEditing && <TextArea ref={rInput} text={text} {...editableTextRest} />}
|
||||||
<textarea
|
|
||||||
ref={rInput}
|
|
||||||
className="tl-text tl-text-input"
|
|
||||||
name="text"
|
|
||||||
tabIndex={-1}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoSave="off"
|
|
||||||
autoFocus
|
|
||||||
placeholder=""
|
|
||||||
spellCheck="true"
|
|
||||||
wrap="off"
|
|
||||||
dir="auto"
|
|
||||||
datatype="wysiwyg"
|
|
||||||
defaultValue={text}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onTouchEnd={stopEventPropagation}
|
|
||||||
onContextMenu={stopEventPropagation}
|
|
||||||
onPointerDown={handleInputPointerDown}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -53,3 +53,5 @@ export const FONT_FAMILIES: Record<TLDefaultFontStyle, string> = {
|
||||||
export const LABEL_TO_ARROW_PADDING = 20
|
export const LABEL_TO_ARROW_PADDING = 20
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const ARROW_LABEL_PADDING = 4.25
|
export const ARROW_LABEL_PADDING = 4.25
|
||||||
|
/** @internal */
|
||||||
|
export const LABEL_PADDING = 16
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TLShape,
|
TLShape,
|
||||||
|
TLShapeId,
|
||||||
TLUnknownShape,
|
TLUnknownShape,
|
||||||
getPointerInfo,
|
getPointerInfo,
|
||||||
preventDefault,
|
preventDefault,
|
||||||
|
@ -12,11 +13,8 @@ import {
|
||||||
import React, { useCallback, useEffect, useRef } from 'react'
|
import React, { useCallback, useEffect, useRef } from 'react'
|
||||||
import { INDENT, TextHelpers } from './TextHelpers'
|
import { INDENT, TextHelpers } from './TextHelpers'
|
||||||
|
|
||||||
export function useEditableText<T extends Extract<TLShape, { props: { text: string } }>>(
|
/** @public */
|
||||||
id: T['id'],
|
export function useEditableText(id: TLShapeId, type: string, text: string) {
|
||||||
type: T['type'],
|
|
||||||
text: string
|
|
||||||
) {
|
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
|
|
||||||
const rInput = useRef<HTMLTextAreaElement>(null)
|
const rInput = useRef<HTMLTextAreaElement>(null)
|
||||||
|
|
53
packages/tldraw/src/lib/shapes/text/TextArea.tsx
Normal file
53
packages/tldraw/src/lib/shapes/text/TextArea.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { stopEventPropagation } from '@tldraw/editor'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
|
type TextAreaProps = {
|
||||||
|
text: string
|
||||||
|
handleFocus: () => void
|
||||||
|
handleBlur: () => void
|
||||||
|
handleKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
|
handleInputPointerDown: (e: React.PointerEvent<HTMLTextAreaElement>) => void
|
||||||
|
handleDoubleClick: (e: any) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(function TextArea(
|
||||||
|
{
|
||||||
|
text,
|
||||||
|
handleFocus,
|
||||||
|
handleChange,
|
||||||
|
handleKeyDown,
|
||||||
|
handleBlur,
|
||||||
|
handleInputPointerDown,
|
||||||
|
handleDoubleClick,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
ref={ref}
|
||||||
|
className="tl-text tl-text-input"
|
||||||
|
name="text"
|
||||||
|
tabIndex={-1}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoSave="off"
|
||||||
|
autoFocus
|
||||||
|
placeholder=""
|
||||||
|
spellCheck="true"
|
||||||
|
wrap="off"
|
||||||
|
dir="auto"
|
||||||
|
datatype="wysiwyg"
|
||||||
|
defaultValue={text}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onTouchEnd={stopEventPropagation}
|
||||||
|
onContextMenu={stopEventPropagation}
|
||||||
|
onPointerDown={handleInputPointerDown}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
|
@ -12,18 +12,16 @@ import {
|
||||||
TLTextShape,
|
TLTextShape,
|
||||||
Vec,
|
Vec,
|
||||||
WeakMapCache,
|
WeakMapCache,
|
||||||
getDefaultColorTheme,
|
|
||||||
stopEventPropagation,
|
|
||||||
textShapeMigrations,
|
textShapeMigrations,
|
||||||
textShapeProps,
|
textShapeProps,
|
||||||
toDomPrecision,
|
toDomPrecision,
|
||||||
useEditor,
|
useEditor,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { SvgTextLabel } from '../shared/SvgTextLabel'
|
import { SvgTextLabel } from '../shared/SvgTextLabel'
|
||||||
|
import { TextLabel } from '../shared/TextLabel'
|
||||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants'
|
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants'
|
||||||
import { getFontDefForExport } from '../shared/defaultStyleDefs'
|
import { getFontDefForExport } from '../shared/defaultStyleDefs'
|
||||||
import { resizeScaled } from '../shared/resizeScaled'
|
import { resizeScaled } from '../shared/resizeScaled'
|
||||||
import { useEditableText } from '../shared/useEditableText'
|
|
||||||
|
|
||||||
const sizeCache = new WeakMapCache<TLTextShape['props'], { height: number; width: number }>()
|
const sizeCache = new WeakMapCache<TLTextShape['props'], { height: number; width: number }>()
|
||||||
|
|
||||||
|
@ -67,75 +65,32 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
||||||
component(shape: TLTextShape) {
|
component(shape: TLTextShape) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
type,
|
props: { font, size, text, color, scale, align },
|
||||||
props: { text, color },
|
|
||||||
} = shape
|
} = shape
|
||||||
|
|
||||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.getIsDarkMode() })
|
|
||||||
const { width, height } = this.getMinDimensions(shape)
|
const { width, height } = this.getMinDimensions(shape)
|
||||||
|
|
||||||
const {
|
|
||||||
rInput,
|
|
||||||
isEmpty,
|
|
||||||
isEditing,
|
|
||||||
handleFocus,
|
|
||||||
handleChange,
|
|
||||||
handleKeyDown,
|
|
||||||
handleBlur,
|
|
||||||
handleInputPointerDown,
|
|
||||||
handleDoubleClick,
|
|
||||||
} = useEditableText(id, type, text)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLContainer id={shape.id}>
|
<HTMLContainer id={shape.id}>
|
||||||
<div
|
<TextLabel
|
||||||
className="tl-text-shape__wrapper tl-text-shadow"
|
id={id}
|
||||||
data-font={shape.props.font}
|
classNamePrefix="tl-text-shape"
|
||||||
data-align={shape.props.align}
|
type="text"
|
||||||
data-hastext={!isEmpty}
|
font={font}
|
||||||
data-isediting={isEditing}
|
fontSize={FONT_SIZES[size]}
|
||||||
data-textwrap={true}
|
lineHeight={TEXT_PROPS.lineHeight}
|
||||||
|
align={align}
|
||||||
|
verticalAlign="middle"
|
||||||
|
text={text}
|
||||||
|
labelColor={color}
|
||||||
|
textWidth={width}
|
||||||
|
textHeight={height}
|
||||||
style={{
|
style={{
|
||||||
fontSize: FONT_SIZES[shape.props.size],
|
transform: `scale(${scale})`,
|
||||||
lineHeight: FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight + 'px',
|
|
||||||
transform: `scale(${shape.props.scale})`,
|
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
width: Math.max(1, width),
|
|
||||||
height: Math.max(FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight, height),
|
|
||||||
color: theme[color].solid,
|
|
||||||
}}
|
}}
|
||||||
>
|
wrap
|
||||||
<div className="tl-text tl-text-content" dir="ltr">
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
{isEditing ? (
|
|
||||||
<textarea
|
|
||||||
ref={rInput}
|
|
||||||
className="tl-text tl-text-input"
|
|
||||||
name="text"
|
|
||||||
tabIndex={-1}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoSave="off"
|
|
||||||
autoFocus
|
|
||||||
placeholder=""
|
|
||||||
spellCheck="true"
|
|
||||||
wrap="off"
|
|
||||||
dir="auto"
|
|
||||||
datatype="wysiwyg"
|
|
||||||
defaultValue={text}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onTouchEnd={stopEventPropagation}
|
|
||||||
onContextMenu={stopEventPropagation}
|
|
||||||
onPointerDown={handleInputPointerDown}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</HTMLContainer>
|
</HTMLContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue