@tldraw/tlschema 4.1.0-canary.a5989c7a02c8 → 4.1.0-canary.a94551535730

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.
Files changed (212) hide show
  1. package/dist-cjs/TLStore.js +3 -10
  2. package/dist-cjs/TLStore.js.map +2 -2
  3. package/dist-cjs/assets/TLBaseAsset.js.map +2 -2
  4. package/dist-cjs/assets/TLBookmarkAsset.js.map +2 -2
  5. package/dist-cjs/assets/TLImageAsset.js.map +2 -2
  6. package/dist-cjs/assets/TLVideoAsset.js.map +2 -2
  7. package/dist-cjs/bindings/TLArrowBinding.js.map +2 -2
  8. package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
  9. package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
  10. package/dist-cjs/createTLSchema.js.map +2 -2
  11. package/dist-cjs/index.d.ts +4416 -223
  12. package/dist-cjs/index.js +1 -1
  13. package/dist-cjs/index.js.map +2 -2
  14. package/dist-cjs/misc/TLColor.js.map +2 -2
  15. package/dist-cjs/misc/TLCursor.js.map +2 -2
  16. package/dist-cjs/misc/TLHandle.js.map +2 -2
  17. package/dist-cjs/misc/TLOpacity.js.map +2 -2
  18. package/dist-cjs/misc/TLRichText.js.map +2 -2
  19. package/dist-cjs/misc/TLScribble.js.map +2 -2
  20. package/dist-cjs/misc/geometry-types.js.map +2 -2
  21. package/dist-cjs/misc/id-validator.js.map +2 -2
  22. package/dist-cjs/records/TLAsset.js.map +2 -2
  23. package/dist-cjs/records/TLBinding.js.map +2 -2
  24. package/dist-cjs/records/TLCamera.js.map +2 -2
  25. package/dist-cjs/records/TLDocument.js.map +2 -2
  26. package/dist-cjs/records/TLInstance.js.map +2 -2
  27. package/dist-cjs/records/TLPage.js.map +2 -2
  28. package/dist-cjs/records/TLPageState.js.map +2 -2
  29. package/dist-cjs/records/TLPointer.js.map +2 -2
  30. package/dist-cjs/records/TLPresence.js.map +2 -2
  31. package/dist-cjs/records/TLRecord.js.map +1 -1
  32. package/dist-cjs/records/TLShape.js.map +2 -2
  33. package/dist-cjs/recordsWithProps.js.map +2 -2
  34. package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
  35. package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
  36. package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
  37. package/dist-cjs/shapes/TLBookmarkShape.js.map +2 -2
  38. package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
  39. package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
  40. package/dist-cjs/shapes/TLFrameShape.js.map +2 -2
  41. package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
  42. package/dist-cjs/shapes/TLGroupShape.js.map +2 -2
  43. package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
  44. package/dist-cjs/shapes/TLImageShape.js.map +2 -2
  45. package/dist-cjs/shapes/TLLineShape.js.map +2 -2
  46. package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
  47. package/dist-cjs/shapes/TLTextShape.js.map +2 -2
  48. package/dist-cjs/shapes/TLVideoShape.js.map +2 -2
  49. package/dist-cjs/store-migrations.js.map +2 -2
  50. package/dist-cjs/styles/TLColorStyle.js.map +2 -2
  51. package/dist-cjs/styles/TLDashStyle.js.map +2 -2
  52. package/dist-cjs/styles/TLFillStyle.js.map +2 -2
  53. package/dist-cjs/styles/TLFontStyle.js.map +2 -2
  54. package/dist-cjs/styles/TLHorizontalAlignStyle.js.map +2 -2
  55. package/dist-cjs/styles/TLSizeStyle.js.map +2 -2
  56. package/dist-cjs/styles/TLTextAlignStyle.js.map +2 -2
  57. package/dist-cjs/styles/TLVerticalAlignStyle.js.map +2 -2
  58. package/dist-cjs/translations/translations.js +1 -1
  59. package/dist-cjs/translations/translations.js.map +2 -2
  60. package/dist-cjs/util-types.js.map +1 -1
  61. package/dist-esm/TLStore.mjs +3 -10
  62. package/dist-esm/TLStore.mjs.map +2 -2
  63. package/dist-esm/assets/TLBaseAsset.mjs.map +2 -2
  64. package/dist-esm/assets/TLBookmarkAsset.mjs.map +2 -2
  65. package/dist-esm/assets/TLImageAsset.mjs.map +2 -2
  66. package/dist-esm/assets/TLVideoAsset.mjs.map +2 -2
  67. package/dist-esm/bindings/TLArrowBinding.mjs.map +2 -2
  68. package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
  69. package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
  70. package/dist-esm/createTLSchema.mjs.map +2 -2
  71. package/dist-esm/index.d.mts +4416 -223
  72. package/dist-esm/index.mjs +1 -1
  73. package/dist-esm/index.mjs.map +2 -2
  74. package/dist-esm/misc/TLColor.mjs.map +2 -2
  75. package/dist-esm/misc/TLCursor.mjs.map +2 -2
  76. package/dist-esm/misc/TLHandle.mjs.map +2 -2
  77. package/dist-esm/misc/TLOpacity.mjs.map +2 -2
  78. package/dist-esm/misc/TLRichText.mjs.map +2 -2
  79. package/dist-esm/misc/TLScribble.mjs.map +2 -2
  80. package/dist-esm/misc/geometry-types.mjs.map +2 -2
  81. package/dist-esm/misc/id-validator.mjs.map +2 -2
  82. package/dist-esm/records/TLAsset.mjs.map +2 -2
  83. package/dist-esm/records/TLBinding.mjs.map +2 -2
  84. package/dist-esm/records/TLCamera.mjs.map +2 -2
  85. package/dist-esm/records/TLDocument.mjs.map +2 -2
  86. package/dist-esm/records/TLInstance.mjs.map +2 -2
  87. package/dist-esm/records/TLPage.mjs.map +2 -2
  88. package/dist-esm/records/TLPageState.mjs.map +2 -2
  89. package/dist-esm/records/TLPointer.mjs.map +2 -2
  90. package/dist-esm/records/TLPresence.mjs.map +2 -2
  91. package/dist-esm/records/TLShape.mjs.map +2 -2
  92. package/dist-esm/recordsWithProps.mjs.map +2 -2
  93. package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
  94. package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
  95. package/dist-esm/shapes/TLBookmarkShape.mjs.map +2 -2
  96. package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
  97. package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
  98. package/dist-esm/shapes/TLFrameShape.mjs.map +2 -2
  99. package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
  100. package/dist-esm/shapes/TLGroupShape.mjs.map +2 -2
  101. package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
  102. package/dist-esm/shapes/TLImageShape.mjs.map +2 -2
  103. package/dist-esm/shapes/TLLineShape.mjs.map +2 -2
  104. package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
  105. package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
  106. package/dist-esm/shapes/TLVideoShape.mjs.map +2 -2
  107. package/dist-esm/store-migrations.mjs.map +2 -2
  108. package/dist-esm/styles/TLColorStyle.mjs.map +2 -2
  109. package/dist-esm/styles/TLDashStyle.mjs.map +2 -2
  110. package/dist-esm/styles/TLFillStyle.mjs.map +2 -2
  111. package/dist-esm/styles/TLFontStyle.mjs.map +2 -2
  112. package/dist-esm/styles/TLHorizontalAlignStyle.mjs.map +2 -2
  113. package/dist-esm/styles/TLSizeStyle.mjs.map +2 -2
  114. package/dist-esm/styles/TLTextAlignStyle.mjs.map +2 -2
  115. package/dist-esm/styles/TLVerticalAlignStyle.mjs.map +2 -2
  116. package/dist-esm/translations/translations.mjs +1 -1
  117. package/dist-esm/translations/translations.mjs.map +2 -2
  118. package/package.json +5 -5
  119. package/src/TLStore.test.ts +644 -0
  120. package/src/TLStore.ts +205 -20
  121. package/src/assets/TLBaseAsset.ts +90 -7
  122. package/src/assets/TLBookmarkAsset.test.ts +96 -0
  123. package/src/assets/TLBookmarkAsset.ts +52 -2
  124. package/src/assets/TLImageAsset.test.ts +213 -0
  125. package/src/assets/TLImageAsset.ts +60 -2
  126. package/src/assets/TLVideoAsset.test.ts +105 -0
  127. package/src/assets/TLVideoAsset.ts +93 -4
  128. package/src/bindings/TLArrowBinding.test.ts +55 -0
  129. package/src/bindings/TLArrowBinding.ts +132 -10
  130. package/src/bindings/TLBaseBinding.ts +140 -3
  131. package/src/createPresenceStateDerivation.test.ts +158 -0
  132. package/src/createPresenceStateDerivation.ts +71 -2
  133. package/src/createTLSchema.test.ts +181 -0
  134. package/src/createTLSchema.ts +164 -7
  135. package/src/index.ts +32 -0
  136. package/src/misc/TLColor.ts +50 -6
  137. package/src/misc/TLCursor.ts +110 -8
  138. package/src/misc/TLHandle.ts +86 -6
  139. package/src/misc/TLOpacity.ts +51 -2
  140. package/src/misc/TLRichText.ts +56 -3
  141. package/src/misc/TLScribble.ts +105 -5
  142. package/src/misc/geometry-types.ts +30 -2
  143. package/src/misc/id-validator.test.ts +50 -0
  144. package/src/misc/id-validator.ts +20 -1
  145. package/src/records/TLAsset.test.ts +234 -0
  146. package/src/records/TLAsset.ts +165 -8
  147. package/src/records/TLBinding.test.ts +22 -0
  148. package/src/records/TLBinding.ts +277 -11
  149. package/src/records/TLCamera.test.ts +19 -0
  150. package/src/records/TLCamera.ts +118 -7
  151. package/src/records/TLDocument.test.ts +35 -0
  152. package/src/records/TLDocument.ts +148 -8
  153. package/src/records/TLInstance.test.ts +201 -0
  154. package/src/records/TLInstance.ts +117 -9
  155. package/src/records/TLPage.test.ts +110 -0
  156. package/src/records/TLPage.ts +106 -8
  157. package/src/records/TLPageState.test.ts +228 -0
  158. package/src/records/TLPageState.ts +88 -7
  159. package/src/records/TLPointer.test.ts +63 -0
  160. package/src/records/TLPointer.ts +105 -7
  161. package/src/records/TLPresence.test.ts +190 -0
  162. package/src/records/TLPresence.ts +99 -5
  163. package/src/records/TLRecord.test.ts +70 -0
  164. package/src/records/TLRecord.ts +43 -1
  165. package/src/records/TLShape.test.ts +232 -0
  166. package/src/records/TLShape.ts +289 -12
  167. package/src/recordsWithProps.test.ts +188 -0
  168. package/src/recordsWithProps.ts +131 -2
  169. package/src/shapes/ShapeWithCrop.test.ts +18 -0
  170. package/src/shapes/ShapeWithCrop.ts +64 -2
  171. package/src/shapes/TLArrowShape.test.ts +505 -0
  172. package/src/shapes/TLArrowShape.ts +188 -10
  173. package/src/shapes/TLBaseShape.test.ts +142 -0
  174. package/src/shapes/TLBaseShape.ts +103 -4
  175. package/src/shapes/TLBookmarkShape.test.ts +122 -0
  176. package/src/shapes/TLBookmarkShape.ts +58 -4
  177. package/src/shapes/TLDrawShape.test.ts +177 -0
  178. package/src/shapes/TLDrawShape.ts +97 -6
  179. package/src/shapes/TLEmbedShape.test.ts +286 -0
  180. package/src/shapes/TLEmbedShape.ts +57 -4
  181. package/src/shapes/TLFrameShape.test.ts +71 -0
  182. package/src/shapes/TLFrameShape.ts +59 -4
  183. package/src/shapes/TLGeoShape.test.ts +247 -0
  184. package/src/shapes/TLGeoShape.ts +103 -7
  185. package/src/shapes/TLGroupShape.test.ts +59 -0
  186. package/src/shapes/TLGroupShape.ts +52 -4
  187. package/src/shapes/TLHighlightShape.test.ts +325 -0
  188. package/src/shapes/TLHighlightShape.ts +79 -4
  189. package/src/shapes/TLImageShape.test.ts +534 -0
  190. package/src/shapes/TLImageShape.ts +105 -5
  191. package/src/shapes/TLLineShape.test.ts +269 -0
  192. package/src/shapes/TLLineShape.ts +128 -8
  193. package/src/shapes/TLNoteShape.test.ts +1568 -0
  194. package/src/shapes/TLNoteShape.ts +97 -4
  195. package/src/shapes/TLTextShape.test.ts +407 -0
  196. package/src/shapes/TLTextShape.ts +94 -4
  197. package/src/shapes/TLVideoShape.test.ts +112 -0
  198. package/src/shapes/TLVideoShape.ts +99 -4
  199. package/src/store-migrations.test.ts +88 -0
  200. package/src/store-migrations.ts +47 -1
  201. package/src/styles/TLColorStyle.test.ts +439 -0
  202. package/src/styles/TLColorStyle.ts +228 -10
  203. package/src/styles/TLDashStyle.ts +54 -2
  204. package/src/styles/TLFillStyle.ts +54 -2
  205. package/src/styles/TLFontStyle.ts +72 -3
  206. package/src/styles/TLHorizontalAlignStyle.ts +55 -2
  207. package/src/styles/TLSizeStyle.ts +54 -2
  208. package/src/styles/TLTextAlignStyle.ts +52 -2
  209. package/src/styles/TLVerticalAlignStyle.ts +52 -2
  210. package/src/translations/translations.test.ts +378 -35
  211. package/src/translations/translations.ts +157 -10
  212. package/src/util-types.ts +51 -1
@@ -18,13 +18,45 @@ import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'
18
18
  import { TLBaseShape } from './TLBaseShape'
19
19
 
20
20
  const arrowKinds = ['arc', 'elbow'] as const
21
- /** @public */
21
+ /**
22
+ * Style property for arrow shape kind, determining how the arrow is drawn.
23
+ *
24
+ * Arrows can be drawn as arcs (curved) or elbows (angled with straight segments).
25
+ * This affects the visual appearance and behavior of arrow shapes.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Create an arrow with arc style (curved)
30
+ * const arcArrow: TLArrowShape = {
31
+ * // ... other properties
32
+ * props: {
33
+ * kind: 'arc',
34
+ * // ... other props
35
+ * }
36
+ * }
37
+ *
38
+ * // Create an arrow with elbow style (angled)
39
+ * const elbowArrow: TLArrowShape = {
40
+ * // ... other properties
41
+ * props: {
42
+ * kind: 'elbow',
43
+ * // ... other props
44
+ * }
45
+ * }
46
+ * ```
47
+ *
48
+ * @public
49
+ */
22
50
  export const ArrowShapeKindStyle = StyleProp.defineEnum('tldraw:arrowKind', {
23
51
  defaultValue: 'arc',
24
52
  values: arrowKinds,
25
53
  })
26
54
 
27
- /** @public */
55
+ /**
56
+ * The type representing arrow shape kinds.
57
+ *
58
+ * @public
59
+ */
28
60
  export type TLArrowShapeKind = T.TypeOf<typeof ArrowShapeKindStyle>
29
61
 
30
62
  const arrowheadTypes = [
@@ -39,22 +71,96 @@ const arrowheadTypes = [
39
71
  'none',
40
72
  ] as const
41
73
 
42
- /** @public */
74
+ /**
75
+ * Style property for the arrowhead at the start of an arrow.
76
+ *
77
+ * Defines the visual style of the arrowhead at the beginning of the arrow path.
78
+ * Can be one of several predefined styles or none for no arrowhead.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // Arrow with no start arrowhead but triangle end arrowhead
83
+ * const arrow: TLArrowShape = {
84
+ * // ... other properties
85
+ * props: {
86
+ * arrowheadStart: 'none',
87
+ * arrowheadEnd: 'triangle',
88
+ * // ... other props
89
+ * }
90
+ * }
91
+ * ```
92
+ *
93
+ * @public
94
+ */
43
95
  export const ArrowShapeArrowheadStartStyle = StyleProp.defineEnum('tldraw:arrowheadStart', {
44
96
  defaultValue: 'none',
45
97
  values: arrowheadTypes,
46
98
  })
47
99
 
48
- /** @public */
100
+ /**
101
+ * Style property for the arrowhead at the end of an arrow.
102
+ *
103
+ * Defines the visual style of the arrowhead at the end of the arrow path.
104
+ * Defaults to 'arrow' style, giving arrows their characteristic pointed appearance.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // Arrow with different start and end arrowheads
109
+ * const doubleArrow: TLArrowShape = {
110
+ * // ... other properties
111
+ * props: {
112
+ * arrowheadStart: 'triangle',
113
+ * arrowheadEnd: 'diamond',
114
+ * // ... other props
115
+ * }
116
+ * }
117
+ * ```
118
+ *
119
+ * @public
120
+ */
49
121
  export const ArrowShapeArrowheadEndStyle = StyleProp.defineEnum('tldraw:arrowheadEnd', {
50
122
  defaultValue: 'arrow',
51
123
  values: arrowheadTypes,
52
124
  })
53
125
 
54
- /** @public */
126
+ /**
127
+ * The type representing arrowhead styles for both start and end of arrows.
128
+ *
129
+ * @public
130
+ */
55
131
  export type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle>
56
132
 
57
- /** @public */
133
+ /**
134
+ * Properties specific to arrow shapes.
135
+ *
136
+ * Defines all the configurable aspects of an arrow shape, including visual styling,
137
+ * geometry, text labeling, and positioning. Arrows can connect two points and
138
+ * optionally display text labels.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const arrowProps: TLArrowShapeProps = {
143
+ * kind: 'arc',
144
+ * labelColor: 'black',
145
+ * color: 'blue',
146
+ * fill: 'none',
147
+ * dash: 'solid',
148
+ * size: 'm',
149
+ * arrowheadStart: 'none',
150
+ * arrowheadEnd: 'arrow',
151
+ * font: 'draw',
152
+ * start: { x: 0, y: 0 },
153
+ * end: { x: 100, y: 100 },
154
+ * bend: 0.2,
155
+ * richText: toRichText('Label'),
156
+ * labelPosition: 0.5,
157
+ * scale: 1,
158
+ * elbowMidPoint: 0.5
159
+ * }
160
+ * ```
161
+ *
162
+ * @public
163
+ */
58
164
  export interface TLArrowShapeProps {
59
165
  kind: TLArrowShapeKind
60
166
  labelColor: TLDefaultColorStyle
@@ -74,10 +180,59 @@ export interface TLArrowShapeProps {
74
180
  elbowMidPoint: number
75
181
  }
76
182
 
77
- /** @public */
183
+ /**
184
+ * A complete arrow shape record.
185
+ *
186
+ * Combines the base shape interface with arrow-specific properties to create
187
+ * a full arrow shape that can be stored and manipulated in the editor.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const arrowShape: TLArrowShape = {
192
+ * id: 'shape:arrow123',
193
+ * typeName: 'shape',
194
+ * type: 'arrow',
195
+ * x: 100,
196
+ * y: 200,
197
+ * rotation: 0,
198
+ * index: 'a1',
199
+ * parentId: 'page:main',
200
+ * isLocked: false,
201
+ * opacity: 1,
202
+ * props: {
203
+ * kind: 'arc',
204
+ * start: { x: 0, y: 0 },
205
+ * end: { x: 150, y: 100 },
206
+ * // ... other props
207
+ * },
208
+ * meta: {}
209
+ * }
210
+ * ```
211
+ *
212
+ * @public
213
+ */
78
214
  export type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>
79
215
 
80
- /** @public */
216
+ /**
217
+ * Validation configuration for arrow shape properties.
218
+ *
219
+ * Defines the validators for each property of an arrow shape, ensuring that
220
+ * arrow shape data is valid and conforms to the expected types and constraints.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * // The validators ensure proper typing and validation
225
+ * const validator = T.object(arrowShapeProps)
226
+ * const validArrowProps = validator.validate({
227
+ * kind: 'arc',
228
+ * start: { x: 0, y: 0 },
229
+ * end: { x: 100, y: 50 },
230
+ * // ... other required properties
231
+ * })
232
+ * ```
233
+ *
234
+ * @public
235
+ */
81
236
  export const arrowShapeProps: RecordProps<TLArrowShape> = {
82
237
  kind: ArrowShapeKindStyle,
83
238
  labelColor: DefaultLabelColorStyle,
@@ -97,7 +252,22 @@ export const arrowShapeProps: RecordProps<TLArrowShape> = {
97
252
  elbowMidPoint: T.number,
98
253
  }
99
254
 
100
- /** @public */
255
+ /**
256
+ * Migration version identifiers for arrow shape properties.
257
+ *
258
+ * These track the evolution of the arrow shape schema over time, with each
259
+ * version representing a specific change to the arrow shape structure or properties.
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * // Used internally for migration system
264
+ * if (version < arrowShapeVersions.AddLabelColor) {
265
+ * // Apply label color migration
266
+ * }
267
+ * ```
268
+ *
269
+ * @public
270
+ */
101
271
  export const arrowShapeVersions = createShapePropsMigrationIds('arrow', {
102
272
  AddLabelColor: 1,
103
273
  AddIsPrecise: 2,
@@ -112,7 +282,15 @@ function propsMigration(migration: TLPropsMigration) {
112
282
  return createPropsMigration<TLArrowShape>('shape', 'arrow', migration)
113
283
  }
114
284
 
115
- /** @public */
285
+ /**
286
+ * Complete migration sequence for arrow shapes.
287
+ *
288
+ * Defines all the migrations needed to transform arrow shape data from older
289
+ * versions to the current version. Each migration handles a specific schema change,
290
+ * ensuring backward compatibility and smooth data evolution.
291
+ *
292
+ * @public
293
+ */
116
294
  export const arrowShapeMigrations = createMigrationSequence({
117
295
  sequenceId: 'com.tldraw.shape.arrow',
118
296
  retroactive: false,
@@ -0,0 +1,142 @@
1
+ import { T } from '@tldraw/validate'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { createShapeValidator, parentIdValidator, shapeIdValidator } from './TLBaseShape'
4
+
5
+ describe('TLBaseShape', () => {
6
+ describe('parentIdValidator', () => {
7
+ it('should accept valid page and shape parent IDs', () => {
8
+ expect(() => parentIdValidator.validate('page:main')).not.toThrow()
9
+ expect(() => parentIdValidator.validate('shape:frame1')).not.toThrow()
10
+ expect(parentIdValidator.validate('page:main')).toBe('page:main')
11
+ })
12
+
13
+ it('should reject invalid parent ID prefixes', () => {
14
+ expect(() => parentIdValidator.validate('invalid:123')).toThrow(
15
+ 'Parent ID must start with "page:" or "shape:"'
16
+ )
17
+ expect(() => parentIdValidator.validate('asset:123')).toThrow(
18
+ 'Parent ID must start with "page:" or "shape:"'
19
+ )
20
+ expect(() => parentIdValidator.validate('page-main')).toThrow(
21
+ 'Parent ID must start with "page:" or "shape:"'
22
+ )
23
+ })
24
+ })
25
+
26
+ describe('shapeIdValidator', () => {
27
+ it('should accept valid shape IDs', () => {
28
+ expect(() => shapeIdValidator.validate('shape:abc123')).not.toThrow()
29
+ expect(shapeIdValidator.validate('shape:test')).toBe('shape:test')
30
+ })
31
+
32
+ it('should reject non-shape IDs', () => {
33
+ expect(() => shapeIdValidator.validate('page:123')).toThrow(
34
+ 'shape ID must start with "shape:"'
35
+ )
36
+ expect(() => shapeIdValidator.validate('asset:123')).toThrow(
37
+ 'shape ID must start with "shape:"'
38
+ )
39
+ expect(() => shapeIdValidator.validate('shape-abc123')).toThrow(
40
+ 'shape ID must start with "shape:"'
41
+ )
42
+ })
43
+ })
44
+
45
+ describe('createShapeValidator', () => {
46
+ it('should create validator for shape with no custom props', () => {
47
+ const validator = createShapeValidator('simple')
48
+
49
+ const validShape = {
50
+ id: 'shape:simple123',
51
+ typeName: 'shape',
52
+ type: 'simple',
53
+ x: 100,
54
+ y: 200,
55
+ rotation: 0.5,
56
+ index: 'a1',
57
+ parentId: 'page:main',
58
+ isLocked: false,
59
+ opacity: 0.8,
60
+ props: {},
61
+ meta: {},
62
+ }
63
+
64
+ expect(() => validator.validate(validShape)).not.toThrow()
65
+ })
66
+
67
+ it('should create validator for shape with custom props', () => {
68
+ const validator = createShapeValidator('custom', {
69
+ width: T.number,
70
+ height: T.number,
71
+ color: T.string,
72
+ })
73
+
74
+ const validShape = {
75
+ id: 'shape:custom456',
76
+ typeName: 'shape',
77
+ type: 'custom',
78
+ x: 50,
79
+ y: 75,
80
+ rotation: 1.0,
81
+ index: 'a2',
82
+ parentId: 'shape:frame1',
83
+ isLocked: true,
84
+ opacity: 0.5,
85
+ props: {
86
+ width: 150,
87
+ height: 100,
88
+ color: 'blue',
89
+ },
90
+ meta: {},
91
+ }
92
+
93
+ expect(() => validator.validate(validShape)).not.toThrow()
94
+ })
95
+
96
+ it('should reject shapes with wrong type', () => {
97
+ const validator = createShapeValidator('test')
98
+
99
+ const invalidShape = {
100
+ id: 'shape:test',
101
+ typeName: 'shape',
102
+ type: 'wrong',
103
+ x: 0,
104
+ y: 0,
105
+ rotation: 0,
106
+ index: 'a1',
107
+ parentId: 'page:main',
108
+ isLocked: false,
109
+ opacity: 1,
110
+ props: {},
111
+ meta: {},
112
+ }
113
+
114
+ expect(() => validator.validate(invalidShape)).toThrow()
115
+ })
116
+
117
+ it('should reject shapes with invalid custom props', () => {
118
+ const validator = createShapeValidator('custom', {
119
+ width: T.number,
120
+ height: T.number,
121
+ color: T.string,
122
+ })
123
+
124
+ const invalidShape = {
125
+ id: 'shape:custom123',
126
+ typeName: 'shape',
127
+ type: 'custom',
128
+ x: 0,
129
+ y: 0,
130
+ rotation: 0,
131
+ index: 'a1',
132
+ parentId: 'page:main',
133
+ isLocked: false,
134
+ opacity: 1,
135
+ meta: {},
136
+ props: { width: '100' }, // Wrong type - should be number
137
+ }
138
+
139
+ expect(() => validator.validate(invalidShape)).toThrow()
140
+ })
141
+ })
142
+ })
@@ -5,7 +5,39 @@ import { TLOpacityType, opacityValidator } from '../misc/TLOpacity'
5
5
  import { idValidator } from '../misc/id-validator'
6
6
  import { TLParentId, TLShapeId } from '../records/TLShape'
7
7
 
8
- /** @public */
8
+ /**
9
+ * Base interface for all shapes in tldraw.
10
+ *
11
+ * This interface defines the common properties that all shapes share, regardless of their
12
+ * specific type. Every shape extends this base with additional type-specific properties.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // Define a custom shape type
17
+ * interface MyCustomShape extends TLBaseShape<'custom', { size: number; color: string }> {}
18
+ *
19
+ * // Create a shape instance
20
+ * const myShape: MyCustomShape = {
21
+ * id: 'shape:abc123',
22
+ * typeName: 'shape',
23
+ * type: 'custom',
24
+ * x: 100,
25
+ * y: 200,
26
+ * rotation: 0,
27
+ * index: 'a1',
28
+ * parentId: 'page:main',
29
+ * isLocked: false,
30
+ * opacity: 1,
31
+ * props: {
32
+ * size: 50,
33
+ * color: 'blue'
34
+ * },
35
+ * meta: {}
36
+ * }
37
+ * ```
38
+ *
39
+ * @public
40
+ */
9
41
  export interface TLBaseShape<Type extends string, Props extends object>
10
42
  extends BaseRecord<'shape', TLShapeId> {
11
43
  type: Type
@@ -20,7 +52,24 @@ export interface TLBaseShape<Type extends string, Props extends object>
20
52
  meta: JsonObject
21
53
  }
22
54
 
23
- /** @public */
55
+ /**
56
+ * Validator for parent IDs, ensuring they follow the correct format.
57
+ *
58
+ * Parent IDs must start with either "page:" (for shapes directly on a page)
59
+ * or "shape:" (for shapes inside other shapes like frames or groups).
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Valid parent IDs
64
+ * const pageParent = parentIdValidator.validate('page:main') // ✓
65
+ * const shapeParent = parentIdValidator.validate('shape:frame1') // ✓
66
+ *
67
+ * // Invalid parent ID (throws error)
68
+ * const invalid = parentIdValidator.validate('invalid:123') // ✗
69
+ * ```
70
+ *
71
+ * @public
72
+ */
24
73
  export const parentIdValidator = T.string.refine((id) => {
25
74
  if (!id.startsWith('page:') && !id.startsWith('shape:')) {
26
75
  throw new Error('Parent ID must start with "page:" or "shape:"')
@@ -28,10 +77,60 @@ export const parentIdValidator = T.string.refine((id) => {
28
77
  return id as TLParentId
29
78
  })
30
79
 
31
- /** @public */
80
+ /**
81
+ * Validator for shape IDs, ensuring they follow the "shape:" format.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const validId = shapeIdValidator.validate('shape:abc123') // ✓
86
+ * const invalidId = shapeIdValidator.validate('page:abc123') // ✗ throws error
87
+ * ```
88
+ *
89
+ * @public
90
+ */
32
91
  export const shapeIdValidator = idValidator<TLShapeId>('shape')
33
92
 
34
- /** @public */
93
+ /**
94
+ * Creates a validator for a specific shape type.
95
+ *
96
+ * This function generates a complete validator that can validate shape records
97
+ * of the specified type, including both the base shape properties and any
98
+ * custom properties and metadata specific to that shape type.
99
+ *
100
+ * @param type - The string literal type for this shape (e.g., 'geo', 'arrow')
101
+ * @param props - Optional validator configuration for shape-specific properties
102
+ * @param meta - Optional validator configuration for shape-specific metadata
103
+ * @returns A validator that can validate complete shape records of the specified type
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * // Create a validator for a custom shape type
108
+ * const customShapeValidator = createShapeValidator('custom', {
109
+ * width: T.number,
110
+ * height: T.number,
111
+ * color: T.string
112
+ * })
113
+ *
114
+ * // Use the validator to validate shape data
115
+ * const shapeData = {
116
+ * id: 'shape:abc123',
117
+ * typeName: 'shape',
118
+ * type: 'custom',
119
+ * x: 100,
120
+ * y: 200,
121
+ * // ... other base properties
122
+ * props: {
123
+ * width: 150,
124
+ * height: 100,
125
+ * color: 'red'
126
+ * }
127
+ * }
128
+ *
129
+ * const validatedShape = customShapeValidator.validate(shapeData)
130
+ * ```
131
+ *
132
+ * @public
133
+ */
35
134
  export function createShapeValidator<
36
135
  Type extends string,
37
136
  Props extends JsonObject,
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getTestMigration } from '../__tests__/migrationTestUtils'
3
+ import { bookmarkShapeProps, bookmarkShapeVersions } from './TLBookmarkShape'
4
+
5
+ describe('TLBookmarkShape', () => {
6
+ describe('bookmarkShapeProps validation', () => {
7
+ it('should validate width as nonZeroNumber', () => {
8
+ // Valid non-zero positive numbers
9
+ expect(() => bookmarkShapeProps.w.validate(0.1)).not.toThrow()
10
+ expect(() => bookmarkShapeProps.w.validate(100)).not.toThrow()
11
+
12
+ // Invalid: zero and negative
13
+ expect(() => bookmarkShapeProps.w.validate(0)).toThrow()
14
+ expect(() => bookmarkShapeProps.w.validate(-1)).toThrow()
15
+ })
16
+
17
+ it('should validate height as nonZeroNumber', () => {
18
+ // Valid non-zero positive numbers
19
+ expect(() => bookmarkShapeProps.h.validate(0.1)).not.toThrow()
20
+ expect(() => bookmarkShapeProps.h.validate(100)).not.toThrow()
21
+
22
+ // Invalid: zero and negative
23
+ expect(() => bookmarkShapeProps.h.validate(0)).toThrow()
24
+ expect(() => bookmarkShapeProps.h.validate(-1)).toThrow()
25
+ })
26
+
27
+ it('should validate assetId as nullable asset ID', () => {
28
+ // Valid asset IDs
29
+ expect(() => bookmarkShapeProps.assetId.validate(null)).not.toThrow()
30
+ expect(() => bookmarkShapeProps.assetId.validate('asset:bookmark123')).not.toThrow()
31
+
32
+ // Invalid asset IDs
33
+ expect(() => bookmarkShapeProps.assetId.validate('shape:notasset')).toThrow()
34
+ expect(() => bookmarkShapeProps.assetId.validate('bookmark123')).toThrow()
35
+ expect(() => bookmarkShapeProps.assetId.validate(undefined)).toThrow()
36
+ })
37
+
38
+ it('should validate url as linkUrl', () => {
39
+ // Valid URLs
40
+ expect(() => bookmarkShapeProps.url.validate('')).not.toThrow()
41
+ expect(() => bookmarkShapeProps.url.validate('https://example.com')).not.toThrow()
42
+
43
+ // Invalid URLs
44
+ expect(() => bookmarkShapeProps.url.validate('not-a-url')).toThrow()
45
+ expect(() => bookmarkShapeProps.url.validate('javascript:alert("xss")')).toThrow()
46
+ })
47
+ })
48
+
49
+ describe('NullAssetId migration', () => {
50
+ const { up, down } = getTestMigration(bookmarkShapeVersions.NullAssetId)
51
+
52
+ it('should add assetId as null when undefined', () => {
53
+ const oldRecord = {
54
+ props: {
55
+ w: 300,
56
+ h: 320,
57
+ url: 'https://example.com',
58
+ // assetId undefined
59
+ },
60
+ }
61
+
62
+ const result = up(oldRecord)
63
+ expect(result.props.assetId).toBeNull()
64
+ })
65
+
66
+ it('should preserve existing assetId when present', () => {
67
+ const oldRecord = {
68
+ props: {
69
+ w: 300,
70
+ h: 320,
71
+ url: 'https://example.com',
72
+ assetId: 'asset:existing123',
73
+ },
74
+ }
75
+
76
+ const result = up(oldRecord)
77
+ expect(result.props.assetId).toBe('asset:existing123')
78
+ })
79
+
80
+ it('should throw on retired down migration', () => {
81
+ expect(() => down({})).toThrow()
82
+ })
83
+ })
84
+
85
+ describe('MakeUrlsValid migration', () => {
86
+ const { up, down } = getTestMigration(bookmarkShapeVersions.MakeUrlsValid)
87
+
88
+ it('should set invalid URLs to empty string', () => {
89
+ const oldRecord = {
90
+ props: {
91
+ w: 300,
92
+ h: 320,
93
+ assetId: null,
94
+ url: 'not-a-valid-url',
95
+ },
96
+ }
97
+
98
+ const result = up(oldRecord)
99
+ expect(result.props.url).toBe('')
100
+ })
101
+
102
+ it('should preserve valid URLs', () => {
103
+ const oldRecord = {
104
+ props: {
105
+ w: 300,
106
+ h: 320,
107
+ assetId: null,
108
+ url: 'https://example.com',
109
+ },
110
+ }
111
+
112
+ const result = up(oldRecord)
113
+ expect(result.props.url).toBe('https://example.com')
114
+ })
115
+
116
+ it('should be noop for down migration', () => {
117
+ const newRecord = { props: { url: 'https://example.com' } }
118
+ const result = down(newRecord)
119
+ expect(result).toEqual(newRecord)
120
+ })
121
+ })
122
+ })