@tldraw/tlschema 4.1.0-canary.bdf9b3703a3d → 4.1.0-canary.bf8b596d2b31
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.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.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 -4
- 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
|
@@ -245,17 +245,60 @@ const EMBED_DEFINITIONS = [
|
|
|
245
245
|
},
|
|
246
246
|
]
|
|
247
247
|
|
|
248
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Properties for the embed shape, which displays embedded content from external services.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
249
253
|
export interface TLEmbedShapeProps {
|
|
254
|
+
/** Width of the embed shape in pixels */
|
|
250
255
|
w: number
|
|
256
|
+
/** Height of the embed shape in pixels */
|
|
251
257
|
h: number
|
|
258
|
+
/** URL of the content to embed (supports YouTube, Figma, CodePen, etc.) */
|
|
252
259
|
url: string
|
|
253
260
|
}
|
|
254
261
|
|
|
255
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* An embed shape displays external content like YouTube videos, Figma designs, CodePen demos,
|
|
264
|
+
* and other embeddable content within the tldraw canvas.
|
|
265
|
+
*
|
|
266
|
+
* @public
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const embedShape: TLEmbedShape = {
|
|
270
|
+
* id: createShapeId(),
|
|
271
|
+
* typeName: 'shape',
|
|
272
|
+
* type: 'embed',
|
|
273
|
+
* x: 200,
|
|
274
|
+
* y: 200,
|
|
275
|
+
* rotation: 0,
|
|
276
|
+
* index: 'a1',
|
|
277
|
+
* parentId: 'page:page1',
|
|
278
|
+
* isLocked: false,
|
|
279
|
+
* opacity: 1,
|
|
280
|
+
* props: {
|
|
281
|
+
* w: 560,
|
|
282
|
+
* h: 315,
|
|
283
|
+
* url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
|
284
|
+
* },
|
|
285
|
+
* meta: {}
|
|
286
|
+
* }
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
256
289
|
export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps>
|
|
257
290
|
|
|
258
|
-
/**
|
|
291
|
+
/**
|
|
292
|
+
* Validation schema for embed shape properties.
|
|
293
|
+
*
|
|
294
|
+
* @public
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* // Validate embed shape properties
|
|
298
|
+
* const isValidUrl = embedShapeProps.url.isValid('https://youtube.com/watch?v=abc123')
|
|
299
|
+
* const isValidSize = embedShapeProps.w.isValid(560)
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
259
302
|
export const embedShapeProps: RecordProps<TLEmbedShape> = {
|
|
260
303
|
w: T.nonZeroNumber,
|
|
261
304
|
h: T.nonZeroNumber,
|
|
@@ -269,9 +312,19 @@ const Versions = createShapePropsMigrationIds('embed', {
|
|
|
269
312
|
RemovePermissionOverrides: 4,
|
|
270
313
|
})
|
|
271
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Version identifiers for embed shape migrations.
|
|
317
|
+
*
|
|
318
|
+
* @public
|
|
319
|
+
*/
|
|
272
320
|
export { Versions as embedShapeVersions }
|
|
273
321
|
|
|
274
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Migration sequence for embed shape properties across different schema versions.
|
|
324
|
+
* Handles URL transformations and removal of deprecated properties.
|
|
325
|
+
*
|
|
326
|
+
* @public
|
|
327
|
+
*/
|
|
275
328
|
export const embedShapeMigrations = createShapePropsMigrationSequence({
|
|
276
329
|
sequence: [
|
|
277
330
|
{
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { T } from '@tldraw/validate'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
4
|
+
import { frameShapeProps, frameShapeVersions } from './TLFrameShape'
|
|
5
|
+
|
|
6
|
+
describe('TLFrameShape', () => {
|
|
7
|
+
describe('frameShapeProps validation', () => {
|
|
8
|
+
it('should validate valid frame props', () => {
|
|
9
|
+
const validProps = {
|
|
10
|
+
w: 400,
|
|
11
|
+
h: 300,
|
|
12
|
+
name: 'Test Frame',
|
|
13
|
+
color: 'blue' as const,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const validator = T.object(frameShapeProps)
|
|
17
|
+
expect(() => validator.validate(validProps)).not.toThrow()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should reject invalid dimensions', () => {
|
|
21
|
+
// Zero and negative values should be rejected
|
|
22
|
+
expect(() => frameShapeProps.w.validate(0)).toThrow()
|
|
23
|
+
expect(() => frameShapeProps.h.validate(-1)).toThrow()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should reject invalid colors', () => {
|
|
27
|
+
// Invalid color values
|
|
28
|
+
expect(() => frameShapeProps.color.validate('invalid-color')).toThrow()
|
|
29
|
+
expect(() => frameShapeProps.color.validate('')).toThrow()
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('AddColorProp migration', () => {
|
|
34
|
+
const { up, down } = getTestMigration(frameShapeVersions.AddColorProp)
|
|
35
|
+
|
|
36
|
+
it('should add color property with default value "black"', () => {
|
|
37
|
+
const oldRecord = {
|
|
38
|
+
id: 'shape:frame1',
|
|
39
|
+
props: {
|
|
40
|
+
w: 400,
|
|
41
|
+
h: 300,
|
|
42
|
+
name: 'Test Frame',
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = up(oldRecord)
|
|
47
|
+
expect(result.props.color).toBe('black')
|
|
48
|
+
expect(result.props.w).toBe(400)
|
|
49
|
+
expect(result.props.h).toBe(300)
|
|
50
|
+
expect(result.props.name).toBe('Test Frame')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should remove color property on down migration', () => {
|
|
54
|
+
const newRecord = {
|
|
55
|
+
id: 'shape:frame1',
|
|
56
|
+
props: {
|
|
57
|
+
w: 400,
|
|
58
|
+
h: 300,
|
|
59
|
+
name: 'Test Frame',
|
|
60
|
+
color: 'blue',
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = down(newRecord)
|
|
65
|
+
expect(result.props.color).toBeUndefined()
|
|
66
|
+
expect(result.props.w).toBe(400)
|
|
67
|
+
expect(result.props.h).toBe(300)
|
|
68
|
+
expect(result.props.name).toBe('Test Frame')
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -4,18 +4,63 @@ import { RecordProps } from '../recordsWithProps'
|
|
|
4
4
|
import { DefaultColorStyle, TLDefaultColorStyle } from '../styles/TLColorStyle'
|
|
5
5
|
import { TLBaseShape } from './TLBaseShape'
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Properties for the frame shape, which provides a container for organizing other shapes.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
8
12
|
export interface TLFrameShapeProps {
|
|
13
|
+
/** Width of the frame in pixels */
|
|
9
14
|
w: number
|
|
15
|
+
/** Height of the frame in pixels */
|
|
10
16
|
h: number
|
|
17
|
+
/** Display name for the frame (shown in UI) */
|
|
11
18
|
name: string
|
|
19
|
+
/** Color style for the frame border and label */
|
|
12
20
|
color: TLDefaultColorStyle
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* A frame shape provides a container for organizing and grouping other shapes.
|
|
25
|
+
* Frames can be used to create sections, organize content, or define specific areas.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const frameShape: TLFrameShape = {
|
|
31
|
+
* id: createShapeId(),
|
|
32
|
+
* typeName: 'shape',
|
|
33
|
+
* type: 'frame',
|
|
34
|
+
* x: 0,
|
|
35
|
+
* y: 0,
|
|
36
|
+
* rotation: 0,
|
|
37
|
+
* index: 'a1',
|
|
38
|
+
* parentId: 'page:page1',
|
|
39
|
+
* isLocked: false,
|
|
40
|
+
* opacity: 1,
|
|
41
|
+
* props: {
|
|
42
|
+
* w: 400,
|
|
43
|
+
* h: 300,
|
|
44
|
+
* name: 'Header Section',
|
|
45
|
+
* color: 'blue'
|
|
46
|
+
* },
|
|
47
|
+
* meta: {}
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
16
51
|
export type TLFrameShape = TLBaseShape<'frame', TLFrameShapeProps>
|
|
17
52
|
|
|
18
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Validation schema for frame shape properties.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* // Validate frame properties
|
|
60
|
+
* const isValidName = frameShapeProps.name.isValid('My Frame')
|
|
61
|
+
* const isValidColor = frameShapeProps.color.isValid('red')
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
19
64
|
export const frameShapeProps: RecordProps<TLFrameShape> = {
|
|
20
65
|
w: T.nonZeroNumber,
|
|
21
66
|
h: T.nonZeroNumber,
|
|
@@ -31,9 +76,19 @@ const Versions = createShapePropsMigrationIds('frame', {
|
|
|
31
76
|
AddColorProp: 1,
|
|
32
77
|
})
|
|
33
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Version identifiers for frame shape migrations.
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
34
84
|
export { Versions as frameShapeVersions }
|
|
35
85
|
|
|
36
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Migration sequence for frame shape properties across different schema versions.
|
|
88
|
+
* Handles adding color properties to existing frame shapes.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
37
92
|
export const frameShapeMigrations = createShapePropsMigrationSequence({
|
|
38
93
|
sequence: [
|
|
39
94
|
{
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { toRichText } from '../misc/TLRichText'
|
|
4
|
+
import { GeoShapeGeoStyle, geoShapeProps, geoShapeVersions } from './TLGeoShape'
|
|
5
|
+
|
|
6
|
+
describe('TLGeoShape', () => {
|
|
7
|
+
describe('GeoShapeGeoStyle', () => {
|
|
8
|
+
it('should validate all geometric shape types', () => {
|
|
9
|
+
const validGeoTypes = [
|
|
10
|
+
'cloud',
|
|
11
|
+
'rectangle',
|
|
12
|
+
'ellipse',
|
|
13
|
+
'triangle',
|
|
14
|
+
'diamond',
|
|
15
|
+
'pentagon',
|
|
16
|
+
'hexagon',
|
|
17
|
+
'octagon',
|
|
18
|
+
'star',
|
|
19
|
+
'rhombus',
|
|
20
|
+
'rhombus-2',
|
|
21
|
+
'oval',
|
|
22
|
+
'trapezoid',
|
|
23
|
+
'arrow-right',
|
|
24
|
+
'arrow-left',
|
|
25
|
+
'arrow-up',
|
|
26
|
+
'arrow-down',
|
|
27
|
+
'x-box',
|
|
28
|
+
'check-box',
|
|
29
|
+
'heart',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
validGeoTypes.forEach((geoType) => {
|
|
33
|
+
expect(() => GeoShapeGeoStyle.validate(geoType)).not.toThrow()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should reject invalid geometric shape types', () => {
|
|
38
|
+
const invalidGeoTypes = ['square', 'circle', 'invalid-shape', null, undefined]
|
|
39
|
+
|
|
40
|
+
invalidGeoTypes.forEach((geoType) => {
|
|
41
|
+
expect(() => GeoShapeGeoStyle.validate(geoType)).toThrow()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('geoShapeProps validation schema', () => {
|
|
47
|
+
it('should validate numeric constraints', () => {
|
|
48
|
+
// Test nonZeroNumber validation for w, h, scale
|
|
49
|
+
expect(() => geoShapeProps.w.validate(100)).not.toThrow()
|
|
50
|
+
expect(() => geoShapeProps.h.validate(50)).not.toThrow()
|
|
51
|
+
expect(() => geoShapeProps.scale.validate(1.5)).not.toThrow()
|
|
52
|
+
|
|
53
|
+
expect(() => geoShapeProps.w.validate(0)).toThrow()
|
|
54
|
+
expect(() => geoShapeProps.h.validate(0)).toThrow()
|
|
55
|
+
expect(() => geoShapeProps.scale.validate(0)).toThrow()
|
|
56
|
+
|
|
57
|
+
// Test positiveNumber validation for growY
|
|
58
|
+
expect(() => geoShapeProps.growY.validate(0)).not.toThrow()
|
|
59
|
+
expect(() => geoShapeProps.growY.validate(10)).not.toThrow()
|
|
60
|
+
expect(() => geoShapeProps.growY.validate(-1)).toThrow()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should validate rich text property', () => {
|
|
64
|
+
expect(() => geoShapeProps.richText.validate(toRichText('test'))).not.toThrow()
|
|
65
|
+
expect(() => geoShapeProps.richText.validate('plain string')).toThrow()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('geoShapeMigrations - AddUrlProp migration', () => {
|
|
70
|
+
const { up } = getTestMigration(geoShapeVersions.AddUrlProp)
|
|
71
|
+
|
|
72
|
+
it('should add url property with empty string default', () => {
|
|
73
|
+
const oldRecord = {
|
|
74
|
+
props: {
|
|
75
|
+
geo: 'rectangle',
|
|
76
|
+
w: 100,
|
|
77
|
+
h: 80,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = up(oldRecord)
|
|
82
|
+
expect(result.props.url).toBe('')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('geoShapeMigrations - AddLabelColor migration', () => {
|
|
87
|
+
const { up } = getTestMigration(geoShapeVersions.AddLabelColor)
|
|
88
|
+
|
|
89
|
+
it('should add labelColor property with default value "black"', () => {
|
|
90
|
+
const oldRecord = {
|
|
91
|
+
props: {
|
|
92
|
+
geo: 'triangle',
|
|
93
|
+
color: 'blue',
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = up(oldRecord)
|
|
98
|
+
expect(result.props.labelColor).toBe('black')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('geoShapeMigrations - RemoveJustify migration', () => {
|
|
103
|
+
const { up } = getTestMigration(geoShapeVersions.RemoveJustify)
|
|
104
|
+
|
|
105
|
+
it('should convert justify alignment to start', () => {
|
|
106
|
+
const oldRecord = {
|
|
107
|
+
props: {
|
|
108
|
+
align: 'justify',
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const result = up(oldRecord)
|
|
113
|
+
expect(result.props.align).toBe('start')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should preserve non-justify alignments', () => {
|
|
117
|
+
const oldRecord = {
|
|
118
|
+
props: {
|
|
119
|
+
align: 'middle',
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = up(oldRecord)
|
|
124
|
+
expect(result.props.align).toBe('middle')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('geoShapeMigrations - AddVerticalAlign migration', () => {
|
|
129
|
+
const { up } = getTestMigration(geoShapeVersions.AddVerticalAlign)
|
|
130
|
+
|
|
131
|
+
it('should add verticalAlign property with default value "middle"', () => {
|
|
132
|
+
const oldRecord = {
|
|
133
|
+
props: {
|
|
134
|
+
geo: 'rectangle',
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = up(oldRecord)
|
|
139
|
+
expect(result.props.verticalAlign).toBe('middle')
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('geoShapeMigrations - MigrateLegacyAlign migration', () => {
|
|
144
|
+
const { up } = getTestMigration(geoShapeVersions.MigrateLegacyAlign)
|
|
145
|
+
|
|
146
|
+
it('should convert alignment values to legacy versions', () => {
|
|
147
|
+
const testCases = [
|
|
148
|
+
{ input: 'start', expected: 'start-legacy' },
|
|
149
|
+
{ input: 'end', expected: 'end-legacy' },
|
|
150
|
+
{ input: 'middle', expected: 'middle-legacy' },
|
|
151
|
+
{ input: 'unknown-align', expected: 'middle-legacy' },
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
testCases.forEach(({ input, expected }) => {
|
|
155
|
+
const oldRecord = {
|
|
156
|
+
props: {
|
|
157
|
+
align: input,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const result = up(oldRecord)
|
|
162
|
+
expect(result.props.align).toBe(expected)
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('geoShapeMigrations - MakeUrlsValid migration', () => {
|
|
168
|
+
const { up } = getTestMigration(geoShapeVersions.MakeUrlsValid)
|
|
169
|
+
|
|
170
|
+
it('should clear invalid URLs', () => {
|
|
171
|
+
const oldRecord = {
|
|
172
|
+
props: {
|
|
173
|
+
url: 'invalid-url',
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = up(oldRecord)
|
|
178
|
+
expect(result.props.url).toBe('')
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should preserve valid URLs', () => {
|
|
182
|
+
const oldRecord = {
|
|
183
|
+
props: {
|
|
184
|
+
url: 'https://example.com',
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = up(oldRecord)
|
|
189
|
+
expect(result.props.url).toBe('https://example.com')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('geoShapeMigrations - AddScale migration', () => {
|
|
194
|
+
const { up, down } = getTestMigration(geoShapeVersions.AddScale)
|
|
195
|
+
|
|
196
|
+
it('should add scale property with default value 1', () => {
|
|
197
|
+
const oldRecord = {
|
|
198
|
+
props: {
|
|
199
|
+
geo: 'rectangle',
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = up(oldRecord)
|
|
204
|
+
expect(result.props.scale).toBe(1)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should remove scale property on down migration', () => {
|
|
208
|
+
const newRecord = {
|
|
209
|
+
props: {
|
|
210
|
+
geo: 'rectangle',
|
|
211
|
+
scale: 1.5,
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const result = down(newRecord)
|
|
216
|
+
expect(result.props.scale).toBeUndefined()
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
describe('geoShapeMigrations - AddRichText migration', () => {
|
|
221
|
+
const { up } = getTestMigration(geoShapeVersions.AddRichText)
|
|
222
|
+
|
|
223
|
+
it('should convert text property to richText', () => {
|
|
224
|
+
const oldRecord = {
|
|
225
|
+
props: {
|
|
226
|
+
text: 'Simple text content',
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const result = up(oldRecord)
|
|
231
|
+
expect(result.props.richText).toBeDefined()
|
|
232
|
+
expect(result.props.text).toBeUndefined()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should handle empty text', () => {
|
|
236
|
+
const oldRecord = {
|
|
237
|
+
props: {
|
|
238
|
+
text: '',
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const result = up(oldRecord)
|
|
243
|
+
expect(result.props.richText).toBeDefined()
|
|
244
|
+
expect(result.props.text).toBeUndefined()
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|
package/src/shapes/TLGeoShape.ts
CHANGED
|
@@ -22,7 +22,21 @@ import {
|
|
|
22
22
|
} from '../styles/TLVerticalAlignStyle'
|
|
23
23
|
import { TLBaseShape } from './TLBaseShape'
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Style property defining the geometric shape type for geo shapes.
|
|
27
|
+
* Provides a variety of built-in geometric forms including basic shapes,
|
|
28
|
+
* polygons, arrows, and special shapes.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // Use in shape props
|
|
34
|
+
* const props = {
|
|
35
|
+
* geo: 'rectangle', // or 'ellipse', 'triangle', etc.
|
|
36
|
+
* // other properties...
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
26
40
|
export const GeoShapeGeoStyle = StyleProp.defineEnum('tldraw:geo', {
|
|
27
41
|
defaultValue: 'rectangle',
|
|
28
42
|
values: [
|
|
@@ -49,34 +63,105 @@ export const GeoShapeGeoStyle = StyleProp.defineEnum('tldraw:geo', {
|
|
|
49
63
|
],
|
|
50
64
|
})
|
|
51
65
|
|
|
52
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Type representing valid geometric shape styles for geo shapes.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
53
71
|
export type TLGeoShapeGeoStyle = T.TypeOf<typeof GeoShapeGeoStyle>
|
|
54
72
|
|
|
55
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Properties for the geo shape, which renders various geometric forms with styling and text.
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
56
78
|
export interface TLGeoShapeProps {
|
|
79
|
+
/** Geometric shape type (rectangle, ellipse, triangle, etc.) */
|
|
57
80
|
geo: TLGeoShapeGeoStyle
|
|
81
|
+
/** Dash pattern style for the shape outline */
|
|
58
82
|
dash: TLDefaultDashStyle
|
|
83
|
+
/** URL link associated with the shape */
|
|
59
84
|
url: string
|
|
85
|
+
/** Width of the shape in pixels */
|
|
60
86
|
w: number
|
|
87
|
+
/** Height of the shape in pixels */
|
|
61
88
|
h: number
|
|
89
|
+
/** Additional vertical growth for text content */
|
|
62
90
|
growY: number
|
|
91
|
+
/** Scale factor applied to the shape */
|
|
63
92
|
scale: number
|
|
64
93
|
|
|
65
|
-
|
|
94
|
+
/** Color style for text label */
|
|
66
95
|
labelColor: TLDefaultColorStyle
|
|
96
|
+
/** Color style for the shape outline */
|
|
67
97
|
color: TLDefaultColorStyle
|
|
98
|
+
/** Fill style for the shape interior */
|
|
68
99
|
fill: TLDefaultFillStyle
|
|
100
|
+
/** Size/thickness style for outline and text */
|
|
69
101
|
size: TLDefaultSizeStyle
|
|
102
|
+
/** Font style for text content */
|
|
70
103
|
font: TLDefaultFontStyle
|
|
104
|
+
/** Horizontal alignment for text content */
|
|
71
105
|
align: TLDefaultHorizontalAlignStyle
|
|
106
|
+
/** Vertical alignment for text content */
|
|
72
107
|
verticalAlign: TLDefaultVerticalAlignStyle
|
|
108
|
+
/** Rich text content displayed within the shape */
|
|
73
109
|
richText: TLRichText
|
|
74
110
|
}
|
|
75
111
|
|
|
76
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* A geo shape represents geometric forms like rectangles, ellipses, triangles, and other
|
|
114
|
+
* predefined shapes. Geo shapes support styling, text content, and can act as containers.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const geoShape: TLGeoShape = {
|
|
120
|
+
* id: createShapeId(),
|
|
121
|
+
* typeName: 'shape',
|
|
122
|
+
* type: 'geo',
|
|
123
|
+
* x: 100,
|
|
124
|
+
* y: 100,
|
|
125
|
+
* rotation: 0,
|
|
126
|
+
* index: 'a1',
|
|
127
|
+
* parentId: 'page:page1',
|
|
128
|
+
* isLocked: false,
|
|
129
|
+
* opacity: 1,
|
|
130
|
+
* props: {
|
|
131
|
+
* geo: 'rectangle',
|
|
132
|
+
* w: 200,
|
|
133
|
+
* h: 100,
|
|
134
|
+
* color: 'black',
|
|
135
|
+
* fill: 'solid',
|
|
136
|
+
* dash: 'solid',
|
|
137
|
+
* size: 'm',
|
|
138
|
+
* font: 'draw',
|
|
139
|
+
* align: 'middle',
|
|
140
|
+
* verticalAlign: 'middle',
|
|
141
|
+
* richText: toRichText('Hello World'),
|
|
142
|
+
* labelColor: 'black',
|
|
143
|
+
* url: '',
|
|
144
|
+
* growY: 0,
|
|
145
|
+
* scale: 1
|
|
146
|
+
* },
|
|
147
|
+
* meta: {}
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
77
151
|
export type TLGeoShape = TLBaseShape<'geo', TLGeoShapeProps>
|
|
78
152
|
|
|
79
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Validation schema for geo shape properties.
|
|
155
|
+
*
|
|
156
|
+
* @public
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* // Validate geo shape properties
|
|
160
|
+
* const isValidGeo = geoShapeProps.geo.isValid('rectangle')
|
|
161
|
+
* const isValidSize = geoShapeProps.w.isValid(100)
|
|
162
|
+
* const isValidText = geoShapeProps.richText.isValid(toRichText('Hello'))
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
80
165
|
export const geoShapeProps: RecordProps<TLGeoShape> = {
|
|
81
166
|
geo: GeoShapeGeoStyle,
|
|
82
167
|
dash: DefaultDashStyle,
|
|
@@ -110,9 +195,20 @@ const geoShapeVersions = createShapePropsMigrationIds('geo', {
|
|
|
110
195
|
AddRichText: 10,
|
|
111
196
|
})
|
|
112
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Version identifiers for geo shape migrations.
|
|
200
|
+
*
|
|
201
|
+
* @public
|
|
202
|
+
*/
|
|
113
203
|
export { geoShapeVersions as geoShapeVersions }
|
|
114
204
|
|
|
115
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Migration sequence for geo shape properties across different schema versions.
|
|
207
|
+
* Handles evolution of geo shapes including URL support, label colors, alignment changes,
|
|
208
|
+
* and the transition from plain text to rich text.
|
|
209
|
+
*
|
|
210
|
+
* @public
|
|
211
|
+
*/
|
|
116
212
|
export const geoShapeMigrations = createShapePropsMigrationSequence({
|
|
117
213
|
sequence: [
|
|
118
214
|
{
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createShapeValidator } from './TLBaseShape'
|
|
3
|
+
import { groupShapeMigrations, groupShapeProps } from './TLGroupShape'
|
|
4
|
+
|
|
5
|
+
describe('TLGroupShape', () => {
|
|
6
|
+
describe('groupShapeProps', () => {
|
|
7
|
+
it('should be an empty object', () => {
|
|
8
|
+
expect(groupShapeProps).toEqual({})
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('groupShapeMigrations', () => {
|
|
13
|
+
it('should have empty migration sequence', () => {
|
|
14
|
+
expect(groupShapeMigrations.sequence).toEqual([])
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('group shape validation', () => {
|
|
19
|
+
const groupValidator = createShapeValidator('group', groupShapeProps)
|
|
20
|
+
|
|
21
|
+
it('should validate valid group shapes', () => {
|
|
22
|
+
const validGroup = {
|
|
23
|
+
id: 'shape:test',
|
|
24
|
+
typeName: 'shape',
|
|
25
|
+
type: 'group',
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
rotation: 0,
|
|
29
|
+
index: 'a1',
|
|
30
|
+
parentId: 'page:main',
|
|
31
|
+
isLocked: false,
|
|
32
|
+
opacity: 1,
|
|
33
|
+
props: {},
|
|
34
|
+
meta: {},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
expect(() => groupValidator.validate(validGroup)).not.toThrow()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should reject shapes with non-empty props', () => {
|
|
41
|
+
const invalidGroup = {
|
|
42
|
+
id: 'shape:invalid',
|
|
43
|
+
typeName: 'shape',
|
|
44
|
+
type: 'group',
|
|
45
|
+
x: 0,
|
|
46
|
+
y: 0,
|
|
47
|
+
rotation: 0,
|
|
48
|
+
index: 'a1',
|
|
49
|
+
parentId: 'page:main',
|
|
50
|
+
isLocked: false,
|
|
51
|
+
opacity: 1,
|
|
52
|
+
props: { invalid: 'prop' },
|
|
53
|
+
meta: {},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
expect(() => groupValidator.validate(invalidGroup)).toThrow()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|