import { T } from '@tldraw/validate' import { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText' import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape' import { RecordProps } from '../recordsWithProps' import { DefaultColorStyle, TLDefaultColorStyle } from '../styles/TLColorStyle' import { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle' import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle' import { DefaultTextAlignStyle, TLDefaultTextAlignStyle } from '../styles/TLTextAlignStyle' import { TLBaseShape } from './TLBaseShape' /** * Configuration interface defining properties for text shapes in tldraw. * Text shapes support rich formatting, styling, and automatic sizing. * * @public * @example * ```ts * const textProps: TLTextShapeProps = { * color: 'black', * size: 'm', * font: 'draw', * textAlign: 'start', * w: 200, * richText: toRichText('Hello **bold** text'), * scale: 1, * autoSize: true * } * ``` */ export interface TLTextShapeProps { color: TLDefaultColorStyle size: TLDefaultSizeStyle font: TLDefaultFontStyle textAlign: TLDefaultTextAlignStyle w: number richText: TLRichText scale: number autoSize: boolean } /** * A text shape that can display formatted text content with various styling options. * Text shapes support rich formatting, automatic sizing, and consistent styling through * the tldraw style system. * * @public * @example * ```ts * const textShape: TLTextShape = { * id: 'shape:text123', * typeName: 'shape', * type: 'text', * x: 100, * y: 200, * rotation: 0, * index: 'a1', * parentId: 'page:main', * isLocked: false, * opacity: 1, * props: { * color: 'black', * size: 'm', * font: 'draw', * textAlign: 'start', * w: 200, * richText: toRichText('Sample text'), * scale: 1, * autoSize: false * }, * meta: {} * } * ``` */ export type TLTextShape = TLBaseShape<'text', TLTextShapeProps> /** * Validation schema for text shape properties. This defines the runtime validation * rules that ensure text shape data integrity when records are stored or transmitted. * * @public * @example * ```ts * import { textShapeProps } from '@tldraw/tlschema' * * // Validate text shape properties * const isValid = textShapeProps.richText.isValid(myRichText) * if (isValid) { * // Properties are valid, safe to use * } * ``` */ export const textShapeProps: RecordProps = { color: DefaultColorStyle, size: DefaultSizeStyle, font: DefaultFontStyle, textAlign: DefaultTextAlignStyle, w: T.nonZeroNumber, richText: richTextValidator, scale: T.nonZeroNumber, autoSize: T.boolean, } const Versions = createShapePropsMigrationIds('text', { RemoveJustify: 1, AddTextAlign: 2, AddRichText: 3, AddRichTextAttrs: 4, }) /** * Version identifiers for text shape migrations. These constants track * the evolution of the text shape schema over time. * * @public * @example * ```ts * import { textShapeVersions } from '@tldraw/tlschema' * * // Check if shape data needs migration * if (shapeVersion < textShapeVersions.AddRichTextAttrs) { * // Apply rich text attrs migration * } * ``` */ export { Versions as textShapeVersions } /** * Migration sequence for text shape schema evolution. This handles transforming * text shape data between different versions as the schema evolves over time. * * Key migrations include: * - RemoveJustify: Replaced 'justify' alignment with 'start' * - AddTextAlign: Migrated from 'align' to 'textAlign' property * - AddRichText: Converted plain text to rich text format * - AddRichTextAttrs: Added support for attrs property on richText * * @public */ export const textShapeMigrations = createShapePropsMigrationSequence({ sequence: [ { id: Versions.RemoveJustify, up: (props) => { if (props.align === 'justify') { props.align = 'start' } }, down: 'retired', }, { id: Versions.AddTextAlign, up: (props) => { props.textAlign = props.align delete props.align }, down: (props) => { props.align = props.textAlign delete props.textAlign }, }, { id: Versions.AddRichText, up: (props) => { props.richText = toRichText(props.text) delete props.text }, // N.B. Explicitly no down state so that we force clients to update. // down: (props) => { // delete props.richText // }, }, { id: Versions.AddRichTextAttrs, up: (_props) => { // noop - attrs is optional so old records are valid }, down: (props) => { // Remove attrs from richText when migrating down if (props.richText && 'attrs' in props.richText) { delete props.richText.attrs } }, }, ], })