@tldraw/tlschema 4.1.0-canary.c62140a07605 → 4.1.0-canary.caaa99b713a2

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
@@ -8,28 +8,96 @@ import { RecordProps } from '../recordsWithProps'
8
8
  import { arrowShapeVersions } from '../shapes/TLArrowShape'
9
9
  import { TLBaseBinding } from './TLBaseBinding'
10
10
 
11
- /** @public */
11
+ /**
12
+ * Defines the snapping behavior for elbow-style arrows when binding to shapes.
13
+ * Controls how the arrow segment aligns with the target shape's geometry.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const binding: TLArrowBindingProps = {
18
+ * terminal: 'end',
19
+ * normalizedAnchor: { x: 0.5, y: 0.5 },
20
+ * isExact: false,
21
+ * isPrecise: true,
22
+ * snap: 'edge' // Snap to shape edge
23
+ * }
24
+ * ```
25
+ *
26
+ * @public
27
+ */
12
28
  export const ElbowArrowSnap = T.literalEnum('center', 'edge-point', 'edge', 'none')
13
- /** @public */
29
+ /**
30
+ * Type representing the possible elbow arrow snap modes.
31
+ *
32
+ * - `'center'` - Snap to the center of the target shape
33
+ * - `'edge-point'` - Snap to a specific point on the shape's edge
34
+ * - `'edge'` - Snap to the nearest edge of the shape
35
+ * - `'none'` - No snapping behavior
36
+ *
37
+ * @public
38
+ */
14
39
  export type ElbowArrowSnap = T.TypeOf<typeof ElbowArrowSnap>
15
40
 
16
- /** @public */
41
+ /**
42
+ * Properties that define how an arrow binds to a target shape.
43
+ * These properties control the visual and behavioral aspects of the arrow-to-shape connection.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const arrowBindingProps: TLArrowBindingProps = {
48
+ * terminal: 'end', // Bind the arrow's end point
49
+ * normalizedAnchor: { x: 0.5, y: 0.0 }, // Bind to top center of shape
50
+ * isExact: true, // Arrow head enters the shape
51
+ * isPrecise: true, // Use exact anchor position
52
+ * snap: 'edge' // Snap to shape edge
53
+ * }
54
+ * ```
55
+ *
56
+ * @public
57
+ */
17
58
  export interface TLArrowBindingProps {
59
+ /** Which end of the arrow is bound - either 'start' or 'end' */
18
60
  terminal: 'start' | 'end'
61
+ /**
62
+ * Normalized anchor point on the target shape (0,0 = top-left, 1,1 = bottom-right).
63
+ * Coordinates are relative to the shape's bounding box.
64
+ */
19
65
  normalizedAnchor: VecModel
20
66
  /**
21
- * exact is whether the arrow head 'enters' the bound shape to point directly at the binding
22
- * anchor point
67
+ * Whether the arrow head 'enters' the bound shape to point directly at the binding
68
+ * anchor point. When true, the arrow head will be positioned inside the target shape.
23
69
  */
24
70
  isExact: boolean
25
71
  /**
26
- * precise is whether to bind to the normalizedAnchor, or to the middle of the shape
72
+ * Whether to bind to the exact normalizedAnchor position, or to the center of the shape.
73
+ * When false, the arrow will connect to the shape's center regardless of anchor position.
27
74
  */
28
75
  isPrecise: boolean
76
+ /** Snapping behavior for elbow-style arrows */
29
77
  snap: ElbowArrowSnap
30
78
  }
31
79
 
32
- /** @public */
80
+ /**
81
+ * Validation schema for arrow binding properties.
82
+ * Defines the runtime validation rules for each property in TLArrowBindingProps.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import { arrowBindingProps } from '@tldraw/tlschema'
87
+ *
88
+ * // Use in custom shape schema
89
+ * const customSchema = createTLSchema({
90
+ * bindings: {
91
+ * arrow: {
92
+ * props: arrowBindingProps,
93
+ * migrations: arrowBindingMigrations
94
+ * }
95
+ * }
96
+ * })
97
+ * ```
98
+ *
99
+ * @public
100
+ */
33
101
  export const arrowBindingProps: RecordProps<TLArrowBinding> = {
34
102
  terminal: T.literalEnum('start', 'end'),
35
103
  normalizedAnchor: vecModelValidator,
@@ -38,15 +106,69 @@ export const arrowBindingProps: RecordProps<TLArrowBinding> = {
38
106
  snap: ElbowArrowSnap,
39
107
  }
40
108
 
41
- /** @public */
109
+ /**
110
+ * Represents a binding relationship between an arrow shape and another shape.
111
+ * Arrow bindings allow arrows to connect to and follow other shapes, maintaining
112
+ * the connection even when shapes are moved or transformed.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * const arrowBinding: TLArrowBinding = {
117
+ * id: 'binding:abc123',
118
+ * typeName: 'binding',
119
+ * type: 'arrow',
120
+ * fromId: 'shape:arrow1', // The arrow shape
121
+ * toId: 'shape:rectangle1', // The target shape
122
+ * props: {
123
+ * terminal: 'end',
124
+ * normalizedAnchor: { x: 0.5, y: 0.5 },
125
+ * isExact: false,
126
+ * isPrecise: true,
127
+ * snap: 'edge'
128
+ * },
129
+ * meta: {}
130
+ * }
131
+ * ```
132
+ *
133
+ * @public
134
+ */
42
135
  export type TLArrowBinding = TLBaseBinding<'arrow', TLArrowBindingProps>
43
136
 
44
- /** @public */
137
+ /**
138
+ * Version identifiers for arrow binding property migrations.
139
+ * Each version represents a schema change that requires data migration.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * // Check if migration is needed
144
+ * if (bindingVersion < arrowBindingVersions.AddSnap) {
145
+ * // Apply AddSnap migration
146
+ * }
147
+ * ```
148
+ *
149
+ * @public
150
+ */
45
151
  export const arrowBindingVersions = createBindingPropsMigrationIds('arrow', {
46
152
  AddSnap: 1,
47
153
  })
48
154
 
49
- /** @public */
155
+ /**
156
+ * Migration sequence for arrow binding properties.
157
+ * Handles schema evolution over time by defining how to migrate data between versions.
158
+ *
159
+ * The sequence includes:
160
+ * - **AddSnap (v1)**: Adds the `snap` property with default value 'none'
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * import { arrowBindingMigrations } from '@tldraw/tlschema'
165
+ *
166
+ * // Apply migrations when loading older data
167
+ * const migratedBinding = arrowBindingMigrations.migrate(oldBinding)
168
+ * ```
169
+ *
170
+ * @public
171
+ */
50
172
  export const arrowBindingMigrations = createBindingPropsMigrationSequence({
51
173
  sequence: [
52
174
  { dependsOn: [arrowShapeVersions.ExtractBindings] },
@@ -6,20 +6,157 @@ import { TLBindingId } from '../records/TLBinding'
6
6
  import { TLShapeId } from '../records/TLShape'
7
7
  import { shapeIdValidator } from '../shapes/TLBaseShape'
8
8
 
9
- /** @public */
9
+ /**
10
+ * Base interface for all binding types in tldraw. Bindings represent relationships
11
+ * between shapes, such as arrows connecting to other shapes or organizational connections.
12
+ *
13
+ * All bindings extend this base interface with specific type and property definitions.
14
+ * The binding system enables shapes to maintain relationships that persist through
15
+ * transformations, movements, and other operations.
16
+ *
17
+ * @param Type - String literal type identifying the specific binding type (e.g., 'arrow')
18
+ * @param Props - Object containing binding-specific properties and configuration
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Define a custom binding type
23
+ * interface MyCustomBinding extends TLBaseBinding<'custom', MyCustomProps> {}
24
+ *
25
+ * interface MyCustomProps {
26
+ * strength: number
27
+ * color: string
28
+ * }
29
+ *
30
+ * // Create a binding instance
31
+ * const binding: MyCustomBinding = {
32
+ * id: 'binding:abc123',
33
+ * typeName: 'binding',
34
+ * type: 'custom',
35
+ * fromId: 'shape:source1',
36
+ * toId: 'shape:target1',
37
+ * props: {
38
+ * strength: 0.8,
39
+ * color: 'red'
40
+ * },
41
+ * meta: {}
42
+ * }
43
+ * ```
44
+ *
45
+ * @public
46
+ */
10
47
  export interface TLBaseBinding<Type extends string, Props extends object>
11
48
  extends BaseRecord<'binding', TLBindingId> {
49
+ /** The specific type of this binding (e.g., 'arrow', 'custom') */
12
50
  type: Type
51
+ /** ID of the source shape in this binding relationship */
13
52
  fromId: TLShapeId
53
+ /** ID of the target shape in this binding relationship */
14
54
  toId: TLShapeId
55
+ /** Binding-specific properties that define behavior and appearance */
15
56
  props: Props
57
+ /** User-defined metadata for extending binding functionality */
16
58
  meta: JsonObject
17
59
  }
18
60
 
19
- /** @public */
61
+ /**
62
+ * Validator for binding IDs. Ensures that binding identifiers follow the correct
63
+ * format and type constraints required by the tldraw schema system.
64
+ *
65
+ * Used internally by the schema validation system to verify binding IDs when
66
+ * records are created or modified. All binding IDs must be prefixed with 'binding:'.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { bindingIdValidator } from '@tldraw/tlschema'
71
+ *
72
+ * // Validate a binding ID
73
+ * const isValid = bindingIdValidator.isValid('binding:abc123') // true
74
+ * const isInvalid = bindingIdValidator.isValid('shape:abc123') // false
75
+ *
76
+ * // Use in custom validation schema
77
+ * const customBindingValidator = T.object({
78
+ * id: bindingIdValidator,
79
+ * // ... other properties
80
+ * })
81
+ * ```
82
+ *
83
+ * @public
84
+ */
20
85
  export const bindingIdValidator = idValidator<TLBindingId>('binding')
21
86
 
22
- /** @public */
87
+ /**
88
+ * Creates a runtime validator for a specific binding type. This factory function
89
+ * generates a complete validation schema for custom bindings that extends TLBaseBinding.
90
+ *
91
+ * The validator ensures all binding records conform to the expected structure with
92
+ * proper type safety and runtime validation. It validates the base binding properties
93
+ * (id, type, fromId, toId) along with custom props and meta fields.
94
+ *
95
+ * @param type - The string literal type identifier for this binding (e.g., 'arrow', 'custom')
96
+ * @param props - Optional validation schema for binding-specific properties
97
+ * @param meta - Optional validation schema for metadata fields
98
+ *
99
+ * @returns A validator object that can validate complete binding records
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { createBindingValidator } from '@tldraw/tlschema'
104
+ * import { T } from '@tldraw/validate'
105
+ *
106
+ * // Create validator for a custom binding type
107
+ * const myBindingValidator = createBindingValidator(
108
+ * 'myBinding',
109
+ * {
110
+ * strength: T.number,
111
+ * color: T.string,
112
+ * enabled: T.boolean
113
+ * },
114
+ * {
115
+ * createdAt: T.number,
116
+ * author: T.string
117
+ * }
118
+ * )
119
+ *
120
+ * // Validate a binding instance
121
+ * const bindingData = {
122
+ * id: 'binding:123',
123
+ * typeName: 'binding',
124
+ * type: 'myBinding',
125
+ * fromId: 'shape:abc',
126
+ * toId: 'shape:def',
127
+ * props: {
128
+ * strength: 0.8,
129
+ * color: 'red',
130
+ * enabled: true
131
+ * },
132
+ * meta: {
133
+ * createdAt: Date.now(),
134
+ * author: 'user123'
135
+ * }
136
+ * }
137
+ *
138
+ * const isValid = myBindingValidator.isValid(bindingData) // true
139
+ * ```
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * // Simple binding without custom props or meta
144
+ * const simpleBindingValidator = createBindingValidator('simple')
145
+ *
146
+ * // This will use JsonValue validation for props and meta
147
+ * const binding = {
148
+ * id: 'binding:456',
149
+ * typeName: 'binding',
150
+ * type: 'simple',
151
+ * fromId: 'shape:start',
152
+ * toId: 'shape:end',
153
+ * props: {}, // Any JSON value allowed
154
+ * meta: {} // Any JSON value allowed
155
+ * }
156
+ * ```
157
+ *
158
+ * @public
159
+ */
23
160
  export function createBindingValidator<
24
161
  Type extends string,
25
162
  Props extends JsonObject,
@@ -0,0 +1,158 @@
1
+ import { atom } from '@tldraw/state'
2
+ import { Store } from '@tldraw/store'
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ createPresenceStateDerivation,
6
+ getDefaultUserPresence,
7
+ TLPresenceUserInfo,
8
+ } from './createPresenceStateDerivation'
9
+ import { createTLSchema } from './createTLSchema'
10
+ import { TLINSTANCE_ID } from './records/TLInstance'
11
+ import { TLPageId } from './records/TLPage'
12
+ import { InstancePageStateRecordType } from './records/TLPageState'
13
+ import { TLRecord } from './records/TLRecord'
14
+ import { createIntegrityChecker, TLStoreProps } from './TLStore'
15
+
16
+ describe('createPresenceStateDerivation', () => {
17
+ let store: Store<TLRecord, TLStoreProps>
18
+ let userSignal: ReturnType<typeof atom<TLPresenceUserInfo>>
19
+
20
+ beforeEach(() => {
21
+ const schema = createTLSchema()
22
+ store = new Store({
23
+ schema,
24
+ props: {
25
+ defaultName: 'Test Store',
26
+ assets: {
27
+ upload: vi.fn().mockResolvedValue({ src: 'uploaded-url' }),
28
+ resolve: vi.fn().mockResolvedValue('resolved-url'),
29
+ remove: vi.fn().mockResolvedValue(undefined),
30
+ },
31
+ onMount: vi.fn(),
32
+ },
33
+ })
34
+
35
+ // Initialize store with required records
36
+ const checker = createIntegrityChecker(store)
37
+ checker()
38
+
39
+ userSignal = atom('user', {
40
+ id: 'user123',
41
+ name: 'Test User',
42
+ color: '#007AFF',
43
+ })
44
+ })
45
+
46
+ afterEach(() => {
47
+ vi.clearAllMocks()
48
+ })
49
+
50
+ describe('basic functionality', () => {
51
+ it('should create presence state and be reactive to changes', () => {
52
+ const derivation = createPresenceStateDerivation(userSignal)
53
+ const presenceSignal = derivation(store)
54
+ const presence = presenceSignal.get()
55
+
56
+ expect(presence!.userId).toBe('user123')
57
+ expect(presence!.userName).toBe('Test User')
58
+ expect(presence!.color).toBe('#007AFF')
59
+
60
+ // Update user signal
61
+ userSignal.set({
62
+ id: 'user456',
63
+ name: 'Updated User',
64
+ color: '#FF6B6B',
65
+ })
66
+
67
+ const updatedPresence = presenceSignal.get()
68
+ expect(updatedPresence!.userName).toBe('Updated User')
69
+ expect(updatedPresence!.color).toBe('#FF6B6B')
70
+ })
71
+
72
+ it('should return null when required records are missing', () => {
73
+ store.remove([TLINSTANCE_ID])
74
+
75
+ const derivation = createPresenceStateDerivation(userSignal)
76
+ expect(derivation(store).get()).toBe(null)
77
+ })
78
+ })
79
+
80
+ describe('store state integration', () => {
81
+ it('should be reactive to store changes', () => {
82
+ const derivation = createPresenceStateDerivation(userSignal)
83
+ const presenceSignal = derivation(store)
84
+
85
+ const pageId = [...store.query.ids('page').get()][0] as TLPageId
86
+ const pageState = store.get(InstancePageStateRecordType.createId(pageId))!
87
+ store.put([
88
+ {
89
+ ...pageState,
90
+ selectedShapeIds: ['shape:test' as any],
91
+ },
92
+ ])
93
+
94
+ const presence = presenceSignal.get()
95
+ expect(presence!.selectedShapeIds).toEqual(['shape:test'])
96
+ })
97
+ })
98
+ })
99
+
100
+ describe('getDefaultUserPresence', () => {
101
+ let store: Store<TLRecord, TLStoreProps>
102
+
103
+ beforeEach(() => {
104
+ const schema = createTLSchema()
105
+ store = new Store({
106
+ schema,
107
+ props: {
108
+ defaultName: 'Test Store',
109
+ assets: {
110
+ upload: vi.fn().mockResolvedValue({ src: 'uploaded-url' }),
111
+ resolve: vi.fn().mockResolvedValue('resolved-url'),
112
+ remove: vi.fn().mockResolvedValue(undefined),
113
+ },
114
+ onMount: vi.fn(),
115
+ },
116
+ })
117
+
118
+ // Initialize store with required records
119
+ const checker = createIntegrityChecker(store)
120
+ checker()
121
+ })
122
+
123
+ afterEach(() => {
124
+ vi.clearAllMocks()
125
+ })
126
+
127
+ describe('basic functionality', () => {
128
+ it('should return presence state with user info and default values', () => {
129
+ const user: TLPresenceUserInfo = {
130
+ id: 'test-user',
131
+ name: 'Test User',
132
+ color: '#00FF00',
133
+ }
134
+
135
+ const presence = getDefaultUserPresence(store, user)
136
+
137
+ expect(presence!.userId).toBe('test-user')
138
+ expect(presence!.userName).toBe('Test User')
139
+ expect(presence!.color).toBe('#00FF00')
140
+ })
141
+
142
+ it('should use defaults for missing user fields', () => {
143
+ const minimalUser: TLPresenceUserInfo = { id: 'minimal' }
144
+ const presence = getDefaultUserPresence(store, minimalUser)
145
+
146
+ expect(presence!.userName).toBe('')
147
+ expect(presence!.color).toBe('#FF0000')
148
+ })
149
+ })
150
+
151
+ describe('error conditions', () => {
152
+ it('should return null when required records are missing', () => {
153
+ store.remove([TLINSTANCE_ID])
154
+ const user: TLPresenceUserInfo = { id: 'test' }
155
+ expect(getDefaultUserPresence(store, user)).toBe(null)
156
+ })
157
+ })
158
+ })
@@ -27,6 +27,29 @@ export interface TLPresenceUserInfo {
27
27
 
28
28
  /**
29
29
  * Creates a derivation that represents the current presence state of the current user.
30
+ *
31
+ * This function returns a derivation factory that, when given a store, creates a computed signal
32
+ * containing the user's current presence state. The presence state includes information like cursor
33
+ * position, selected shapes, camera position, and user metadata that gets synchronized in
34
+ * multiplayer scenarios.
35
+ *
36
+ * @param $user - A reactive signal containing the user information
37
+ * @param instanceId - Optional custom instance ID. If not provided, one will be generated based on the store ID
38
+ * @returns A function that takes a store and returns a computed signal of the user's presence state
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { createPresenceStateDerivation } from '@tldraw/tlschema'
43
+ * import { atom } from '@tldraw/state'
44
+ *
45
+ * const userSignal = atom('user', { id: 'user-123', name: 'Alice', color: '#ff0000' })
46
+ * const presenceDerivation = createPresenceStateDerivation(userSignal)
47
+ *
48
+ * // Use with a store to get reactive presence state
49
+ * const presenceState = presenceDerivation(store)
50
+ * console.log(presenceState.get()) // Current user presence or null
51
+ * ```
52
+ *
30
53
  * @public
31
54
  */
32
55
  export function createPresenceStateDerivation(
@@ -49,10 +72,56 @@ export function createPresenceStateDerivation(
49
72
  }
50
73
  }
51
74
 
52
- /** @public */
75
+ /**
76
+ * The shape of data used to create a presence record.
77
+ *
78
+ * This type represents all the properties needed to construct a TLInstancePresence record.
79
+ * It includes user information, cursor state, camera position, selected shapes, and other
80
+ * presence-related data that gets synchronized across multiplayer clients.
81
+ *
82
+ * @public
83
+ */
53
84
  export type TLPresenceStateInfo = Parameters<(typeof InstancePresenceRecordType)['create']>[0]
54
85
 
55
- /** @public */
86
+ /**
87
+ * Creates default presence state information for a user based on the current store state.
88
+ *
89
+ * This function extracts the current state from various store records (instance, page state,
90
+ * camera, pointer) and combines them with user information to create a complete presence
91
+ * state object. This is commonly used as a starting point for custom presence implementations.
92
+ *
93
+ * @param store - The tldraw store containing the current editor state
94
+ * @param user - The user information to include in the presence state
95
+ * @returns The default presence state info, or null if required store records are missing
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * import { getDefaultUserPresence } from '@tldraw/tlschema'
100
+ *
101
+ * const user = { id: 'user-123', name: 'Alice', color: '#ff0000' }
102
+ * const presenceInfo = getDefaultUserPresence(store, user)
103
+ *
104
+ * if (presenceInfo) {
105
+ * console.log('Current cursor:', presenceInfo.cursor)
106
+ * console.log('Selected shapes:', presenceInfo.selectedShapeIds)
107
+ * console.log('Camera position:', presenceInfo.camera)
108
+ * }
109
+ * ```
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * // Common pattern: customize default presence
114
+ * const customPresence = {
115
+ * ...getDefaultUserPresence(store, user),
116
+ * // Remove camera for privacy
117
+ * camera: undefined,
118
+ * // Add custom metadata
119
+ * customField: 'my-data'
120
+ * }
121
+ * ```
122
+ *
123
+ * @public
124
+ */
56
125
  export function getDefaultUserPresence(store: TLStore, user: TLPresenceUserInfo) {
57
126
  const instance = store.get(TLINSTANCE_ID)
58
127
  const pageState = store.get(InstancePageStateRecordType.createId(instance?.currentPageId))