@tldraw/tlschema 4.2.1 → 4.2.2
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/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +242 -71
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +1 -5
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +5 -1
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/b64Vecs.js +224 -0
- package/dist-cjs/misc/b64Vecs.js.map +7 -0
- package/dist-cjs/records/TLAsset.js.map +1 -1
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js +26 -13
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +37 -4
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +17 -0
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +12 -1
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +29 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +12 -1
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +12 -1
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/store-migrations.js +15 -15
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +242 -71
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +1 -5
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +5 -1
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/b64Vecs.mjs +204 -0
- package/dist-esm/misc/b64Vecs.mjs.map +7 -0
- package/dist-esm/records/TLAsset.mjs.map +1 -1
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs +26 -13
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +37 -4
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +17 -0
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +12 -1
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +29 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +12 -1
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +12 -1
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs +15 -15
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/package.json +8 -8
- package/src/__tests__/migrationTestUtils.ts +9 -3
- package/src/bindings/TLBaseBinding.ts +25 -14
- package/src/createTLSchema.ts +8 -2
- package/src/index.ts +9 -0
- package/src/migrations.test.ts +149 -1
- package/src/misc/TLOpacity.ts +1 -5
- package/src/misc/TLRichText.ts +6 -1
- package/src/misc/b64Vecs.ts +308 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.ts +65 -23
- package/src/records/TLShape.ts +100 -5
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.ts +28 -14
- package/src/shapes/TLBaseShape.ts +34 -10
- package/src/shapes/TLDrawShape.ts +59 -12
- package/src/shapes/TLEmbedShape.ts +17 -0
- package/src/shapes/TLGeoShape.ts +14 -1
- package/src/shapes/TLHighlightShape.ts +37 -0
- package/src/shapes/TLNoteShape.ts +15 -1
- package/src/shapes/TLTextShape.ts +16 -2
- package/src/store-migrations.ts +17 -16
- package/src/assets/TLBookmarkAsset.test.ts +0 -96
- package/src/assets/TLImageAsset.test.ts +0 -213
- package/src/assets/TLVideoAsset.test.ts +0 -105
- package/src/bindings/TLArrowBinding.test.ts +0 -55
- package/src/misc/id-validator.test.ts +0 -50
- package/src/records/TLAsset.test.ts +0 -234
- package/src/records/TLBinding.test.ts +0 -22
- package/src/records/TLCamera.test.ts +0 -19
- package/src/records/TLDocument.test.ts +0 -35
- package/src/records/TLInstance.test.ts +0 -201
- package/src/records/TLPage.test.ts +0 -110
- package/src/records/TLPageState.test.ts +0 -228
- package/src/records/TLPointer.test.ts +0 -63
- package/src/records/TLPresence.test.ts +0 -190
- package/src/records/TLRecord.test.ts +0 -70
- package/src/records/TLShape.test.ts +0 -232
- package/src/shapes/ShapeWithCrop.test.ts +0 -18
- package/src/shapes/TLArrowShape.test.ts +0 -505
- package/src/shapes/TLBaseShape.test.ts +0 -142
- package/src/shapes/TLBookmarkShape.test.ts +0 -122
- package/src/shapes/TLDrawShape.test.ts +0 -177
- package/src/shapes/TLEmbedShape.test.ts +0 -286
- package/src/shapes/TLFrameShape.test.ts +0 -71
- package/src/shapes/TLGeoShape.test.ts +0 -247
- package/src/shapes/TLGroupShape.test.ts +0 -59
- package/src/shapes/TLHighlightShape.test.ts +0 -325
- package/src/shapes/TLImageShape.test.ts +0 -534
- package/src/shapes/TLLineShape.test.ts +0 -269
- package/src/shapes/TLNoteShape.test.ts +0 -1568
- package/src/shapes/TLTextShape.test.ts +0 -407
- package/src/shapes/TLVideoShape.test.ts +0 -112
- package/src/styles/TLColorStyle.test.ts +0 -439
package/src/misc/TLOpacity.ts
CHANGED
|
@@ -53,8 +53,4 @@ export type TLOpacityType = number
|
|
|
53
53
|
*
|
|
54
54
|
* @public
|
|
55
55
|
*/
|
|
56
|
-
export const opacityValidator = T.
|
|
57
|
-
if (n < 0 || n > 1) {
|
|
58
|
-
throw new T.ValidationError('Opacity must be between 0 and 1')
|
|
59
|
-
}
|
|
60
|
-
})
|
|
56
|
+
export const opacityValidator = T.unitInterval
|
package/src/misc/TLRichText.ts
CHANGED
|
@@ -12,7 +12,12 @@ import { T } from '@tldraw/validate'
|
|
|
12
12
|
* const isValid = richTextValidator.check(richText) // true
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
export const richTextValidator = T.object({
|
|
17
|
+
type: T.string,
|
|
18
|
+
content: T.arrayOf(T.unknown),
|
|
19
|
+
attrs: T.any.optional(),
|
|
20
|
+
})
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Type representing rich text content in tldraw. Rich text follows a document-based
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { VecModel } from './geometry-types'
|
|
2
|
+
|
|
3
|
+
// Each point = 3 Float16s = 6 bytes = 8 base64 chars
|
|
4
|
+
const POINT_B64_LENGTH = 8
|
|
5
|
+
|
|
6
|
+
// O(1) lookup table for base64 decoding (maps char code -> 6-bit value)
|
|
7
|
+
const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
8
|
+
const B64_LOOKUP = new Uint8Array(128)
|
|
9
|
+
for (let i = 0; i < 64; i++) {
|
|
10
|
+
B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Precomputed powers of 2 for Float16 exponents (exp - 15, so indices 0-30 map to 2^-15 to 2^15)
|
|
14
|
+
const POW2 = new Float64Array(31)
|
|
15
|
+
for (let i = 0; i < 31; i++) {
|
|
16
|
+
POW2[i] = Math.pow(2, i - 15)
|
|
17
|
+
}
|
|
18
|
+
const POW2_SUBNORMAL = Math.pow(2, -14) / 1024 // For subnormal numbers
|
|
19
|
+
|
|
20
|
+
// Precomputed mantissa values: 1 + frac/1024 for all 1024 possible frac values
|
|
21
|
+
// Avoids division in hot path
|
|
22
|
+
const MANTISSA = new Float64Array(1024)
|
|
23
|
+
for (let i = 0; i < 1024; i++) {
|
|
24
|
+
MANTISSA[i] = 1 + i / 1024
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert a Uint16Array (containing Float16 bits) to base64.
|
|
29
|
+
* Processes bytes in groups of 3 to produce 4 base64 characters.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
function uint16ArrayToBase64(uint16Array: Uint16Array): string {
|
|
34
|
+
const uint8Array = new Uint8Array(
|
|
35
|
+
uint16Array.buffer,
|
|
36
|
+
uint16Array.byteOffset,
|
|
37
|
+
uint16Array.byteLength
|
|
38
|
+
)
|
|
39
|
+
let result = ''
|
|
40
|
+
|
|
41
|
+
// Process bytes in groups of 3 -> 4 base64 chars
|
|
42
|
+
for (let i = 0; i < uint8Array.length; i += 3) {
|
|
43
|
+
const byte1 = uint8Array[i]
|
|
44
|
+
const byte2 = uint8Array[i + 1] // Always exists for our use case (multiple of 6 bytes)
|
|
45
|
+
const byte3 = uint8Array[i + 2]
|
|
46
|
+
|
|
47
|
+
const bitmap = (byte1 << 16) | (byte2 << 8) | byte3
|
|
48
|
+
result +=
|
|
49
|
+
BASE64_CHARS[(bitmap >> 18) & 63] +
|
|
50
|
+
BASE64_CHARS[(bitmap >> 12) & 63] +
|
|
51
|
+
BASE64_CHARS[(bitmap >> 6) & 63] +
|
|
52
|
+
BASE64_CHARS[bitmap & 63]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert a base64 string to Uint16Array containing Float16 bits.
|
|
60
|
+
* The base64 string must have a length that is a multiple of 4.
|
|
61
|
+
*
|
|
62
|
+
* @param base64 - The base64-encoded string to decode
|
|
63
|
+
* @returns A Uint16Array containing the decoded Float16 bit values
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
function base64ToUint16Array(base64: string): Uint16Array {
|
|
67
|
+
// Calculate exact number of bytes (4 base64 chars = 3 bytes)
|
|
68
|
+
const numBytes = Math.floor((base64.length * 3) / 4)
|
|
69
|
+
const bytes = new Uint8Array(numBytes)
|
|
70
|
+
let byteIndex = 0
|
|
71
|
+
|
|
72
|
+
// Process in groups of 4 base64 characters
|
|
73
|
+
for (let i = 0; i < base64.length; i += 4) {
|
|
74
|
+
const c0 = B64_LOOKUP[base64.charCodeAt(i)]
|
|
75
|
+
const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)]
|
|
76
|
+
const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)]
|
|
77
|
+
const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)]
|
|
78
|
+
|
|
79
|
+
const bitmap = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3
|
|
80
|
+
|
|
81
|
+
bytes[byteIndex++] = (bitmap >> 16) & 255
|
|
82
|
+
bytes[byteIndex++] = (bitmap >> 8) & 255
|
|
83
|
+
bytes[byteIndex++] = bitmap & 255
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert Float16 bits to a number using optimized lookup tables.
|
|
91
|
+
* Handles normal numbers, subnormal numbers, zero, infinity, and NaN.
|
|
92
|
+
*
|
|
93
|
+
* @param bits - The 16-bit Float16 value to decode
|
|
94
|
+
* @returns The decoded number value
|
|
95
|
+
*/
|
|
96
|
+
function float16BitsToNumber(bits: number): number {
|
|
97
|
+
const sign = bits >> 15
|
|
98
|
+
const exp = (bits >> 10) & 0x1f
|
|
99
|
+
const frac = bits & 0x3ff
|
|
100
|
+
|
|
101
|
+
if (exp === 0) {
|
|
102
|
+
// Subnormal or zero - rare case
|
|
103
|
+
return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL
|
|
104
|
+
}
|
|
105
|
+
if (exp === 31) {
|
|
106
|
+
// Infinity or NaN - very rare
|
|
107
|
+
return frac ? NaN : sign ? -Infinity : Infinity
|
|
108
|
+
}
|
|
109
|
+
// Normal case - two table lookups, one multiply, no division
|
|
110
|
+
const magnitude = POW2[exp] * MANTISSA[frac]
|
|
111
|
+
return sign ? -magnitude : magnitude
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Convert a number to Float16 bits.
|
|
116
|
+
* Handles normal numbers, subnormal numbers, zero, infinity, and NaN.
|
|
117
|
+
*
|
|
118
|
+
* @param value - The number to encode as Float16
|
|
119
|
+
* @returns The 16-bit Float16 representation of the number
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
function numberToFloat16Bits(value: number): number {
|
|
123
|
+
if (value === 0) return Object.is(value, -0) ? 0x8000 : 0
|
|
124
|
+
if (!Number.isFinite(value)) {
|
|
125
|
+
if (Number.isNaN(value)) return 0x7e00
|
|
126
|
+
return value > 0 ? 0x7c00 : 0xfc00
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const sign = value < 0 ? 1 : 0
|
|
130
|
+
value = Math.abs(value)
|
|
131
|
+
|
|
132
|
+
// Find exponent and mantissa
|
|
133
|
+
const exp = Math.floor(Math.log2(value))
|
|
134
|
+
let expBiased = exp + 15
|
|
135
|
+
|
|
136
|
+
if (expBiased >= 31) {
|
|
137
|
+
// Overflow to infinity
|
|
138
|
+
return (sign << 15) | 0x7c00
|
|
139
|
+
}
|
|
140
|
+
if (expBiased <= 0) {
|
|
141
|
+
// Subnormal or underflow
|
|
142
|
+
const frac = Math.round(value * Math.pow(2, 14) * 1024)
|
|
143
|
+
return (sign << 15) | (frac & 0x3ff)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Normal number
|
|
147
|
+
const mantissa = value / Math.pow(2, exp) - 1
|
|
148
|
+
let frac = Math.round(mantissa * 1024)
|
|
149
|
+
|
|
150
|
+
// Handle rounding overflow: if frac rounds to 1024, increment exponent
|
|
151
|
+
if (frac >= 1024) {
|
|
152
|
+
frac = 0
|
|
153
|
+
expBiased++
|
|
154
|
+
if (expBiased >= 31) {
|
|
155
|
+
// Overflow to infinity
|
|
156
|
+
return (sign << 15) | 0x7c00
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (sign << 15) | (expBiased << 10) | frac
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Utilities for encoding and decoding points using base64 and Float16 encoding.
|
|
165
|
+
* Provides functions for converting between VecModel arrays and compact base64 strings,
|
|
166
|
+
* as well as individual point encoding/decoding operations.
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
169
|
+
*/
|
|
170
|
+
export class b64Vecs {
|
|
171
|
+
/**
|
|
172
|
+
* Encode a single point (x, y, z) to 8 base64 characters.
|
|
173
|
+
* Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
|
|
174
|
+
*
|
|
175
|
+
* @param x - The x coordinate
|
|
176
|
+
* @param y - The y coordinate
|
|
177
|
+
* @param z - The z coordinate
|
|
178
|
+
* @returns An 8-character base64 string representing the point
|
|
179
|
+
*/
|
|
180
|
+
static encodePoint(x: number, y: number, z: number): string {
|
|
181
|
+
const xBits = numberToFloat16Bits(x)
|
|
182
|
+
const yBits = numberToFloat16Bits(y)
|
|
183
|
+
const zBits = numberToFloat16Bits(z)
|
|
184
|
+
|
|
185
|
+
// Convert Float16 bits to 6 bytes (little-endian)
|
|
186
|
+
const b0 = xBits & 0xff
|
|
187
|
+
const b1 = (xBits >> 8) & 0xff
|
|
188
|
+
const b2 = yBits & 0xff
|
|
189
|
+
const b3 = (yBits >> 8) & 0xff
|
|
190
|
+
const b4 = zBits & 0xff
|
|
191
|
+
const b5 = (zBits >> 8) & 0xff
|
|
192
|
+
|
|
193
|
+
// Convert 6 bytes to 8 base64 chars
|
|
194
|
+
const bitmap1 = (b0 << 16) | (b1 << 8) | b2
|
|
195
|
+
const bitmap2 = (b3 << 16) | (b4 << 8) | b5
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
BASE64_CHARS[(bitmap1 >> 18) & 0x3f] +
|
|
199
|
+
BASE64_CHARS[(bitmap1 >> 12) & 0x3f] +
|
|
200
|
+
BASE64_CHARS[(bitmap1 >> 6) & 0x3f] +
|
|
201
|
+
BASE64_CHARS[bitmap1 & 0x3f] +
|
|
202
|
+
BASE64_CHARS[(bitmap2 >> 18) & 0x3f] +
|
|
203
|
+
BASE64_CHARS[(bitmap2 >> 12) & 0x3f] +
|
|
204
|
+
BASE64_CHARS[(bitmap2 >> 6) & 0x3f] +
|
|
205
|
+
BASE64_CHARS[bitmap2 & 0x3f]
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Convert an array of VecModels to a base64 string for compact storage.
|
|
211
|
+
* Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
|
|
212
|
+
* undefined, it defaults to 0.5.
|
|
213
|
+
*
|
|
214
|
+
* @param points - An array of VecModel objects to encode
|
|
215
|
+
* @returns A base64-encoded string containing all points
|
|
216
|
+
*/
|
|
217
|
+
static encodePoints(points: VecModel[]): string {
|
|
218
|
+
const uint16s = new Uint16Array(points.length * 3)
|
|
219
|
+
for (let i = 0; i < points.length; i++) {
|
|
220
|
+
const p = points[i]
|
|
221
|
+
uint16s[i * 3] = numberToFloat16Bits(p.x)
|
|
222
|
+
uint16s[i * 3 + 1] = numberToFloat16Bits(p.y)
|
|
223
|
+
uint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5)
|
|
224
|
+
}
|
|
225
|
+
return uint16ArrayToBase64(uint16s)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Convert a base64 string back to an array of VecModels.
|
|
230
|
+
* Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
|
|
231
|
+
*
|
|
232
|
+
* @param base64 - The base64-encoded string containing point data
|
|
233
|
+
* @returns An array of VecModel objects decoded from the string
|
|
234
|
+
*/
|
|
235
|
+
static decodePoints(base64: string): VecModel[] {
|
|
236
|
+
const uint16s = base64ToUint16Array(base64)
|
|
237
|
+
const result: VecModel[] = []
|
|
238
|
+
for (let i = 0; i < uint16s.length; i += 3) {
|
|
239
|
+
result.push({
|
|
240
|
+
x: float16BitsToNumber(uint16s[i]),
|
|
241
|
+
y: float16BitsToNumber(uint16s[i + 1]),
|
|
242
|
+
z: float16BitsToNumber(uint16s[i + 2]),
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
return result
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Decode a single point (8 base64 chars) starting at the given offset.
|
|
250
|
+
* Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.
|
|
251
|
+
*
|
|
252
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
253
|
+
* @param charOffset - The character offset where the point starts (must be a multiple of 8)
|
|
254
|
+
* @returns A VecModel object with x, y, and z coordinates
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
static decodePointAt(b64Points: string, charOffset: number): VecModel {
|
|
258
|
+
// Decode 8 base64 chars -> 6 bytes -> 3 Float16s using O(1) lookup
|
|
259
|
+
const c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)]
|
|
260
|
+
const c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)]
|
|
261
|
+
const c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)]
|
|
262
|
+
const c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)]
|
|
263
|
+
const c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)]
|
|
264
|
+
const c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)]
|
|
265
|
+
const c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)]
|
|
266
|
+
const c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)]
|
|
267
|
+
|
|
268
|
+
// 4 base64 chars -> 24 bits -> 3 bytes
|
|
269
|
+
const bitmap1 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3
|
|
270
|
+
const bitmap2 = (c4 << 18) | (c5 << 12) | (c6 << 6) | c7
|
|
271
|
+
|
|
272
|
+
// Extract Float16 bits directly (little-endian byte order)
|
|
273
|
+
// bitmap1 = [byte0:8][byte1:8][byte2:8], bitmap2 = [byte3:8][byte4:8][byte5:8]
|
|
274
|
+
// xBits = byte0 | (byte1 << 8), yBits = byte2 | (byte3 << 8), zBits = byte4 | (byte5 << 8)
|
|
275
|
+
const xBits = ((bitmap1 >> 16) & 0xff) | (bitmap1 & 0xff00)
|
|
276
|
+
const yBits = (bitmap1 & 0xff) | ((bitmap2 >> 8) & 0xff00)
|
|
277
|
+
const zBits = ((bitmap2 >> 8) & 0xff) | ((bitmap2 << 8) & 0xff00)
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
x: float16BitsToNumber(xBits),
|
|
281
|
+
y: float16BitsToNumber(yBits),
|
|
282
|
+
z: float16BitsToNumber(zBits),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get the first point from a base64-encoded string of points.
|
|
288
|
+
*
|
|
289
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
290
|
+
* @returns The first point as a VecModel, or null if the string is too short
|
|
291
|
+
* @public
|
|
292
|
+
*/
|
|
293
|
+
static decodeFirstPoint(b64Points: string): VecModel | null {
|
|
294
|
+
if (b64Points.length < POINT_B64_LENGTH) return null
|
|
295
|
+
return b64Vecs.decodePointAt(b64Points, 0)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get the last point from a base64-encoded string of points.
|
|
300
|
+
*
|
|
301
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
302
|
+
* @returns The last point as a VecModel, or null if the string is too short
|
|
303
|
+
*/
|
|
304
|
+
static decodeLastPoint(b64Points: string): VecModel | null {
|
|
305
|
+
if (b64Points.length < POINT_B64_LENGTH) return null
|
|
306
|
+
return b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH)
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/records/TLAsset.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { TLBaseAsset } from '../assets/TLBaseAsset'
|
|
|
9
9
|
import { bookmarkAssetValidator, TLBookmarkAsset } from '../assets/TLBookmarkAsset'
|
|
10
10
|
import { imageAssetValidator, TLImageAsset } from '../assets/TLImageAsset'
|
|
11
11
|
import { TLVideoAsset, videoAssetValidator } from '../assets/TLVideoAsset'
|
|
12
|
-
import {
|
|
12
|
+
import { ExtractShapeByProps } from './TLShape'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Union type representing all possible asset types in tldraw.
|
|
@@ -222,4 +222,4 @@ export type TLAssetId = RecordId<TLBaseAsset<any, any>>
|
|
|
222
222
|
*
|
|
223
223
|
* @public
|
|
224
224
|
*/
|
|
225
|
-
export type TLAssetShape =
|
|
225
|
+
export type TLAssetShape = ExtractShapeByProps<{ assetId: TLAssetId }>
|
package/src/records/TLBinding.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createRecordMigrationSequence,
|
|
6
6
|
createRecordType,
|
|
7
7
|
} from '@tldraw/store'
|
|
8
|
-
import {
|
|
8
|
+
import { mapObjectMapValues, uniqueId } from '@tldraw/utils'
|
|
9
9
|
import { T } from '@tldraw/validate'
|
|
10
10
|
import { TLArrowBinding } from '../bindings/TLArrowBinding'
|
|
11
11
|
import { TLBaseBinding, createBindingValidator } from '../bindings/TLBaseBinding'
|
|
@@ -56,10 +56,42 @@ export type TLDefaultBinding = TLArrowBinding
|
|
|
56
56
|
*/
|
|
57
57
|
export type TLUnknownBinding = TLBaseBinding<string, object>
|
|
58
58
|
|
|
59
|
+
/** @public */
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
61
|
+
export interface TLGlobalBindingPropsMap {}
|
|
62
|
+
|
|
63
|
+
/** @public */
|
|
64
|
+
// prettier-ignore
|
|
65
|
+
export type TLIndexedBindings = {
|
|
66
|
+
// We iterate over a union of augmented keys and default binding types.
|
|
67
|
+
// This allows us to include (or conditionally exclude or override) the default bindings in one go.
|
|
68
|
+
//
|
|
69
|
+
// In the `as` clause we are filtering out disabled bindings.
|
|
70
|
+
[K in keyof TLGlobalBindingPropsMap | TLDefaultBinding['type'] as K extends TLDefaultBinding['type']
|
|
71
|
+
? K extends keyof TLGlobalBindingPropsMap
|
|
72
|
+
? // if it extends a nullish value the user has disabled this binding type so we filter it out with never
|
|
73
|
+
TLGlobalBindingPropsMap[K] extends null | undefined
|
|
74
|
+
? never
|
|
75
|
+
: K
|
|
76
|
+
: K
|
|
77
|
+
: K]: K extends TLDefaultBinding['type']
|
|
78
|
+
? // if it's a default binding type we need to check if it's been overridden
|
|
79
|
+
K extends keyof TLGlobalBindingPropsMap
|
|
80
|
+
? // if it has been overriden then use the custom binding definition
|
|
81
|
+
TLBaseBinding<K, TLGlobalBindingPropsMap[K]>
|
|
82
|
+
: // if it has not been overriden then reuse existing type aliases for better type display
|
|
83
|
+
Extract<TLDefaultBinding, { type: K }>
|
|
84
|
+
: // use the custom binding definition
|
|
85
|
+
TLBaseBinding<K, TLGlobalBindingPropsMap[K & keyof TLGlobalBindingPropsMap]>
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
/**
|
|
60
|
-
* The set of all bindings that are available in the editor
|
|
89
|
+
* The set of all bindings that are available in the editor.
|
|
61
90
|
* Bindings represent relationships between shapes, such as arrows connecting to other shapes.
|
|
62
91
|
*
|
|
92
|
+
* You can use this type without a type argument to work with any binding, or pass
|
|
93
|
+
* a specific binding type string (e.g., `'arrow'`) to narrow down to that specific binding type.
|
|
94
|
+
*
|
|
63
95
|
* @example
|
|
64
96
|
* ```ts
|
|
65
97
|
* // Check binding type and handle accordingly
|
|
@@ -73,11 +105,17 @@ export type TLUnknownBinding = TLBaseBinding<string, object>
|
|
|
73
105
|
* break
|
|
74
106
|
* }
|
|
75
107
|
* }
|
|
108
|
+
*
|
|
109
|
+
* // Narrow to a specific binding type by passing the type as a generic argument
|
|
110
|
+
* function getArrowSourceId(binding: TLBinding<'arrow'>) {
|
|
111
|
+
* return binding.fromId // TypeScript knows this is a TLArrowBinding
|
|
112
|
+
* }
|
|
76
113
|
* ```
|
|
77
114
|
*
|
|
78
115
|
* @public
|
|
79
116
|
*/
|
|
80
|
-
export type TLBinding =
|
|
117
|
+
export type TLBinding<K extends keyof TLIndexedBindings = keyof TLIndexedBindings> =
|
|
118
|
+
TLIndexedBindings[K]
|
|
81
119
|
|
|
82
120
|
/**
|
|
83
121
|
* Type for updating existing bindings with partial properties.
|
|
@@ -99,15 +137,17 @@ export type TLBinding = TLDefaultBinding | TLUnknownBinding
|
|
|
99
137
|
*
|
|
100
138
|
* @public
|
|
101
139
|
*/
|
|
102
|
-
export type TLBindingUpdate<T extends TLBinding = TLBinding> =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
export type TLBindingUpdate<T extends TLBinding = TLBinding> = T extends T
|
|
141
|
+
? {
|
|
142
|
+
id: TLBindingId
|
|
143
|
+
type: T['type']
|
|
144
|
+
typeName?: T['typeName']
|
|
145
|
+
fromId?: T['fromId']
|
|
146
|
+
toId?: T['toId']
|
|
147
|
+
props?: Partial<T['props']>
|
|
148
|
+
meta?: Partial<T['meta']>
|
|
149
|
+
}
|
|
150
|
+
: never
|
|
111
151
|
|
|
112
152
|
/**
|
|
113
153
|
* Type for creating new bindings with required fromId and toId.
|
|
@@ -133,15 +173,17 @@ export type TLBindingUpdate<T extends TLBinding = TLBinding> = Expand<{
|
|
|
133
173
|
*
|
|
134
174
|
* @public
|
|
135
175
|
*/
|
|
136
|
-
export type TLBindingCreate<T extends TLBinding = TLBinding> =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
176
|
+
export type TLBindingCreate<T extends TLBinding = TLBinding> = T extends T
|
|
177
|
+
? {
|
|
178
|
+
id?: TLBindingId
|
|
179
|
+
type: T['type']
|
|
180
|
+
typeName?: T['typeName']
|
|
181
|
+
fromId: T['fromId']
|
|
182
|
+
toId: T['toId']
|
|
183
|
+
props?: Partial<T['props']>
|
|
184
|
+
meta?: Partial<T['meta']>
|
|
185
|
+
}
|
|
186
|
+
: never
|
|
145
187
|
|
|
146
188
|
/**
|
|
147
189
|
* Branded string type for binding record identifiers.
|
|
@@ -166,7 +208,7 @@ export type TLBindingCreate<T extends TLBinding = TLBinding> = Expand<{
|
|
|
166
208
|
*
|
|
167
209
|
* @public
|
|
168
210
|
*/
|
|
169
|
-
export type TLBindingId = RecordId<
|
|
211
|
+
export type TLBindingId = RecordId<TLBinding>
|
|
170
212
|
|
|
171
213
|
/**
|
|
172
214
|
* Migration version identifiers for the root binding record schema.
|
|
@@ -375,7 +417,7 @@ export function createBindingPropsMigrationIds<S extends string, T extends Recor
|
|
|
375
417
|
* @internal
|
|
376
418
|
*/
|
|
377
419
|
export function createBindingRecordType(bindings: Record<string, SchemaPropsInfo>) {
|
|
378
|
-
return createRecordType
|
|
420
|
+
return createRecordType('binding', {
|
|
379
421
|
scope: 'document',
|
|
380
422
|
validator: T.model(
|
|
381
423
|
'binding',
|
package/src/records/TLShape.ts
CHANGED
|
@@ -79,12 +79,51 @@ export type TLDefaultShape =
|
|
|
79
79
|
*/
|
|
80
80
|
export type TLUnknownShape = TLBaseShape<string, object>
|
|
81
81
|
|
|
82
|
+
/** @public */
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
84
|
+
export interface TLGlobalShapePropsMap {}
|
|
85
|
+
|
|
86
|
+
/** @public */
|
|
87
|
+
// prettier-ignore
|
|
88
|
+
export type TLIndexedShapes = {
|
|
89
|
+
// We iterate over a union of augmented keys and default shape types.
|
|
90
|
+
// This allows us to include (or conditionally exclude or override) the default shapes in one go.
|
|
91
|
+
//
|
|
92
|
+
// In the `as` clause we are filtering out disabled shapes.
|
|
93
|
+
[K in keyof TLGlobalShapePropsMap | TLDefaultShape['type'] as K extends TLDefaultShape['type']
|
|
94
|
+
? // core shapes are always available and cannot be overridden so we just include them
|
|
95
|
+
K extends 'group'
|
|
96
|
+
? K
|
|
97
|
+
: K extends keyof TLGlobalShapePropsMap
|
|
98
|
+
? // if it extends a nullish value the user has disabled this shape type so we filter it out with never
|
|
99
|
+
TLGlobalShapePropsMap[K] extends null | undefined
|
|
100
|
+
? never
|
|
101
|
+
: K
|
|
102
|
+
: K
|
|
103
|
+
: K]: K extends 'group'
|
|
104
|
+
? // core shapes are always available and cannot be overridden so we just include them
|
|
105
|
+
Extract<TLDefaultShape, { type: K }>
|
|
106
|
+
: K extends TLDefaultShape['type']
|
|
107
|
+
? // if it's a default shape type we need to check if it's been overridden
|
|
108
|
+
K extends keyof TLGlobalShapePropsMap
|
|
109
|
+
? // if it has been overriden then use the custom shape definition
|
|
110
|
+
TLBaseShape<K, TLGlobalShapePropsMap[K]>
|
|
111
|
+
: // if it has not been overriden then reuse existing type aliases for better type display
|
|
112
|
+
Extract<TLDefaultShape, { type: K }>
|
|
113
|
+
: // use the custom shape definition
|
|
114
|
+
TLBaseShape<K, TLGlobalShapePropsMap[K & keyof TLGlobalShapePropsMap]>
|
|
115
|
+
}
|
|
116
|
+
|
|
82
117
|
/**
|
|
83
|
-
* The set of all shapes that are available in the editor
|
|
118
|
+
* The set of all shapes that are available in the editor.
|
|
84
119
|
*
|
|
85
120
|
* This is the primary shape type used throughout tldraw. It includes both the
|
|
86
121
|
* built-in default shapes and any custom shapes that might be added.
|
|
87
122
|
*
|
|
123
|
+
* You can use this type without a type argument to work with any shape, or pass
|
|
124
|
+
* a specific shape type string (e.g., `'geo'`, `'arrow'`, `'text'`) to narrow
|
|
125
|
+
* down to that specific shape type.
|
|
126
|
+
*
|
|
88
127
|
* @example
|
|
89
128
|
* ```ts
|
|
90
129
|
* // Work with any shape in the editor
|
|
@@ -95,11 +134,16 @@ export type TLUnknownShape = TLBaseShape<string, object>
|
|
|
95
134
|
* y: shape.y + deltaY
|
|
96
135
|
* }
|
|
97
136
|
* }
|
|
137
|
+
*
|
|
138
|
+
* // Narrow to a specific shape type by passing the type as a generic argument
|
|
139
|
+
* function getArrowLabel(shape: TLShape<'arrow'>): string {
|
|
140
|
+
* return shape.props.text // TypeScript knows this is a TLArrowShape
|
|
141
|
+
* }
|
|
98
142
|
* ```
|
|
99
143
|
*
|
|
100
144
|
* @public
|
|
101
145
|
*/
|
|
102
|
-
export type TLShape =
|
|
146
|
+
export type TLShape<K extends keyof TLIndexedShapes = keyof TLIndexedShapes> = TLIndexedShapes[K]
|
|
103
147
|
|
|
104
148
|
/**
|
|
105
149
|
* A partial version of a shape, useful for updates and patches.
|
|
@@ -139,6 +183,57 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
|
|
139
183
|
} & Partial<Omit<T, 'type' | 'id' | 'props' | 'meta'>>
|
|
140
184
|
: never
|
|
141
185
|
|
|
186
|
+
/**
|
|
187
|
+
* A partial version of a shape, useful for creating shapes.
|
|
188
|
+
*
|
|
189
|
+
* This type represents a shape where all properties except `type` are optional.
|
|
190
|
+
* It's commonly used when creating shapes.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* // Create a shape
|
|
195
|
+
* const shapeCreate: TLCreateShapePartial = {
|
|
196
|
+
* type: 'geo',
|
|
197
|
+
* x: 100,
|
|
198
|
+
* y: 200
|
|
199
|
+
* }
|
|
200
|
+
*
|
|
201
|
+
* // Create shape properties
|
|
202
|
+
* const propsCreate: TLCreateShapePartial<TLGeoShape> = {
|
|
203
|
+
* type: 'geo',
|
|
204
|
+
* props: {
|
|
205
|
+
* w: 150,
|
|
206
|
+
* h: 100
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* @public
|
|
212
|
+
*/
|
|
213
|
+
export type TLCreateShapePartial<T extends TLShape = TLShape> = T extends T
|
|
214
|
+
? {
|
|
215
|
+
type: T['type']
|
|
216
|
+
props?: Partial<T['props']>
|
|
217
|
+
meta?: Partial<T['meta']>
|
|
218
|
+
} & Partial<Omit<T, 'type' | 'props' | 'meta'>>
|
|
219
|
+
: never
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Extract a shape type by its props.
|
|
223
|
+
*
|
|
224
|
+
* This utility type takes a props object type and returns the corresponding shape type
|
|
225
|
+
* from the TLShape union whose props match the given type.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* type MyShape = ExtractShapeByProps<{ w: number; h: number }>
|
|
230
|
+
* // MyShape is now the type of shape(s) that have props with w and h as numbers
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* @public
|
|
234
|
+
*/
|
|
235
|
+
export type ExtractShapeByProps<P> = Extract<TLShape, { props: P }>
|
|
236
|
+
|
|
142
237
|
/**
|
|
143
238
|
* A unique identifier for a shape record.
|
|
144
239
|
*
|
|
@@ -153,7 +248,7 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
|
|
153
248
|
*
|
|
154
249
|
* @public
|
|
155
250
|
*/
|
|
156
|
-
export type TLShapeId = RecordId<
|
|
251
|
+
export type TLShapeId = RecordId<TLShape>
|
|
157
252
|
|
|
158
253
|
/**
|
|
159
254
|
* The ID of a shape's parent, which can be either a page or another shape.
|
|
@@ -195,7 +290,7 @@ export const rootShapeVersions = createMigrationIds('com.tldraw.shape', {
|
|
|
195
290
|
HoistOpacity: 2,
|
|
196
291
|
AddMeta: 3,
|
|
197
292
|
AddWhite: 4,
|
|
198
|
-
}
|
|
293
|
+
})
|
|
199
294
|
|
|
200
295
|
/**
|
|
201
296
|
* Migration sequence for the root shape record type.
|
|
@@ -469,7 +564,7 @@ export function createShapePropsMigrationIds<
|
|
|
469
564
|
* @internal
|
|
470
565
|
*/
|
|
471
566
|
export function createShapeRecordType(shapes: Record<string, SchemaPropsInfo>) {
|
|
472
|
-
return createRecordType
|
|
567
|
+
return createRecordType('shape', {
|
|
473
568
|
scope: 'document',
|
|
474
569
|
validator: T.model(
|
|
475
570
|
'shape',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VecModel } from '../misc/geometry-types'
|
|
2
|
-
import {
|
|
2
|
+
import { ExtractShapeByProps } from '../records/TLShape'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Defines cropping parameters for shapes that support cropping.
|
|
@@ -71,4 +71,4 @@ export interface TLShapeCrop {
|
|
|
71
71
|
*
|
|
72
72
|
* @public
|
|
73
73
|
*/
|
|
74
|
-
export type ShapeWithCrop =
|
|
74
|
+
export type ShapeWithCrop = ExtractShapeByProps<{ w: number; h: number; crop: TLShapeCrop | null }>
|