@tldraw/tlschema 4.1.0-canary.e653ec63c99b → 4.1.0-canary.e87046ba1a0c
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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { T } from '@tldraw/validate'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { StyleProp } from '../styles/StyleProp'
|
|
4
|
+
import {
|
|
5
|
+
createShapeId,
|
|
6
|
+
createShapePropsMigrationIds,
|
|
7
|
+
createShapeRecordType,
|
|
8
|
+
getShapePropKeysByStyle,
|
|
9
|
+
isShape,
|
|
10
|
+
isShapeId,
|
|
11
|
+
rootShapeMigrations,
|
|
12
|
+
rootShapeVersions,
|
|
13
|
+
TLShapeId,
|
|
14
|
+
} from './TLShape'
|
|
15
|
+
|
|
16
|
+
describe('rootShapeMigrations', () => {
|
|
17
|
+
it('should migrate AddIsLocked correctly', () => {
|
|
18
|
+
const migration = rootShapeMigrations.sequence.find(
|
|
19
|
+
(m) => m.id === rootShapeVersions.AddIsLocked
|
|
20
|
+
)!
|
|
21
|
+
expect(migration.up).toBeDefined()
|
|
22
|
+
expect(migration.down).toBeDefined()
|
|
23
|
+
|
|
24
|
+
const record: any = { id: 'shape:test', typeName: 'shape', type: 'geo' }
|
|
25
|
+
migration.up(record)
|
|
26
|
+
expect(record.isLocked).toBe(false)
|
|
27
|
+
|
|
28
|
+
migration.down!(record)
|
|
29
|
+
expect(record.isLocked).toBeUndefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should migrate HoistOpacity correctly', () => {
|
|
33
|
+
const migration = rootShapeMigrations.sequence.find(
|
|
34
|
+
(m) => m.id === rootShapeVersions.HoistOpacity
|
|
35
|
+
)!
|
|
36
|
+
expect(migration.up).toBeDefined()
|
|
37
|
+
expect(migration.down).toBeDefined()
|
|
38
|
+
|
|
39
|
+
// Test up migration
|
|
40
|
+
const record: any = {
|
|
41
|
+
id: 'shape:test',
|
|
42
|
+
typeName: 'shape',
|
|
43
|
+
type: 'geo',
|
|
44
|
+
props: { opacity: '0.5', color: 'red' },
|
|
45
|
+
}
|
|
46
|
+
migration.up(record)
|
|
47
|
+
expect(record.opacity).toBe(0.5)
|
|
48
|
+
expect(record.props.opacity).toBeUndefined()
|
|
49
|
+
expect(record.props.color).toBe('red')
|
|
50
|
+
|
|
51
|
+
// Test down migration
|
|
52
|
+
migration.down!(record)
|
|
53
|
+
expect(record.props.opacity).toBe('0.5')
|
|
54
|
+
expect(record.opacity).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should migrate AddMeta correctly', () => {
|
|
58
|
+
const migration = rootShapeMigrations.sequence.find((m) => m.id === rootShapeVersions.AddMeta)!
|
|
59
|
+
const record: any = { id: 'shape:test', typeName: 'shape', type: 'geo' }
|
|
60
|
+
migration.up(record)
|
|
61
|
+
expect(record.meta).toEqual({})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should handle AddWhite migration', () => {
|
|
65
|
+
const migration = rootShapeMigrations.sequence.find((m) => m.id === rootShapeVersions.AddWhite)!
|
|
66
|
+
expect(migration.up).toBeDefined()
|
|
67
|
+
expect(migration.down).toBeDefined()
|
|
68
|
+
|
|
69
|
+
// Up migration is noop
|
|
70
|
+
const record: any = { props: { color: 'white' } }
|
|
71
|
+
const original = { ...record }
|
|
72
|
+
migration.up(record)
|
|
73
|
+
expect(record).toEqual(original)
|
|
74
|
+
|
|
75
|
+
// Down migration converts white to black
|
|
76
|
+
migration.down!(record)
|
|
77
|
+
expect(record.props.color).toBe('black')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('isShape', () => {
|
|
82
|
+
it('should return true for shape records', () => {
|
|
83
|
+
const shape = {
|
|
84
|
+
id: 'shape:test' as TLShapeId,
|
|
85
|
+
typeName: 'shape',
|
|
86
|
+
type: 'geo',
|
|
87
|
+
x: 0,
|
|
88
|
+
y: 0,
|
|
89
|
+
rotation: 0,
|
|
90
|
+
index: 'a1' as any,
|
|
91
|
+
parentId: 'page:main' as any,
|
|
92
|
+
isLocked: false,
|
|
93
|
+
opacity: 1,
|
|
94
|
+
props: {},
|
|
95
|
+
meta: {},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
expect(isShape(shape)).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should return false for non-shape records', () => {
|
|
102
|
+
const notShape = {
|
|
103
|
+
id: 'page:test',
|
|
104
|
+
typeName: 'page',
|
|
105
|
+
name: 'Test Page',
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
expect(isShape(notShape as any)).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('isShapeId', () => {
|
|
113
|
+
it('should return true for valid shape IDs', () => {
|
|
114
|
+
expect(isShapeId('shape:test')).toBe(true)
|
|
115
|
+
expect(isShapeId('shape:abc123')).toBe(true)
|
|
116
|
+
expect(isShapeId('shape:')).toBe(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should return false for invalid shape IDs', () => {
|
|
120
|
+
expect(isShapeId('page:test')).toBe(false)
|
|
121
|
+
expect(isShapeId('asset:test')).toBe(false)
|
|
122
|
+
expect(isShapeId('invalid')).toBe(false)
|
|
123
|
+
expect(isShapeId('')).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('createShapeId', () => {
|
|
128
|
+
it('should create shape IDs with auto-generated suffix', () => {
|
|
129
|
+
const id1 = createShapeId()
|
|
130
|
+
const id2 = createShapeId()
|
|
131
|
+
|
|
132
|
+
expect(id1.startsWith('shape:')).toBe(true)
|
|
133
|
+
expect(id2.startsWith('shape:')).toBe(true)
|
|
134
|
+
expect(id1).not.toBe(id2)
|
|
135
|
+
expect(id1.length).toBeGreaterThan(6) // 'shape:' + some ID
|
|
136
|
+
expect(id2.length).toBeGreaterThan(6)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should create shape IDs with custom suffix', () => {
|
|
140
|
+
const customId = createShapeId('my-custom-id')
|
|
141
|
+
expect(customId).toBe('shape:my-custom-id')
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('getShapePropKeysByStyle', () => {
|
|
146
|
+
it('should map style props to their keys', () => {
|
|
147
|
+
const colorStyle = StyleProp.define('color', { defaultValue: 'black' })
|
|
148
|
+
const sizeStyle = StyleProp.define('size', { defaultValue: 'm' })
|
|
149
|
+
|
|
150
|
+
const props = {
|
|
151
|
+
color: colorStyle,
|
|
152
|
+
size: sizeStyle,
|
|
153
|
+
width: T.number,
|
|
154
|
+
height: T.number,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const styleMap = getShapePropKeysByStyle(props)
|
|
158
|
+
expect(styleMap.get(colorStyle)).toBe('color')
|
|
159
|
+
expect(styleMap.get(sizeStyle)).toBe('size')
|
|
160
|
+
expect(styleMap.size).toBe(2) // Only style props
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should throw error for duplicate style props', () => {
|
|
164
|
+
const colorStyle = StyleProp.define('color', { defaultValue: 'black' })
|
|
165
|
+
const props = {
|
|
166
|
+
color1: colorStyle,
|
|
167
|
+
color2: colorStyle, // Same style prop used twice
|
|
168
|
+
width: T.number,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
expect(() => getShapePropKeysByStyle(props)).toThrow('Duplicate style prop')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('createShapePropsMigrationIds', () => {
|
|
176
|
+
it('should create formatted migration IDs', () => {
|
|
177
|
+
const ids = createShapePropsMigrationIds('custom', {
|
|
178
|
+
AddColor: 1,
|
|
179
|
+
AddSize: 2,
|
|
180
|
+
RefactorProps: 3,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
expect(ids.AddColor).toBe('com.tldraw.shape.custom/1')
|
|
184
|
+
expect(ids.AddSize).toBe('com.tldraw.shape.custom/2')
|
|
185
|
+
expect(ids.RefactorProps).toBe('com.tldraw.shape.custom/3')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('createShapeRecordType', () => {
|
|
190
|
+
it('should create a record type for shapes', () => {
|
|
191
|
+
const shapes = {
|
|
192
|
+
geo: {
|
|
193
|
+
props: {
|
|
194
|
+
w: T.number,
|
|
195
|
+
h: T.number,
|
|
196
|
+
color: StyleProp.define('color', { defaultValue: 'black' }),
|
|
197
|
+
},
|
|
198
|
+
meta: {},
|
|
199
|
+
},
|
|
200
|
+
text: {
|
|
201
|
+
props: {
|
|
202
|
+
text: T.string,
|
|
203
|
+
size: StyleProp.define('size', { defaultValue: 'm' }),
|
|
204
|
+
},
|
|
205
|
+
meta: {},
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ShapeRecordType = createShapeRecordType(shapes)
|
|
210
|
+
|
|
211
|
+
expect(ShapeRecordType.typeName).toBe('shape')
|
|
212
|
+
expect(ShapeRecordType.scope).toBe('document')
|
|
213
|
+
|
|
214
|
+
// Should be able to create shapes
|
|
215
|
+
const geoShape = ShapeRecordType.create({
|
|
216
|
+
id: createShapeId(),
|
|
217
|
+
type: 'geo',
|
|
218
|
+
parentId: 'page:main' as any,
|
|
219
|
+
index: 'a1' as any,
|
|
220
|
+
props: {},
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
expect(geoShape.typeName).toBe('shape')
|
|
224
|
+
expect(geoShape.type).toBe('geo')
|
|
225
|
+
expect(geoShape.x).toBe(0) // Default
|
|
226
|
+
expect(geoShape.y).toBe(0) // Default
|
|
227
|
+
expect(geoShape.rotation).toBe(0) // Default
|
|
228
|
+
expect(geoShape.isLocked).toBe(false) // Default
|
|
229
|
+
expect(geoShape.opacity).toBe(1) // Default
|
|
230
|
+
expect(geoShape.meta).toEqual({}) // Default
|
|
231
|
+
})
|
|
232
|
+
})
|
package/src/records/TLShape.ts
CHANGED
|
@@ -29,7 +29,21 @@ import { TLPageId } from './TLPage'
|
|
|
29
29
|
/**
|
|
30
30
|
* The default set of shapes that are available in the editor.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
32
|
+
* This union type represents all the built-in shape types supported by tldraw,
|
|
33
|
+
* including arrows, bookmarks, drawings, embeds, frames, geometry shapes,
|
|
34
|
+
* groups, images, lines, notes, text, videos, and highlights.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* // Check if a shape is a default shape type
|
|
39
|
+
* function isDefaultShape(shape: TLShape): shape is TLDefaultShape {
|
|
40
|
+
* const defaultTypes = ['arrow', 'bookmark', 'draw', 'embed', 'frame', 'geo', 'group', 'image', 'line', 'note', 'text', 'video', 'highlight']
|
|
41
|
+
* return defaultTypes.includes(shape.type)
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
33
47
|
export type TLDefaultShape =
|
|
34
48
|
| TLArrowShape
|
|
35
49
|
| TLBookmarkShape
|
|
@@ -49,17 +63,73 @@ export type TLDefaultShape =
|
|
|
49
63
|
* A type for a shape that is available in the editor but whose type is
|
|
50
64
|
* unknown—either one of the editor's default shapes or else a custom shape.
|
|
51
65
|
*
|
|
52
|
-
*
|
|
66
|
+
* This is useful when working with shapes generically without knowing their specific type.
|
|
67
|
+
* The shape type is a string and props are a generic object.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* // Handle any shape regardless of its specific type
|
|
72
|
+
* function processUnknownShape(shape: TLUnknownShape) {
|
|
73
|
+
* console.log(`Processing shape of type: ${shape.type}`)
|
|
74
|
+
* console.log(`Position: (${shape.x}, ${shape.y})`)
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
53
80
|
export type TLUnknownShape = TLBaseShape<string, object>
|
|
54
81
|
|
|
55
82
|
/**
|
|
56
83
|
* The set of all shapes that are available in the editor, including unknown shapes.
|
|
57
84
|
*
|
|
85
|
+
* This is the primary shape type used throughout tldraw. It includes both the
|
|
86
|
+
* built-in default shapes and any custom shapes that might be added.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Work with any shape in the editor
|
|
91
|
+
* function moveShape(shape: TLShape, deltaX: number, deltaY: number): TLShape {
|
|
92
|
+
* return {
|
|
93
|
+
* ...shape,
|
|
94
|
+
* x: shape.x + deltaX,
|
|
95
|
+
* y: shape.y + deltaY
|
|
96
|
+
* }
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
58
100
|
* @public
|
|
59
101
|
*/
|
|
60
102
|
export type TLShape = TLDefaultShape | TLUnknownShape
|
|
61
103
|
|
|
62
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* A partial version of a shape, useful for updates and patches.
|
|
106
|
+
*
|
|
107
|
+
* This type represents a shape where all properties except `id` and `type` are optional.
|
|
108
|
+
* It's commonly used when updating existing shapes or creating shape patches.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // Update a shape's position
|
|
113
|
+
* const shapeUpdate: TLShapePartial = {
|
|
114
|
+
* id: 'shape:123',
|
|
115
|
+
* type: 'geo',
|
|
116
|
+
* x: 100,
|
|
117
|
+
* y: 200
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* // Update shape properties
|
|
121
|
+
* const propsUpdate: TLShapePartial<TLGeoShape> = {
|
|
122
|
+
* id: 'shape:123',
|
|
123
|
+
* type: 'geo',
|
|
124
|
+
* props: {
|
|
125
|
+
* w: 150,
|
|
126
|
+
* h: 100
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @public
|
|
132
|
+
*/
|
|
63
133
|
export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
|
64
134
|
? {
|
|
65
135
|
id: TLShapeId
|
|
@@ -69,13 +139,57 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
|
|
69
139
|
} & Partial<Omit<T, 'type' | 'id' | 'props' | 'meta'>>
|
|
70
140
|
: never
|
|
71
141
|
|
|
72
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* A unique identifier for a shape record.
|
|
144
|
+
*
|
|
145
|
+
* Shape IDs are branded strings that start with "shape:" followed by a unique identifier.
|
|
146
|
+
* This type-safe approach prevents mixing up different types of record IDs.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* const shapeId: TLShapeId = createShapeId() // "shape:abc123"
|
|
151
|
+
* const customId: TLShapeId = createShapeId('my-custom-id') // "shape:my-custom-id"
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @public
|
|
155
|
+
*/
|
|
73
156
|
export type TLShapeId = RecordId<TLUnknownShape>
|
|
74
157
|
|
|
75
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* The ID of a shape's parent, which can be either a page or another shape.
|
|
160
|
+
*
|
|
161
|
+
* Shapes can be parented to pages (for top-level shapes) or to other shapes
|
|
162
|
+
* (for shapes inside frames or groups).
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* // Shape parented to a page
|
|
167
|
+
* const pageParentId: TLParentId = 'page:main'
|
|
168
|
+
*
|
|
169
|
+
* // Shape parented to another shape (e.g., inside a frame)
|
|
170
|
+
* const shapeParentId: TLParentId = 'shape:frame123'
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
76
175
|
export type TLParentId = TLPageId | TLShapeId
|
|
77
176
|
|
|
78
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Migration version IDs for the root shape schema.
|
|
179
|
+
*
|
|
180
|
+
* These track the evolution of the base shape structure over time, ensuring
|
|
181
|
+
* that shapes created in older versions can be migrated to newer formats.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* // Check if a migration needs to be applied
|
|
186
|
+
* if (shapeVersion < rootShapeVersions.AddIsLocked) {
|
|
187
|
+
* // Apply isLocked migration
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
79
193
|
export const rootShapeVersions = createMigrationIds('com.tldraw.shape', {
|
|
80
194
|
AddIsLocked: 1,
|
|
81
195
|
HoistOpacity: 2,
|
|
@@ -83,7 +197,15 @@ export const rootShapeVersions = createMigrationIds('com.tldraw.shape', {
|
|
|
83
197
|
AddWhite: 4,
|
|
84
198
|
} as const)
|
|
85
199
|
|
|
86
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Migration sequence for the root shape record type.
|
|
202
|
+
*
|
|
203
|
+
* This sequence defines how shape records should be transformed when migrating
|
|
204
|
+
* between different schema versions. Each migration handles a specific version
|
|
205
|
+
* upgrade, ensuring data compatibility across tldraw versions.
|
|
206
|
+
*
|
|
207
|
+
* @public
|
|
208
|
+
*/
|
|
87
209
|
export const rootShapeMigrations = createRecordMigrationSequence({
|
|
88
210
|
sequenceId: 'com.tldraw.shape',
|
|
89
211
|
recordType: 'shape',
|
|
@@ -138,24 +260,115 @@ export const rootShapeMigrations = createRecordMigrationSequence({
|
|
|
138
260
|
],
|
|
139
261
|
})
|
|
140
262
|
|
|
141
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Type guard to check if a record is a shape.
|
|
265
|
+
*
|
|
266
|
+
* @param record - The record to check
|
|
267
|
+
* @returns True if the record is a shape, false otherwise
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const record = store.get('shape:abc123')
|
|
272
|
+
* if (isShape(record)) {
|
|
273
|
+
* console.log(`Shape type: ${record.type}`)
|
|
274
|
+
* console.log(`Position: (${record.x}, ${record.y})`)
|
|
275
|
+
* }
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* @public
|
|
279
|
+
*/
|
|
142
280
|
export function isShape(record?: UnknownRecord): record is TLShape {
|
|
143
281
|
if (!record) return false
|
|
144
282
|
return record.typeName === 'shape'
|
|
145
283
|
}
|
|
146
284
|
|
|
147
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* Type guard to check if a string is a valid shape ID.
|
|
287
|
+
*
|
|
288
|
+
* @param id - The string to check
|
|
289
|
+
* @returns True if the string is a valid shape ID, false otherwise
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const id = 'shape:abc123'
|
|
294
|
+
* if (isShapeId(id)) {
|
|
295
|
+
* const shape = store.get(id) // TypeScript knows id is TLShapeId
|
|
296
|
+
* }
|
|
297
|
+
*
|
|
298
|
+
* // Check user input
|
|
299
|
+
* function selectShape(id: string) {
|
|
300
|
+
* if (isShapeId(id)) {
|
|
301
|
+
* editor.selectShape(id)
|
|
302
|
+
* } else {
|
|
303
|
+
* console.error('Invalid shape ID format')
|
|
304
|
+
* }
|
|
305
|
+
* }
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @public
|
|
309
|
+
*/
|
|
148
310
|
export function isShapeId(id?: string): id is TLShapeId {
|
|
149
311
|
if (!id) return false
|
|
150
312
|
return id.startsWith('shape:')
|
|
151
313
|
}
|
|
152
314
|
|
|
153
|
-
/**
|
|
315
|
+
/**
|
|
316
|
+
* Creates a new shape ID.
|
|
317
|
+
*
|
|
318
|
+
* @param id - Optional custom ID suffix. If not provided, a unique ID will be generated
|
|
319
|
+
* @returns A new shape ID with the "shape:" prefix
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* // Create a shape with auto-generated ID
|
|
324
|
+
* const shapeId = createShapeId() // "shape:abc123"
|
|
325
|
+
*
|
|
326
|
+
* // Create a shape with custom ID
|
|
327
|
+
* const customShapeId = createShapeId('my-rectangle') // "shape:my-rectangle"
|
|
328
|
+
*
|
|
329
|
+
* // Use in shape creation
|
|
330
|
+
* const newShape: TLGeoShape = {
|
|
331
|
+
* id: createShapeId(),
|
|
332
|
+
* type: 'geo',
|
|
333
|
+
* x: 100,
|
|
334
|
+
* y: 200,
|
|
335
|
+
* // ... other properties
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
339
|
+
* @public
|
|
340
|
+
*/
|
|
154
341
|
export function createShapeId(id?: string): TLShapeId {
|
|
155
342
|
return `shape:${id ?? uniqueId()}` as TLShapeId
|
|
156
343
|
}
|
|
157
344
|
|
|
158
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* Extracts style properties from a shape's props definition and maps them to their property keys.
|
|
347
|
+
*
|
|
348
|
+
* This function analyzes shape property validators to identify which ones are style properties
|
|
349
|
+
* and creates a mapping from StyleProp instances to their corresponding property keys.
|
|
350
|
+
* It also validates that each style property is only used once per shape.
|
|
351
|
+
*
|
|
352
|
+
* @param props - Record of property validators for a shape type
|
|
353
|
+
* @returns Map from StyleProp instances to their property keys
|
|
354
|
+
* @throws Error if a style property is used more than once in the same shape
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* const geoShapeProps = {
|
|
359
|
+
* color: DefaultColorStyle,
|
|
360
|
+
* fill: DefaultFillStyle,
|
|
361
|
+
* width: T.number,
|
|
362
|
+
* height: T.number
|
|
363
|
+
* }
|
|
364
|
+
*
|
|
365
|
+
* const styleMap = getShapePropKeysByStyle(geoShapeProps)
|
|
366
|
+
* // styleMap.get(DefaultColorStyle) === 'color'
|
|
367
|
+
* // styleMap.get(DefaultFillStyle) === 'fill'
|
|
368
|
+
* ```
|
|
369
|
+
*
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
159
372
|
export function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>>) {
|
|
160
373
|
const propKeysByStyle = new Map<StyleProp<unknown>, string>()
|
|
161
374
|
for (const [key, prop] of Object.entries(props)) {
|
|
@@ -172,6 +385,27 @@ export function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>
|
|
|
172
385
|
}
|
|
173
386
|
|
|
174
387
|
/**
|
|
388
|
+
* Creates a migration sequence for shape properties.
|
|
389
|
+
*
|
|
390
|
+
* This is a pass-through function that maintains the same structure as the input.
|
|
391
|
+
* It's used for consistency and to provide a clear API for defining shape property migrations.
|
|
392
|
+
*
|
|
393
|
+
* @param migrations - The migration sequence to create
|
|
394
|
+
* @returns The same migration sequence (pass-through)
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```ts
|
|
398
|
+
* const myShapeMigrations = createShapePropsMigrationSequence({
|
|
399
|
+
* sequence: [
|
|
400
|
+
* {
|
|
401
|
+
* id: 'com.myapp.shape.custom/1.0.0',
|
|
402
|
+
* up: (props) => ({ ...props, newProperty: 'default' }),
|
|
403
|
+
* down: ({ newProperty, ...props }) => props
|
|
404
|
+
* }
|
|
405
|
+
* ]
|
|
406
|
+
* })
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
175
409
|
* @public
|
|
176
410
|
*/
|
|
177
411
|
export function createShapePropsMigrationSequence(
|
|
@@ -181,6 +415,29 @@ export function createShapePropsMigrationSequence(
|
|
|
181
415
|
}
|
|
182
416
|
|
|
183
417
|
/**
|
|
418
|
+
* Creates properly formatted migration IDs for shape properties.
|
|
419
|
+
*
|
|
420
|
+
* Generates standardized migration IDs following the convention:
|
|
421
|
+
* `com.tldraw.shape.{shapeType}/{version}`
|
|
422
|
+
*
|
|
423
|
+
* @param shapeType - The type of shape these migrations apply to
|
|
424
|
+
* @param ids - Record mapping migration names to version numbers
|
|
425
|
+
* @returns Record with the same keys but formatted migration ID values
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const myShapeVersions = createShapePropsMigrationIds('custom', {
|
|
430
|
+
* AddColor: 1,
|
|
431
|
+
* AddSize: 2,
|
|
432
|
+
* RefactorProps: 3
|
|
433
|
+
* })
|
|
434
|
+
* // Result: {
|
|
435
|
+
* // AddColor: 'com.tldraw.shape.custom/1',
|
|
436
|
+
* // AddSize: 'com.tldraw.shape.custom/2',
|
|
437
|
+
* // RefactorProps: 'com.tldraw.shape.custom/3'
|
|
438
|
+
* // }
|
|
439
|
+
* ```
|
|
440
|
+
*
|
|
184
441
|
* @public
|
|
185
442
|
*/
|
|
186
443
|
export function createShapePropsMigrationIds<
|
|
@@ -190,7 +447,27 @@ export function createShapePropsMigrationIds<
|
|
|
190
447
|
return mapObjectMapValues(ids, (_k, v) => `com.tldraw.shape.${shapeType}/${v}`) as any
|
|
191
448
|
}
|
|
192
449
|
|
|
193
|
-
/**
|
|
450
|
+
/**
|
|
451
|
+
* Creates the record type definition for shapes.
|
|
452
|
+
*
|
|
453
|
+
* This function generates a complete record type for shapes that includes validation
|
|
454
|
+
* for all registered shape types. It combines the base shape properties with
|
|
455
|
+
* type-specific properties and creates a union validator that can handle any
|
|
456
|
+
* registered shape type.
|
|
457
|
+
*
|
|
458
|
+
* @param shapes - Record of shape type names to their schema configuration
|
|
459
|
+
* @returns A complete RecordType for shapes with proper validation and default properties
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```ts
|
|
463
|
+
* const shapeRecordType = createShapeRecordType({
|
|
464
|
+
* geo: { props: geoShapeProps, migrations: geoMigrations },
|
|
465
|
+
* arrow: { props: arrowShapeProps, migrations: arrowMigrations }
|
|
466
|
+
* })
|
|
467
|
+
* ```
|
|
468
|
+
*
|
|
469
|
+
* @internal
|
|
470
|
+
*/
|
|
194
471
|
export function createShapeRecordType(shapes: Record<string, SchemaPropsInfo>) {
|
|
195
472
|
return createRecordType<TLShape>('shape', {
|
|
196
473
|
scope: 'document',
|