@tldraw/tlschema 4.1.0-next.542f014c3fac → 4.1.0-next.58b63dd1ac80
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
|
@@ -8,28 +8,96 @@ import { RecordProps } from '../recordsWithProps'
|
|
|
8
8
|
import { arrowShapeVersions } from '../shapes/TLArrowShape'
|
|
9
9
|
import { TLBaseBinding } from './TLBaseBinding'
|
|
10
10
|
|
|
11
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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))
|