@tldraw/tlschema 4.1.0-canary.e2133d922c9e → 4.1.0-canary.e259b517a450

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
@@ -10,7 +10,23 @@ import { T } from '@tldraw/validate'
10
10
  import { idValidator } from '../misc/id-validator'
11
11
 
12
12
  /**
13
- * TLPage
13
+ * A page within a tldraw document. Pages are containers for shapes and provide
14
+ * a way to organize content into separate canvases. Each document can have multiple
15
+ * pages, and users can navigate between them.
16
+ *
17
+ * Pages have a name for identification, an index for ordering, and can store
18
+ * custom metadata.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const page: TLPage = {
23
+ * id: 'page:page1',
24
+ * typeName: 'page',
25
+ * name: 'Page 1',
26
+ * index: 'a1',
27
+ * meta: { description: 'Main design page' }
28
+ * }
29
+ * ```
14
30
  *
15
31
  * @public
16
32
  */
@@ -20,13 +36,47 @@ export interface TLPage extends BaseRecord<'page', TLPageId> {
20
36
  meta: JsonObject
21
37
  }
22
38
 
23
- /** @public */
39
+ /**
40
+ * A unique identifier for TLPage records.
41
+ *
42
+ * Page IDs follow the format 'page:' followed by a unique string identifier.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const pageId: TLPageId = 'page:main'
47
+ * const pageId2: TLPageId = createShapeId() // generates 'page:abc123'
48
+ * ```
49
+ *
50
+ * @public
51
+ */
24
52
  export type TLPageId = RecordId<TLPage>
25
53
 
26
- /** @public */
54
+ /**
55
+ * Validator for TLPageId values. Ensures the ID follows the correct
56
+ * format for page records ('page:' prefix).
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const isValid = pageIdValidator.isValid('page:main') // true
61
+ * const isValid2 = pageIdValidator.isValid('shape:abc') // false
62
+ * ```
63
+ *
64
+ * @public
65
+ */
27
66
  export const pageIdValidator = idValidator<TLPageId>('page')
28
67
 
29
- /** @public */
68
+ /**
69
+ * Runtime validator for TLPage records. Validates the structure and types
70
+ * of all page properties to ensure data integrity.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const page = { id: 'page:1', typeName: 'page', name: 'My Page', index: 'a1', meta: {} }
75
+ * const isValid = pageValidator.isValid(page) // true
76
+ * ```
77
+ *
78
+ * @public
79
+ */
30
80
  export const pageValidator: T.Validator<TLPage> = T.model(
31
81
  'page',
32
82
  T.object({
@@ -38,12 +88,28 @@ export const pageValidator: T.Validator<TLPage> = T.model(
38
88
  })
39
89
  )
40
90
 
41
- /** @public */
91
+ /**
92
+ * Migration version identifiers for TLPage records. Each version represents
93
+ * a schema change that requires data transformation when loading older documents.
94
+ *
95
+ * @public
96
+ */
42
97
  export const pageVersions = createMigrationIds('com.tldraw.page', {
43
98
  AddMeta: 1,
44
99
  })
45
100
 
46
- /** @public */
101
+ /**
102
+ * Migration sequence for TLPage records. Defines how to transform page
103
+ * records between different schema versions, ensuring data compatibility.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * // Migrations are applied automatically when loading documents
108
+ * const migratedPage = pageMigrations.migrate(oldPage, targetVersion)
109
+ * ```
110
+ *
111
+ * @public
112
+ */
47
113
  export const pageMigrations = createRecordMigrationSequence({
48
114
  sequenceId: 'com.tldraw.page',
49
115
  recordType: 'page',
@@ -57,7 +123,24 @@ export const pageMigrations = createRecordMigrationSequence({
57
123
  ],
58
124
  })
59
125
 
60
- /** @public */
126
+ /**
127
+ * The RecordType definition for TLPage records. Defines validation, scope,
128
+ * and default properties for page records in the tldraw store.
129
+ *
130
+ * Pages are scoped to the document level, meaning they persist across sessions
131
+ * and are shared in collaborative environments.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const page = PageRecordType.create({
136
+ * id: 'page:main',
137
+ * name: 'Main Page',
138
+ * index: 'a1'
139
+ * })
140
+ * ```
141
+ *
142
+ * @public
143
+ */
61
144
  export const PageRecordType = createRecordType<TLPage>('page', {
62
145
  validator: pageValidator,
63
146
  scope: 'document',
@@ -65,7 +148,22 @@ export const PageRecordType = createRecordType<TLPage>('page', {
65
148
  meta: {},
66
149
  }))
67
150
 
68
- /** @public */
151
+ /**
152
+ * Type guard to check if a string is a valid TLPageId.
153
+ *
154
+ * @param id - The string to check
155
+ * @returns True if the ID is a valid page ID, false otherwise
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * if (isPageId('page:main')) {
160
+ * // TypeScript knows this is a TLPageId
161
+ * console.log('Valid page ID')
162
+ * }
163
+ * ```
164
+ *
165
+ * @public
166
+ */
69
167
  export function isPageId(id: string): id is TLPageId {
70
168
  return PageRecordType.isId(id)
71
169
  }
@@ -0,0 +1,228 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ instancePageStateMigrations,
4
+ InstancePageStateRecordType,
5
+ instancePageStateValidator,
6
+ instancePageStateVersions,
7
+ TLInstancePageStateId,
8
+ } from './TLPageState'
9
+
10
+ describe('instancePageStateValidator', () => {
11
+ it('should reject invalid typeName', () => {
12
+ const invalidPageState = {
13
+ typeName: 'not-instance-page-state',
14
+ id: 'instance_page_state:test' as TLInstancePageStateId,
15
+ pageId: 'page:test' as any,
16
+ selectedShapeIds: [],
17
+ hintingShapeIds: [],
18
+ erasingShapeIds: [],
19
+ hoveredShapeId: null,
20
+ editingShapeId: null,
21
+ croppingShapeId: null,
22
+ focusedGroupId: null,
23
+ meta: {},
24
+ }
25
+
26
+ expect(() => instancePageStateValidator.validate(invalidPageState)).toThrow()
27
+ })
28
+
29
+ it('should reject invalid id format', () => {
30
+ const invalidPageState = {
31
+ typeName: 'instance_page_state',
32
+ id: 'not-valid-id' as TLInstancePageStateId,
33
+ pageId: 'page:test' as any,
34
+ selectedShapeIds: [],
35
+ hintingShapeIds: [],
36
+ erasingShapeIds: [],
37
+ hoveredShapeId: null,
38
+ editingShapeId: null,
39
+ croppingShapeId: null,
40
+ focusedGroupId: null,
41
+ meta: {},
42
+ }
43
+
44
+ expect(() => instancePageStateValidator.validate(invalidPageState)).toThrow()
45
+ })
46
+
47
+ it('should reject missing required fields', () => {
48
+ const incompletePageState = {
49
+ typeName: 'instance_page_state',
50
+ id: 'instance_page_state:test' as TLInstancePageStateId,
51
+ pageId: 'page:test' as any,
52
+ // missing required array fields
53
+ }
54
+
55
+ expect(() => instancePageStateValidator.validate(incompletePageState)).toThrow()
56
+ })
57
+ })
58
+
59
+ describe('instancePageStateMigrations', () => {
60
+ it('should migrate AddCroppingId correctly', () => {
61
+ const migration = instancePageStateMigrations.sequence.find(
62
+ (m) => m.id === instancePageStateVersions.AddCroppingId
63
+ )!
64
+ const oldRecord: any = { id: 'instance_page_state:test', typeName: 'instance_page_state' }
65
+ migration.up(oldRecord)
66
+
67
+ expect(oldRecord.croppingShapeId).toBe(null)
68
+ })
69
+
70
+ it('should migrate RemoveInstanceIdAndCameraId correctly', () => {
71
+ const migration = instancePageStateMigrations.sequence.find(
72
+ (m) => m.id === instancePageStateVersions.RemoveInstanceIdAndCameraId
73
+ )!
74
+ const oldRecord: any = {
75
+ id: 'instance_page_state:test',
76
+ typeName: 'instance_page_state',
77
+ instanceId: 'instance:removed',
78
+ cameraId: 'camera:removed',
79
+ otherProp: 'keep-me',
80
+ }
81
+ migration.up(oldRecord)
82
+
83
+ expect(oldRecord.instanceId).toBeUndefined()
84
+ expect(oldRecord.cameraId).toBeUndefined()
85
+ expect(oldRecord.otherProp).toBe('keep-me')
86
+ })
87
+
88
+ it('should migrate AddMeta correctly', () => {
89
+ const migration = instancePageStateMigrations.sequence.find(
90
+ (m) => m.id === instancePageStateVersions.AddMeta
91
+ )!
92
+ const oldRecord: any = { id: 'instance_page_state:test', typeName: 'instance_page_state' }
93
+ migration.up(oldRecord)
94
+
95
+ expect(oldRecord.meta).toEqual({})
96
+ })
97
+
98
+ it('should handle RenameProperties migration (noop)', () => {
99
+ const migration = instancePageStateMigrations.sequence.find(
100
+ (m) => m.id === instancePageStateVersions.RenameProperties
101
+ )!
102
+ const oldRecord: any = {
103
+ id: 'instance_page_state:test',
104
+ typeName: 'instance_page_state',
105
+ selectedIds: ['shape:1'],
106
+ }
107
+ const originalRecord = { ...oldRecord }
108
+ migration.up(oldRecord)
109
+
110
+ // Should be a noop
111
+ expect(oldRecord).toEqual(originalRecord)
112
+ })
113
+
114
+ it('should migrate RenamePropertiesAgain correctly', () => {
115
+ const migration = instancePageStateMigrations.sequence.find(
116
+ (m) => m.id === instancePageStateVersions.RenamePropertiesAgain
117
+ )!
118
+ const oldRecord: any = {
119
+ id: 'instance_page_state:test',
120
+ typeName: 'instance_page_state',
121
+ selectedIds: ['shape:1', 'shape:2'],
122
+ hintingIds: ['shape:3'],
123
+ erasingIds: ['shape:4'],
124
+ hoveredId: 'shape:5',
125
+ editingId: 'shape:6',
126
+ croppingId: 'shape:7',
127
+ focusLayerId: 'shape:8',
128
+ }
129
+ migration.up(oldRecord)
130
+
131
+ expect(oldRecord.selectedShapeIds).toEqual(['shape:1', 'shape:2'])
132
+ expect(oldRecord.hintingShapeIds).toEqual(['shape:3'])
133
+ expect(oldRecord.erasingShapeIds).toEqual(['shape:4'])
134
+ expect(oldRecord.hoveredShapeId).toBe('shape:5')
135
+ expect(oldRecord.editingShapeId).toBe('shape:6')
136
+ expect(oldRecord.croppingShapeId).toBe('shape:7')
137
+ expect(oldRecord.focusedGroupId).toBe('shape:8')
138
+
139
+ // Old properties should be removed
140
+ expect(oldRecord.selectedIds).toBeUndefined()
141
+ expect(oldRecord.hintingIds).toBeUndefined()
142
+ expect(oldRecord.erasingIds).toBeUndefined()
143
+ expect(oldRecord.hoveredId).toBeUndefined()
144
+ expect(oldRecord.editingId).toBeUndefined()
145
+ expect(oldRecord.croppingId).toBeUndefined()
146
+ expect(oldRecord.focusLayerId).toBeUndefined()
147
+ })
148
+
149
+ it('should handle down migration for RenamePropertiesAgain', () => {
150
+ const migration = instancePageStateMigrations.sequence.find(
151
+ (m) => m.id === instancePageStateVersions.RenamePropertiesAgain
152
+ )!
153
+ expect(migration.down).toBeDefined()
154
+
155
+ const record: any = {
156
+ id: 'instance_page_state:test',
157
+ typeName: 'instance_page_state',
158
+ selectedShapeIds: ['shape:1', 'shape:2'],
159
+ hintingShapeIds: ['shape:3'],
160
+ erasingShapeIds: ['shape:4'],
161
+ hoveredShapeId: 'shape:5',
162
+ editingShapeId: 'shape:6',
163
+ croppingShapeId: 'shape:7',
164
+ focusedGroupId: 'shape:8',
165
+ }
166
+ migration.down!(record)
167
+
168
+ expect(record.selectedIds).toEqual(['shape:1', 'shape:2'])
169
+ expect(record.hintingIds).toEqual(['shape:3'])
170
+ expect(record.erasingIds).toEqual(['shape:4'])
171
+ expect(record.hoveredId).toBe('shape:5')
172
+ expect(record.editingId).toBe('shape:6')
173
+ expect(record.croppingId).toBe('shape:7')
174
+ expect(record.focusLayerId).toBe('shape:8')
175
+
176
+ // New properties should be removed
177
+ expect(record.selectedShapeIds).toBeUndefined()
178
+ expect(record.hintingShapeIds).toBeUndefined()
179
+ expect(record.erasingShapeIds).toBeUndefined()
180
+ expect(record.hoveredShapeId).toBeUndefined()
181
+ expect(record.editingShapeId).toBeUndefined()
182
+ expect(record.croppingShapeId).toBeUndefined()
183
+ expect(record.focusedGroupId).toBeUndefined()
184
+ })
185
+
186
+ it('should handle croppingShapeId fallback in RenamePropertiesAgain', () => {
187
+ const migration = instancePageStateMigrations.sequence.find(
188
+ (m) => m.id === instancePageStateVersions.RenamePropertiesAgain
189
+ )!
190
+
191
+ // Test with existing croppingShapeId
192
+ const recordWithCroppingShapeId: any = {
193
+ croppingShapeId: 'shape:existing',
194
+ croppingId: 'shape:fallback',
195
+ }
196
+ migration.up(recordWithCroppingShapeId)
197
+ expect(recordWithCroppingShapeId.croppingShapeId).toBe('shape:existing')
198
+
199
+ // Test with only croppingId
200
+ const recordWithCroppingId: any = {
201
+ croppingId: 'shape:fallback',
202
+ }
203
+ migration.up(recordWithCroppingId)
204
+ expect(recordWithCroppingId.croppingShapeId).toBe('shape:fallback')
205
+
206
+ // Test with neither
207
+ const recordWithNeither: any = {}
208
+ migration.up(recordWithNeither)
209
+ expect(recordWithNeither.croppingShapeId).toBe(null)
210
+ })
211
+ })
212
+
213
+ describe('InstancePageStateRecordType', () => {
214
+ it('should have correct ephemeral keys configuration', () => {
215
+ // Non-ephemeral keys (persistent)
216
+ expect(InstancePageStateRecordType.ephemeralKeys?.pageId).toBe(false)
217
+ expect(InstancePageStateRecordType.ephemeralKeys?.selectedShapeIds).toBe(false)
218
+ expect(InstancePageStateRecordType.ephemeralKeys?.editingShapeId).toBe(false)
219
+ expect(InstancePageStateRecordType.ephemeralKeys?.croppingShapeId).toBe(false)
220
+ expect(InstancePageStateRecordType.ephemeralKeys?.meta).toBe(false)
221
+
222
+ // Ephemeral keys (temporary)
223
+ expect(InstancePageStateRecordType.ephemeralKeys?.hintingShapeIds).toBe(true)
224
+ expect(InstancePageStateRecordType.ephemeralKeys?.erasingShapeIds).toBe(true)
225
+ expect(InstancePageStateRecordType.ephemeralKeys?.hoveredShapeId).toBe(true)
226
+ expect(InstancePageStateRecordType.ephemeralKeys?.focusedGroupId).toBe(true)
227
+ })
228
+ })
@@ -13,9 +13,25 @@ import { pageIdValidator, TLPage } from './TLPage'
13
13
  import { TLShapeId } from './TLShape'
14
14
 
15
15
  /**
16
- * TLInstancePageState
16
+ * State that is unique to a particular page within a particular browser tab.
17
+ * This record tracks all page-specific interaction state including selected shapes,
18
+ * editing state, hover state, and other transient UI state that is tied to
19
+ * both a specific page and a specific browser session.
17
20
  *
18
- * State that is unique to a particular page of the document in a particular browser tab
21
+ * Each combination of page and browser tab has its own TLInstancePageState record.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const pageState: TLInstancePageState = {
26
+ * id: 'instance_page_state:page1',
27
+ * typeName: 'instance_page_state',
28
+ * pageId: 'page:page1',
29
+ * selectedShapeIds: ['shape:rect1', 'shape:circle2'],
30
+ * hoveredShapeId: 'shape:text3',
31
+ * editingShapeId: null,
32
+ * focusedGroupId: null
33
+ * }
34
+ * ```
19
35
  *
20
36
  * @public
21
37
  */
@@ -32,7 +48,24 @@ export interface TLInstancePageState
32
48
  meta: JsonObject
33
49
  }
34
50
 
35
- /** @public */
51
+ /**
52
+ * Runtime validator for TLInstancePageState records. Validates the structure
53
+ * and types of all instance page state properties to ensure data integrity.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const pageState = {
58
+ * id: 'instance_page_state:page1',
59
+ * typeName: 'instance_page_state',
60
+ * pageId: 'page:page1',
61
+ * selectedShapeIds: ['shape:rect1'],
62
+ * // ... other properties
63
+ * }
64
+ * const isValid = instancePageStateValidator.isValid(pageState) // true
65
+ * ```
66
+ *
67
+ * @public
68
+ */
36
69
  export const instancePageStateValidator: T.Validator<TLInstancePageState> = T.model(
37
70
  'instance_page_state',
38
71
  T.object({
@@ -50,7 +83,13 @@ export const instancePageStateValidator: T.Validator<TLInstancePageState> = T.mo
50
83
  })
51
84
  )
52
85
 
53
- /** @public */
86
+ /**
87
+ * Migration version identifiers for TLInstancePageState records. Each version
88
+ * represents a schema change that requires data transformation when loading
89
+ * older documents.
90
+ *
91
+ * @public
92
+ */
54
93
  export const instancePageStateVersions = createMigrationIds('com.tldraw.instance_page_state', {
55
94
  AddCroppingId: 1,
56
95
  RemoveInstanceIdAndCameraId: 2,
@@ -59,7 +98,19 @@ export const instancePageStateVersions = createMigrationIds('com.tldraw.instance
59
98
  RenamePropertiesAgain: 5,
60
99
  } as const)
61
100
 
62
- /** @public */
101
+ /**
102
+ * Migration sequence for TLInstancePageState records. Defines how to transform
103
+ * instance page state records between different schema versions, ensuring data
104
+ * compatibility when loading documents created with different versions.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // Migrations are applied automatically when loading documents
109
+ * const migrated = instancePageStateMigrations.migrate(oldState, targetVersion)
110
+ * ```
111
+ *
112
+ * @public
113
+ */
63
114
  export const instancePageStateMigrations = createRecordMigrationSequence({
64
115
  sequenceId: 'com.tldraw.instance_page_state',
65
116
  recordType: 'instance_page_state',
@@ -132,7 +183,25 @@ export const instancePageStateMigrations = createRecordMigrationSequence({
132
183
  ],
133
184
  })
134
185
 
135
- /** @public */
186
+ /**
187
+ * The RecordType definition for TLInstancePageState records. Defines validation,
188
+ * scope, and default properties for instance page state records.
189
+ *
190
+ * Instance page states are scoped to the session level, meaning they are
191
+ * specific to a browser tab and don't persist across sessions or sync
192
+ * in collaborative environments.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const pageState = InstancePageStateRecordType.create({
197
+ * id: 'instance_page_state:page1',
198
+ * pageId: 'page:page1',
199
+ * selectedShapeIds: ['shape:rect1']
200
+ * })
201
+ * ```
202
+ *
203
+ * @public
204
+ */
136
205
  export const InstancePageStateRecordType = createRecordType<TLInstancePageState>(
137
206
  'instance_page_state',
138
207
  {
@@ -164,5 +233,17 @@ export const InstancePageStateRecordType = createRecordType<TLInstancePageState>
164
233
  })
165
234
  )
166
235
 
167
- /** @public */
236
+ /**
237
+ * A unique identifier for TLInstancePageState records.
238
+ *
239
+ * Instance page state IDs follow the format 'instance_page_state:' followed
240
+ * by a unique identifier, typically related to the page ID.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const stateId: TLInstancePageStateId = 'instance_page_state:page1'
245
+ * ```
246
+ *
247
+ * @public
248
+ */
168
249
  export type TLInstancePageStateId = RecordId<TLInstancePageState>
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { pointerMigrations, pointerValidator, pointerVersions, TLPOINTER_ID } from './TLPointer'
3
+
4
+ describe('pointerValidator', () => {
5
+ it('should validate valid pointer records', () => {
6
+ const validPointer = {
7
+ typeName: 'pointer',
8
+ id: TLPOINTER_ID,
9
+ x: 100,
10
+ y: 200,
11
+ lastActivityTimestamp: Date.now(),
12
+ meta: {},
13
+ }
14
+
15
+ expect(() => pointerValidator.validate(validPointer)).not.toThrow()
16
+ })
17
+
18
+ it('should reject pointers with invalid typeName', () => {
19
+ const invalidPointer = {
20
+ typeName: 'not-pointer',
21
+ id: TLPOINTER_ID,
22
+ x: 0,
23
+ y: 0,
24
+ lastActivityTimestamp: 0,
25
+ meta: {},
26
+ }
27
+
28
+ expect(() => pointerValidator.validate(invalidPointer)).toThrow()
29
+ })
30
+
31
+ it('should reject pointers with missing required fields', () => {
32
+ const incompletePointer = {
33
+ typeName: 'pointer',
34
+ id: TLPOINTER_ID,
35
+ x: 0,
36
+ // missing y, lastActivityTimestamp, meta
37
+ }
38
+
39
+ expect(() => pointerValidator.validate(incompletePointer)).toThrow()
40
+ })
41
+ })
42
+
43
+ describe('pointerMigrations', () => {
44
+ it('should apply AddMeta migration correctly', () => {
45
+ const addMetaMigration = pointerMigrations.sequence.find(
46
+ (m) => m.id === pointerVersions.AddMeta
47
+ )!
48
+
49
+ const oldRecord: any = {
50
+ typeName: 'pointer',
51
+ id: TLPOINTER_ID,
52
+ x: 100,
53
+ y: 200,
54
+ lastActivityTimestamp: 123456,
55
+ }
56
+
57
+ addMetaMigration.up(oldRecord)
58
+ expect(oldRecord.meta).toEqual({})
59
+ expect(oldRecord.x).toBe(100)
60
+ expect(oldRecord.y).toBe(200)
61
+ expect(oldRecord.lastActivityTimestamp).toBe(123456)
62
+ })
63
+ })