@tldraw/tlschema 4.1.0-canary.9c36de6e611c → 4.1.0-canary.a152954244d2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/TLStore.js +3 -10
- package/dist-cjs/TLStore.js.map +2 -2
- package/dist-cjs/assets/TLBaseAsset.js.map +2 -2
- package/dist-cjs/assets/TLBookmarkAsset.js.map +2 -2
- package/dist-cjs/assets/TLImageAsset.js.map +2 -2
- package/dist-cjs/assets/TLVideoAsset.js.map +2 -2
- package/dist-cjs/bindings/TLArrowBinding.js.map +2 -2
- package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +4416 -223
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLColor.js.map +2 -2
- package/dist-cjs/misc/TLCursor.js.map +2 -2
- package/dist-cjs/misc/TLHandle.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/TLScribble.js.map +2 -2
- package/dist-cjs/misc/geometry-types.js.map +2 -2
- package/dist-cjs/misc/id-validator.js.map +2 -2
- package/dist-cjs/records/TLAsset.js.map +2 -2
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLCamera.js.map +2 -2
- package/dist-cjs/records/TLDocument.js.map +2 -2
- package/dist-cjs/records/TLInstance.js.map +2 -2
- package/dist-cjs/records/TLPage.js.map +2 -2
- package/dist-cjs/records/TLPageState.js.map +2 -2
- package/dist-cjs/records/TLPointer.js.map +2 -2
- package/dist-cjs/records/TLPresence.js.map +2 -2
- package/dist-cjs/records/TLRecord.js.map +1 -1
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/recordsWithProps.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLBookmarkShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +0 -10
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLFrameShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLGroupShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLImageShape.js.map +2 -2
- package/dist-cjs/shapes/TLLineShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/shapes/TLVideoShape.js.map +2 -2
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-cjs/styles/TLColorStyle.js.map +2 -2
- package/dist-cjs/styles/TLDashStyle.js.map +2 -2
- package/dist-cjs/styles/TLFillStyle.js.map +2 -2
- package/dist-cjs/styles/TLFontStyle.js.map +2 -2
- package/dist-cjs/styles/TLHorizontalAlignStyle.js.map +2 -2
- package/dist-cjs/styles/TLSizeStyle.js.map +2 -2
- package/dist-cjs/styles/TLTextAlignStyle.js.map +2 -2
- package/dist-cjs/styles/TLVerticalAlignStyle.js.map +2 -2
- package/dist-cjs/translations/translations.js +1 -1
- package/dist-cjs/translations/translations.js.map +2 -2
- package/dist-cjs/util-types.js.map +1 -1
- package/dist-esm/TLStore.mjs +3 -10
- package/dist-esm/TLStore.mjs.map +2 -2
- package/dist-esm/assets/TLBaseAsset.mjs.map +2 -2
- package/dist-esm/assets/TLBookmarkAsset.mjs.map +2 -2
- package/dist-esm/assets/TLImageAsset.mjs.map +2 -2
- package/dist-esm/assets/TLVideoAsset.mjs.map +2 -2
- package/dist-esm/bindings/TLArrowBinding.mjs.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +4416 -223
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLColor.mjs.map +2 -2
- package/dist-esm/misc/TLCursor.mjs.map +2 -2
- package/dist-esm/misc/TLHandle.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/TLScribble.mjs.map +2 -2
- package/dist-esm/misc/geometry-types.mjs.map +2 -2
- package/dist-esm/misc/id-validator.mjs.map +2 -2
- package/dist-esm/records/TLAsset.mjs.map +2 -2
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLCamera.mjs.map +2 -2
- package/dist-esm/records/TLDocument.mjs.map +2 -2
- package/dist-esm/records/TLInstance.mjs.map +2 -2
- package/dist-esm/records/TLPage.mjs.map +2 -2
- package/dist-esm/records/TLPageState.mjs.map +2 -2
- package/dist-esm/records/TLPointer.mjs.map +2 -2
- package/dist-esm/records/TLPresence.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/recordsWithProps.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBookmarkShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +0 -10
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLFrameShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGroupShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLImageShape.mjs.map +2 -2
- package/dist-esm/shapes/TLLineShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/shapes/TLVideoShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/dist-esm/styles/TLColorStyle.mjs.map +2 -2
- package/dist-esm/styles/TLDashStyle.mjs.map +2 -2
- package/dist-esm/styles/TLFillStyle.mjs.map +2 -2
- package/dist-esm/styles/TLFontStyle.mjs.map +2 -2
- package/dist-esm/styles/TLHorizontalAlignStyle.mjs.map +2 -2
- package/dist-esm/styles/TLSizeStyle.mjs.map +2 -2
- package/dist-esm/styles/TLTextAlignStyle.mjs.map +2 -2
- package/dist-esm/styles/TLVerticalAlignStyle.mjs.map +2 -2
- package/dist-esm/translations/translations.mjs +1 -1
- package/dist-esm/translations/translations.mjs.map +2 -2
- package/package.json +5 -5
- package/src/TLStore.test.ts +644 -0
- package/src/TLStore.ts +205 -20
- package/src/assets/TLBaseAsset.ts +90 -7
- package/src/assets/TLBookmarkAsset.test.ts +96 -0
- package/src/assets/TLBookmarkAsset.ts +52 -2
- package/src/assets/TLImageAsset.test.ts +213 -0
- package/src/assets/TLImageAsset.ts +60 -2
- package/src/assets/TLVideoAsset.test.ts +105 -0
- package/src/assets/TLVideoAsset.ts +93 -4
- package/src/bindings/TLArrowBinding.test.ts +55 -0
- package/src/bindings/TLArrowBinding.ts +132 -10
- package/src/bindings/TLBaseBinding.ts +140 -3
- package/src/createPresenceStateDerivation.test.ts +158 -0
- package/src/createPresenceStateDerivation.ts +71 -2
- package/src/createTLSchema.test.ts +181 -0
- package/src/createTLSchema.ts +164 -7
- package/src/index.ts +32 -0
- package/src/misc/TLColor.ts +50 -6
- package/src/misc/TLCursor.ts +110 -8
- package/src/misc/TLHandle.ts +86 -6
- package/src/misc/TLOpacity.ts +51 -2
- package/src/misc/TLRichText.ts +56 -3
- package/src/misc/TLScribble.ts +105 -5
- package/src/misc/geometry-types.ts +30 -2
- package/src/misc/id-validator.test.ts +50 -0
- package/src/misc/id-validator.ts +20 -1
- package/src/records/TLAsset.test.ts +234 -0
- package/src/records/TLAsset.ts +165 -8
- package/src/records/TLBinding.test.ts +22 -0
- package/src/records/TLBinding.ts +277 -11
- package/src/records/TLCamera.test.ts +19 -0
- package/src/records/TLCamera.ts +118 -7
- package/src/records/TLDocument.test.ts +35 -0
- package/src/records/TLDocument.ts +148 -8
- package/src/records/TLInstance.test.ts +201 -0
- package/src/records/TLInstance.ts +117 -9
- package/src/records/TLPage.test.ts +110 -0
- package/src/records/TLPage.ts +106 -8
- package/src/records/TLPageState.test.ts +228 -0
- package/src/records/TLPageState.ts +88 -7
- package/src/records/TLPointer.test.ts +63 -0
- package/src/records/TLPointer.ts +105 -7
- package/src/records/TLPresence.test.ts +190 -0
- package/src/records/TLPresence.ts +99 -5
- package/src/records/TLRecord.test.ts +70 -0
- package/src/records/TLRecord.ts +43 -1
- package/src/records/TLShape.test.ts +232 -0
- package/src/records/TLShape.ts +289 -12
- package/src/recordsWithProps.test.ts +188 -0
- package/src/recordsWithProps.ts +131 -2
- package/src/shapes/ShapeWithCrop.test.ts +18 -0
- package/src/shapes/ShapeWithCrop.ts +64 -2
- package/src/shapes/TLArrowShape.test.ts +505 -0
- package/src/shapes/TLArrowShape.ts +188 -10
- package/src/shapes/TLBaseShape.test.ts +142 -0
- package/src/shapes/TLBaseShape.ts +103 -4
- package/src/shapes/TLBookmarkShape.test.ts +122 -0
- package/src/shapes/TLBookmarkShape.ts +58 -4
- package/src/shapes/TLDrawShape.test.ts +177 -0
- package/src/shapes/TLDrawShape.ts +97 -6
- package/src/shapes/TLEmbedShape.test.ts +286 -0
- package/src/shapes/TLEmbedShape.ts +57 -14
- package/src/shapes/TLFrameShape.test.ts +71 -0
- package/src/shapes/TLFrameShape.ts +59 -4
- package/src/shapes/TLGeoShape.test.ts +247 -0
- package/src/shapes/TLGeoShape.ts +103 -7
- package/src/shapes/TLGroupShape.test.ts +59 -0
- package/src/shapes/TLGroupShape.ts +52 -4
- package/src/shapes/TLHighlightShape.test.ts +325 -0
- package/src/shapes/TLHighlightShape.ts +79 -4
- package/src/shapes/TLImageShape.test.ts +534 -0
- package/src/shapes/TLImageShape.ts +105 -5
- package/src/shapes/TLLineShape.test.ts +269 -0
- package/src/shapes/TLLineShape.ts +128 -8
- package/src/shapes/TLNoteShape.test.ts +1568 -0
- package/src/shapes/TLNoteShape.ts +97 -4
- package/src/shapes/TLTextShape.test.ts +407 -0
- package/src/shapes/TLTextShape.ts +94 -4
- package/src/shapes/TLVideoShape.test.ts +112 -0
- package/src/shapes/TLVideoShape.ts +99 -4
- package/src/store-migrations.test.ts +88 -0
- package/src/store-migrations.ts +47 -1
- package/src/styles/TLColorStyle.test.ts +439 -0
- package/src/styles/TLColorStyle.ts +228 -10
- package/src/styles/TLDashStyle.ts +54 -2
- package/src/styles/TLFillStyle.ts +54 -2
- package/src/styles/TLFontStyle.ts +72 -3
- package/src/styles/TLHorizontalAlignStyle.ts +55 -2
- package/src/styles/TLSizeStyle.ts +54 -2
- package/src/styles/TLTextAlignStyle.ts +52 -2
- package/src/styles/TLVerticalAlignStyle.ts +52 -2
- package/src/translations/translations.test.ts +378 -35
- package/src/translations/translations.ts +157 -10
- package/src/util-types.ts +51 -1
|
@@ -19,25 +19,105 @@ import {
|
|
|
19
19
|
} from '../styles/TLVerticalAlignStyle'
|
|
20
20
|
import { TLBaseShape } from './TLBaseShape'
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Properties for a note shape. Note shapes represent sticky notes or text annotations
|
|
24
|
+
* with rich formatting capabilities and various styling options.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const noteProps: TLNoteShapeProps = {
|
|
30
|
+
* color: 'yellow',
|
|
31
|
+
* labelColor: 'black',
|
|
32
|
+
* size: 'm',
|
|
33
|
+
* font: 'draw',
|
|
34
|
+
* fontSizeAdjustment: 0,
|
|
35
|
+
* align: 'middle',
|
|
36
|
+
* verticalAlign: 'middle',
|
|
37
|
+
* growY: 0,
|
|
38
|
+
* url: '',
|
|
39
|
+
* richText: toRichText('Hello **world**!'),
|
|
40
|
+
* scale: 1
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
23
44
|
export interface TLNoteShapeProps {
|
|
45
|
+
/** Background color style of the note */
|
|
24
46
|
color: TLDefaultColorStyle
|
|
47
|
+
/** Text color style for the note content */
|
|
25
48
|
labelColor: TLDefaultColorStyle
|
|
49
|
+
/** Size style determining the font size and note dimensions */
|
|
26
50
|
size: TLDefaultSizeStyle
|
|
51
|
+
/** Font family style for the note text */
|
|
27
52
|
font: TLDefaultFontStyle
|
|
53
|
+
/** Adjustment to the base font size (positive increases, negative decreases) */
|
|
28
54
|
fontSizeAdjustment: number
|
|
55
|
+
/** Horizontal alignment of text within the note */
|
|
29
56
|
align: TLDefaultHorizontalAlignStyle
|
|
57
|
+
/** Vertical alignment of text within the note */
|
|
30
58
|
verticalAlign: TLDefaultVerticalAlignStyle
|
|
59
|
+
/** Additional height growth for the note beyond its base size */
|
|
31
60
|
growY: number
|
|
61
|
+
/** Optional URL associated with the note for linking */
|
|
32
62
|
url: string
|
|
63
|
+
/** Rich text content with formatting like bold, italic, etc. */
|
|
33
64
|
richText: TLRichText
|
|
65
|
+
/** Scale factor applied to the note shape for display */
|
|
34
66
|
scale: number
|
|
35
67
|
}
|
|
36
68
|
|
|
37
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* A note shape representing a sticky note or text annotation on the canvas.
|
|
71
|
+
* Note shapes support rich text formatting, various styling options, and can
|
|
72
|
+
* be used for annotations, reminders, or general text content.
|
|
73
|
+
*
|
|
74
|
+
* @public
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const noteShape: TLNoteShape = {
|
|
78
|
+
* id: 'shape:note1',
|
|
79
|
+
* type: 'note',
|
|
80
|
+
* x: 100,
|
|
81
|
+
* y: 100,
|
|
82
|
+
* rotation: 0,
|
|
83
|
+
* index: 'a1',
|
|
84
|
+
* parentId: 'page:main',
|
|
85
|
+
* isLocked: false,
|
|
86
|
+
* opacity: 1,
|
|
87
|
+
* props: {
|
|
88
|
+
* color: 'light-blue',
|
|
89
|
+
* labelColor: 'black',
|
|
90
|
+
* size: 's',
|
|
91
|
+
* font: 'sans',
|
|
92
|
+
* fontSizeAdjustment: 2,
|
|
93
|
+
* align: 'start',
|
|
94
|
+
* verticalAlign: 'start',
|
|
95
|
+
* growY: 50,
|
|
96
|
+
* url: 'https://example.com',
|
|
97
|
+
* richText: toRichText('Important **note**!'),
|
|
98
|
+
* scale: 1
|
|
99
|
+
* },
|
|
100
|
+
* meta: {},
|
|
101
|
+
* typeName: 'shape'
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
38
105
|
export type TLNoteShape = TLBaseShape<'note', TLNoteShapeProps>
|
|
39
106
|
|
|
40
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Validation schema for note shape properties. Defines the runtime validation rules
|
|
109
|
+
* for all properties of note shapes, ensuring data integrity and type safety.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* import { noteShapeProps } from '@tldraw/tlschema'
|
|
115
|
+
*
|
|
116
|
+
* // Used internally by the validation system
|
|
117
|
+
* const validator = T.object(noteShapeProps)
|
|
118
|
+
* const validatedProps = validator.validate(someNoteProps)
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
41
121
|
export const noteShapeProps: RecordProps<TLNoteShape> = {
|
|
42
122
|
color: DefaultColorStyle,
|
|
43
123
|
labelColor: DefaultLabelColorStyle,
|
|
@@ -64,9 +144,22 @@ const Versions = createShapePropsMigrationIds('note', {
|
|
|
64
144
|
AddRichText: 9,
|
|
65
145
|
})
|
|
66
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Version identifiers for note shape migrations. These version numbers track
|
|
149
|
+
* significant schema changes over time, enabling proper data migration between versions.
|
|
150
|
+
*
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
67
153
|
export { Versions as noteShapeVersions }
|
|
68
154
|
|
|
69
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Migration sequence for note shapes. Handles schema evolution over time by defining
|
|
157
|
+
* how to upgrade and downgrade note shape data between different versions. Includes
|
|
158
|
+
* migrations for URL properties, text alignment changes, vertical alignment addition,
|
|
159
|
+
* font size adjustments, scaling support, label color, and the transition from plain text to rich text.
|
|
160
|
+
*
|
|
161
|
+
* @public
|
|
162
|
+
*/
|
|
70
163
|
export const noteShapeMigrations = createShapePropsMigrationSequence({
|
|
71
164
|
sequence: [
|
|
72
165
|
{
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { T } from '@tldraw/validate'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
4
|
+
import { TLRichText, toRichText } from '../misc/TLRichText'
|
|
5
|
+
import { DefaultColorStyle } from '../styles/TLColorStyle'
|
|
6
|
+
import { DefaultFontStyle } from '../styles/TLFontStyle'
|
|
7
|
+
import { DefaultSizeStyle } from '../styles/TLSizeStyle'
|
|
8
|
+
import { DefaultTextAlignStyle } from '../styles/TLTextAlignStyle'
|
|
9
|
+
import { textShapeMigrations, textShapeProps, textShapeVersions } from './TLTextShape'
|
|
10
|
+
|
|
11
|
+
describe('TLTextShape', () => {
|
|
12
|
+
describe('textShapeProps validation schema', () => {
|
|
13
|
+
it('should validate using comprehensive object validator', () => {
|
|
14
|
+
const fullValidator = T.object(textShapeProps)
|
|
15
|
+
|
|
16
|
+
const validPropsObject = {
|
|
17
|
+
color: 'red' as const,
|
|
18
|
+
size: 's' as const,
|
|
19
|
+
font: 'mono' as const,
|
|
20
|
+
textAlign: 'end' as const,
|
|
21
|
+
w: 250,
|
|
22
|
+
richText: toRichText('Complete validation test') as TLRichText,
|
|
23
|
+
scale: 0.8,
|
|
24
|
+
autoSize: true,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
expect(() => fullValidator.validate(validPropsObject)).not.toThrow()
|
|
28
|
+
const result = fullValidator.validate(validPropsObject)
|
|
29
|
+
expect(result).toEqual(validPropsObject)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should validate width as nonZeroNumber', () => {
|
|
33
|
+
// Valid non-zero positive numbers
|
|
34
|
+
const validWidths = [0.1, 1, 50, 100, 1000, 0.001]
|
|
35
|
+
|
|
36
|
+
validWidths.forEach((w) => {
|
|
37
|
+
expect(() => textShapeProps.w.validate(w)).not.toThrow()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Invalid widths (zero, negative numbers, and non-numbers)
|
|
41
|
+
const invalidWidths = [0, -1, -0.1, 'not-number', null, undefined, {}, [], true, false]
|
|
42
|
+
|
|
43
|
+
invalidWidths.forEach((w) => {
|
|
44
|
+
expect(() => textShapeProps.w.validate(w)).toThrow()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should validate scale as nonZeroNumber', () => {
|
|
49
|
+
// Valid non-zero positive numbers
|
|
50
|
+
const validScales = [0.1, 0.5, 1, 1.5, 2, 10]
|
|
51
|
+
|
|
52
|
+
validScales.forEach((scale) => {
|
|
53
|
+
expect(() => textShapeProps.scale.validate(scale)).not.toThrow()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Invalid scales (zero, negative numbers, and non-numbers)
|
|
57
|
+
const invalidScales = [0, -0.5, -1, -2, 'not-number', null, undefined, {}, [], true, false]
|
|
58
|
+
|
|
59
|
+
invalidScales.forEach((scale) => {
|
|
60
|
+
expect(() => textShapeProps.scale.validate(scale)).toThrow()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should use correct default style validators', () => {
|
|
65
|
+
// Verify that the props schema uses the expected style validators
|
|
66
|
+
expect(textShapeProps.color).toBe(DefaultColorStyle)
|
|
67
|
+
expect(textShapeProps.size).toBe(DefaultSizeStyle)
|
|
68
|
+
expect(textShapeProps.font).toBe(DefaultFontStyle)
|
|
69
|
+
expect(textShapeProps.textAlign).toBe(DefaultTextAlignStyle)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should use correct primitive validators', () => {
|
|
73
|
+
// Check that non-style properties use correct T validators
|
|
74
|
+
expect(textShapeProps.w).toBe(T.nonZeroNumber)
|
|
75
|
+
expect(textShapeProps.scale).toBe(T.nonZeroNumber)
|
|
76
|
+
expect(textShapeProps.autoSize).toBe(T.boolean)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('textShapeVersions', () => {
|
|
81
|
+
it('should have all expected migration versions', () => {
|
|
82
|
+
const expectedVersions: Array<keyof typeof textShapeVersions> = [
|
|
83
|
+
'RemoveJustify',
|
|
84
|
+
'AddTextAlign',
|
|
85
|
+
'AddRichText',
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
expectedVersions.forEach((version) => {
|
|
89
|
+
expect(textShapeVersions[version]).toBeDefined()
|
|
90
|
+
expect(typeof textShapeVersions[version]).toBe('string')
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('textShapeMigrations', () => {
|
|
96
|
+
it('should have migrations for all version IDs', () => {
|
|
97
|
+
const migrationIds = textShapeMigrations.sequence
|
|
98
|
+
.filter((migration) => 'id' in migration)
|
|
99
|
+
.map((migration) => ('id' in migration ? migration.id : null))
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
|
|
102
|
+
const versionIds = Object.values(textShapeVersions)
|
|
103
|
+
|
|
104
|
+
versionIds.forEach((versionId) => {
|
|
105
|
+
expect(migrationIds).toContain(versionId)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('textShapeMigrations - RemoveJustify migration', () => {
|
|
111
|
+
const { up, down } = getTestMigration(textShapeVersions.RemoveJustify)
|
|
112
|
+
|
|
113
|
+
describe('RemoveJustify up migration', () => {
|
|
114
|
+
it('should convert justify alignment to start', () => {
|
|
115
|
+
const oldRecord = {
|
|
116
|
+
id: 'shape:text1',
|
|
117
|
+
props: {
|
|
118
|
+
color: 'black',
|
|
119
|
+
font: 'draw',
|
|
120
|
+
size: 'm',
|
|
121
|
+
align: 'justify',
|
|
122
|
+
w: 200,
|
|
123
|
+
text: 'Test text',
|
|
124
|
+
scale: 1,
|
|
125
|
+
autoSize: true,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const result = up(oldRecord)
|
|
130
|
+
expect(result.props.align).toBe('start')
|
|
131
|
+
expect(result.props.color).toBe('black') // Preserve other props
|
|
132
|
+
expect(result.props.text).toBe('Test text')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should preserve non-justify alignments', () => {
|
|
136
|
+
const alignments = ['start', 'middle', 'end']
|
|
137
|
+
|
|
138
|
+
alignments.forEach((align) => {
|
|
139
|
+
const oldRecord = {
|
|
140
|
+
id: 'shape:text1',
|
|
141
|
+
props: {
|
|
142
|
+
color: 'red',
|
|
143
|
+
font: 'sans',
|
|
144
|
+
size: 'l',
|
|
145
|
+
align,
|
|
146
|
+
w: 300,
|
|
147
|
+
text: 'Aligned text',
|
|
148
|
+
scale: 1,
|
|
149
|
+
autoSize: false,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = up(oldRecord)
|
|
154
|
+
expect(result.props.align).toBe(align)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should preserve all other properties during migration', () => {
|
|
159
|
+
const oldRecord = {
|
|
160
|
+
id: 'shape:text1',
|
|
161
|
+
props: {
|
|
162
|
+
color: 'blue',
|
|
163
|
+
font: 'serif',
|
|
164
|
+
size: 'xl',
|
|
165
|
+
align: 'justify',
|
|
166
|
+
w: 400,
|
|
167
|
+
text: 'Justified text becomes start aligned',
|
|
168
|
+
scale: 1.5,
|
|
169
|
+
autoSize: false,
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = up(oldRecord)
|
|
174
|
+
expect(result.props.align).toBe('start')
|
|
175
|
+
expect(result.props.color).toBe('blue')
|
|
176
|
+
expect(result.props.font).toBe('serif')
|
|
177
|
+
expect(result.props.size).toBe('xl')
|
|
178
|
+
expect(result.props.w).toBe(400)
|
|
179
|
+
expect(result.props.text).toBe('Justified text becomes start aligned')
|
|
180
|
+
expect(result.props.scale).toBe(1.5)
|
|
181
|
+
expect(result.props.autoSize).toBe(false)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('RemoveJustify down migration', () => {
|
|
186
|
+
it('should be retired (no down migration)', () => {
|
|
187
|
+
expect(() => {
|
|
188
|
+
down({})
|
|
189
|
+
}).toThrow('Migration com.tldraw.shape.text/1 does not have a down function')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('textShapeMigrations - AddTextAlign migration', () => {
|
|
195
|
+
const { up, down } = getTestMigration(textShapeVersions.AddTextAlign)
|
|
196
|
+
|
|
197
|
+
describe('AddTextAlign up migration', () => {
|
|
198
|
+
it('should migrate align to textAlign', () => {
|
|
199
|
+
const oldRecord = {
|
|
200
|
+
id: 'shape:text1',
|
|
201
|
+
props: {
|
|
202
|
+
color: 'black',
|
|
203
|
+
font: 'draw',
|
|
204
|
+
size: 'm',
|
|
205
|
+
align: 'start',
|
|
206
|
+
w: 200,
|
|
207
|
+
text: 'Test text',
|
|
208
|
+
scale: 1,
|
|
209
|
+
autoSize: true,
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = up(oldRecord)
|
|
214
|
+
expect(result.props.textAlign).toBe('start')
|
|
215
|
+
expect(result.props.align).toBeUndefined()
|
|
216
|
+
expect(result.props.color).toBe('black') // Preserve other props
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('should handle all alignment values', () => {
|
|
220
|
+
const alignments = ['start', 'middle', 'end']
|
|
221
|
+
|
|
222
|
+
alignments.forEach((align) => {
|
|
223
|
+
const oldRecord = {
|
|
224
|
+
id: 'shape:text1',
|
|
225
|
+
props: {
|
|
226
|
+
color: 'red',
|
|
227
|
+
align,
|
|
228
|
+
w: 200,
|
|
229
|
+
text: 'Test',
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = up(oldRecord)
|
|
234
|
+
expect(result.props.textAlign).toBe(align)
|
|
235
|
+
expect(result.props.align).toBeUndefined()
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should preserve all other properties during migration', () => {
|
|
240
|
+
const oldRecord = {
|
|
241
|
+
id: 'shape:text1',
|
|
242
|
+
props: {
|
|
243
|
+
color: 'green',
|
|
244
|
+
font: 'mono',
|
|
245
|
+
size: 's',
|
|
246
|
+
align: 'middle',
|
|
247
|
+
w: 150,
|
|
248
|
+
text: 'Migration test',
|
|
249
|
+
scale: 2,
|
|
250
|
+
autoSize: false,
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const result = up(oldRecord)
|
|
255
|
+
expect(result.props.textAlign).toBe('middle')
|
|
256
|
+
expect(result.props.align).toBeUndefined()
|
|
257
|
+
expect(result.props.color).toBe('green')
|
|
258
|
+
expect(result.props.font).toBe('mono')
|
|
259
|
+
expect(result.props.size).toBe('s')
|
|
260
|
+
expect(result.props.w).toBe(150)
|
|
261
|
+
expect(result.props.text).toBe('Migration test')
|
|
262
|
+
expect(result.props.scale).toBe(2)
|
|
263
|
+
expect(result.props.autoSize).toBe(false)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe('AddTextAlign down migration', () => {
|
|
268
|
+
it('should migrate textAlign back to align', () => {
|
|
269
|
+
const newRecord = {
|
|
270
|
+
id: 'shape:text1',
|
|
271
|
+
props: {
|
|
272
|
+
color: 'black',
|
|
273
|
+
font: 'draw',
|
|
274
|
+
size: 'm',
|
|
275
|
+
textAlign: 'start',
|
|
276
|
+
w: 200,
|
|
277
|
+
text: 'Test text',
|
|
278
|
+
scale: 1,
|
|
279
|
+
autoSize: true,
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = down(newRecord)
|
|
284
|
+
expect(result.props.align).toBe('start')
|
|
285
|
+
expect(result.props.textAlign).toBeUndefined()
|
|
286
|
+
expect(result.props.color).toBe('black') // Preserve other props
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should handle all textAlign values during down migration', () => {
|
|
290
|
+
const alignments = ['start', 'middle', 'end']
|
|
291
|
+
|
|
292
|
+
alignments.forEach((textAlign) => {
|
|
293
|
+
const newRecord = {
|
|
294
|
+
id: 'shape:text1',
|
|
295
|
+
props: {
|
|
296
|
+
color: 'blue',
|
|
297
|
+
textAlign,
|
|
298
|
+
w: 200,
|
|
299
|
+
text: 'Test',
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const result = down(newRecord)
|
|
304
|
+
expect(result.props.align).toBe(textAlign)
|
|
305
|
+
expect(result.props.textAlign).toBeUndefined()
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('textShapeMigrations - AddRichText migration', () => {
|
|
312
|
+
const { up } = getTestMigration(textShapeVersions.AddRichText)
|
|
313
|
+
|
|
314
|
+
describe('AddRichText up migration', () => {
|
|
315
|
+
it('should convert text property to richText', () => {
|
|
316
|
+
const oldRecord = {
|
|
317
|
+
id: 'shape:text1',
|
|
318
|
+
props: {
|
|
319
|
+
color: 'black',
|
|
320
|
+
font: 'draw',
|
|
321
|
+
size: 'm',
|
|
322
|
+
textAlign: 'start',
|
|
323
|
+
w: 200,
|
|
324
|
+
text: 'Simple text content',
|
|
325
|
+
scale: 1,
|
|
326
|
+
autoSize: true,
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const result = up(oldRecord)
|
|
331
|
+
expect(result.props.richText).toBeDefined()
|
|
332
|
+
expect(result.props.text).toBeUndefined()
|
|
333
|
+
expect(result.props.color).toBe('black') // Preserve other props
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should handle empty text', () => {
|
|
337
|
+
const oldRecord = {
|
|
338
|
+
id: 'shape:text1',
|
|
339
|
+
props: {
|
|
340
|
+
color: 'red',
|
|
341
|
+
font: 'sans',
|
|
342
|
+
size: 'l',
|
|
343
|
+
textAlign: 'middle',
|
|
344
|
+
w: 300,
|
|
345
|
+
text: '',
|
|
346
|
+
scale: 1,
|
|
347
|
+
autoSize: false,
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const result = up(oldRecord)
|
|
352
|
+
expect(result.props.richText).toBeDefined()
|
|
353
|
+
expect(result.props.text).toBeUndefined()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('should handle multi-line text', () => {
|
|
357
|
+
const oldRecord = {
|
|
358
|
+
id: 'shape:text1',
|
|
359
|
+
props: {
|
|
360
|
+
color: 'blue',
|
|
361
|
+
font: 'serif',
|
|
362
|
+
size: 'xl',
|
|
363
|
+
textAlign: 'end',
|
|
364
|
+
w: 400,
|
|
365
|
+
text: 'Line 1\nLine 2\nLine 3',
|
|
366
|
+
scale: 1.2,
|
|
367
|
+
autoSize: true,
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const result = up(oldRecord)
|
|
372
|
+
expect(result.props.richText).toBeDefined()
|
|
373
|
+
expect(result.props.text).toBeUndefined()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should preserve all other properties during migration', () => {
|
|
377
|
+
const oldRecord = {
|
|
378
|
+
id: 'shape:text1',
|
|
379
|
+
props: {
|
|
380
|
+
color: 'green',
|
|
381
|
+
font: 'mono',
|
|
382
|
+
size: 's',
|
|
383
|
+
textAlign: 'start',
|
|
384
|
+
w: 250,
|
|
385
|
+
text: 'Rich text migration test',
|
|
386
|
+
scale: 0.8,
|
|
387
|
+
autoSize: false,
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const result = up(oldRecord)
|
|
392
|
+
expect(result.props.richText).toBeDefined()
|
|
393
|
+
expect(result.props.text).toBeUndefined()
|
|
394
|
+
expect(result.props.color).toBe('green')
|
|
395
|
+
expect(result.props.font).toBe('mono')
|
|
396
|
+
expect(result.props.size).toBe('s')
|
|
397
|
+
expect(result.props.textAlign).toBe('start')
|
|
398
|
+
expect(result.props.w).toBe(250)
|
|
399
|
+
expect(result.props.scale).toBe(0.8)
|
|
400
|
+
expect(result.props.autoSize).toBe(false)
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
// Note: The down migration is explicitly not defined (forced client update)
|
|
405
|
+
// so we don't test it
|
|
406
|
+
})
|
|
407
|
+
})
|
|
@@ -8,7 +8,25 @@ import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'
|
|
|
8
8
|
import { DefaultTextAlignStyle, TLDefaultTextAlignStyle } from '../styles/TLTextAlignStyle'
|
|
9
9
|
import { TLBaseShape } from './TLBaseShape'
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Configuration interface defining properties for text shapes in tldraw.
|
|
13
|
+
* Text shapes support rich formatting, styling, and automatic sizing.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const textProps: TLTextShapeProps = {
|
|
19
|
+
* color: 'black',
|
|
20
|
+
* size: 'm',
|
|
21
|
+
* font: 'draw',
|
|
22
|
+
* textAlign: 'start',
|
|
23
|
+
* w: 200,
|
|
24
|
+
* richText: toRichText('Hello **bold** text'),
|
|
25
|
+
* scale: 1,
|
|
26
|
+
* autoSize: true
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
12
30
|
export interface TLTextShapeProps {
|
|
13
31
|
color: TLDefaultColorStyle
|
|
14
32
|
size: TLDefaultSizeStyle
|
|
@@ -20,10 +38,57 @@ export interface TLTextShapeProps {
|
|
|
20
38
|
autoSize: boolean
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* A text shape that can display formatted text content with various styling options.
|
|
43
|
+
* Text shapes support rich formatting, automatic sizing, and consistent styling through
|
|
44
|
+
* the tldraw style system.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const textShape: TLTextShape = {
|
|
50
|
+
* id: 'shape:text123',
|
|
51
|
+
* typeName: 'shape',
|
|
52
|
+
* type: 'text',
|
|
53
|
+
* x: 100,
|
|
54
|
+
* y: 200,
|
|
55
|
+
* rotation: 0,
|
|
56
|
+
* index: 'a1',
|
|
57
|
+
* parentId: 'page:main',
|
|
58
|
+
* isLocked: false,
|
|
59
|
+
* opacity: 1,
|
|
60
|
+
* props: {
|
|
61
|
+
* color: 'black',
|
|
62
|
+
* size: 'm',
|
|
63
|
+
* font: 'draw',
|
|
64
|
+
* textAlign: 'start',
|
|
65
|
+
* w: 200,
|
|
66
|
+
* richText: toRichText('Sample text'),
|
|
67
|
+
* scale: 1,
|
|
68
|
+
* autoSize: false
|
|
69
|
+
* },
|
|
70
|
+
* meta: {}
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
24
74
|
export type TLTextShape = TLBaseShape<'text', TLTextShapeProps>
|
|
25
75
|
|
|
26
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Validation schema for text shape properties. This defines the runtime validation
|
|
78
|
+
* rules that ensure text shape data integrity when records are stored or transmitted.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* import { textShapeProps } from '@tldraw/tlschema'
|
|
84
|
+
*
|
|
85
|
+
* // Validate text shape properties
|
|
86
|
+
* const isValid = textShapeProps.richText.isValid(myRichText)
|
|
87
|
+
* if (isValid) {
|
|
88
|
+
* // Properties are valid, safe to use
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
27
92
|
export const textShapeProps: RecordProps<TLTextShape> = {
|
|
28
93
|
color: DefaultColorStyle,
|
|
29
94
|
size: DefaultSizeStyle,
|
|
@@ -41,9 +106,34 @@ const Versions = createShapePropsMigrationIds('text', {
|
|
|
41
106
|
AddRichText: 3,
|
|
42
107
|
})
|
|
43
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Version identifiers for text shape migrations. These constants track
|
|
111
|
+
* the evolution of the text shape schema over time.
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { textShapeVersions } from '@tldraw/tlschema'
|
|
117
|
+
*
|
|
118
|
+
* // Check if shape data needs migration
|
|
119
|
+
* if (shapeVersion < textShapeVersions.AddRichText) {
|
|
120
|
+
* // Apply rich text migration
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
44
124
|
export { Versions as textShapeVersions }
|
|
45
125
|
|
|
46
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Migration sequence for text shape schema evolution. This handles transforming
|
|
128
|
+
* text shape data between different versions as the schema evolves over time.
|
|
129
|
+
*
|
|
130
|
+
* Key migrations include:
|
|
131
|
+
* - RemoveJustify: Replaced 'justify' alignment with 'start'
|
|
132
|
+
* - AddTextAlign: Migrated from 'align' to 'textAlign' property
|
|
133
|
+
* - AddRichText: Converted plain text to rich text format
|
|
134
|
+
*
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
47
137
|
export const textShapeMigrations = createShapePropsMigrationSequence({
|
|
48
138
|
sequence: [
|
|
49
139
|
{
|