@tldraw/tlschema 4.2.2 → 4.2.3
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 +71 -242
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +5 -1
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +1 -5
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- 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 +13 -26
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +4 -37
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +0 -17
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +1 -12
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +2 -29
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +1 -12
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +1 -12
- 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 +71 -242
- package/dist-esm/index.mjs +1 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +5 -1
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +1 -5
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- 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 +13 -26
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +4 -37
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +0 -17
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +1 -12
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +2 -29
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +1 -12
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +1 -12
- 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 +3 -9
- package/src/assets/TLBookmarkAsset.test.ts +96 -0
- package/src/assets/TLImageAsset.test.ts +213 -0
- package/src/assets/TLVideoAsset.test.ts +105 -0
- package/src/bindings/TLArrowBinding.test.ts +55 -0
- package/src/bindings/TLBaseBinding.ts +14 -25
- package/src/createTLSchema.ts +2 -8
- package/src/index.ts +0 -9
- package/src/migrations.test.ts +1 -149
- package/src/misc/TLOpacity.ts +5 -1
- package/src/misc/TLRichText.ts +1 -6
- package/src/misc/id-validator.test.ts +50 -0
- package/src/records/TLAsset.test.ts +234 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.test.ts +22 -0
- package/src/records/TLBinding.ts +23 -65
- package/src/records/TLCamera.test.ts +19 -0
- package/src/records/TLDocument.test.ts +35 -0
- package/src/records/TLInstance.test.ts +201 -0
- package/src/records/TLPage.test.ts +110 -0
- package/src/records/TLPageState.test.ts +228 -0
- package/src/records/TLPointer.test.ts +63 -0
- package/src/records/TLPresence.test.ts +190 -0
- package/src/records/TLRecord.test.ts +70 -0
- package/src/records/TLShape.test.ts +232 -0
- package/src/records/TLShape.ts +5 -100
- package/src/shapes/ShapeWithCrop.test.ts +18 -0
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.test.ts +505 -0
- package/src/shapes/TLArrowShape.ts +14 -28
- package/src/shapes/TLBaseShape.test.ts +142 -0
- package/src/shapes/TLBaseShape.ts +10 -34
- package/src/shapes/TLBookmarkShape.test.ts +122 -0
- package/src/shapes/TLDrawShape.test.ts +177 -0
- package/src/shapes/TLDrawShape.ts +12 -59
- package/src/shapes/TLEmbedShape.test.ts +286 -0
- package/src/shapes/TLEmbedShape.ts +0 -17
- package/src/shapes/TLFrameShape.test.ts +71 -0
- package/src/shapes/TLGeoShape.test.ts +247 -0
- package/src/shapes/TLGeoShape.ts +1 -14
- package/src/shapes/TLGroupShape.test.ts +59 -0
- package/src/shapes/TLHighlightShape.test.ts +325 -0
- package/src/shapes/TLHighlightShape.ts +0 -37
- package/src/shapes/TLImageShape.test.ts +534 -0
- package/src/shapes/TLLineShape.test.ts +269 -0
- package/src/shapes/TLNoteShape.test.ts +1568 -0
- package/src/shapes/TLNoteShape.ts +1 -15
- package/src/shapes/TLTextShape.test.ts +407 -0
- package/src/shapes/TLTextShape.ts +2 -16
- package/src/shapes/TLVideoShape.test.ts +112 -0
- package/src/store-migrations.ts +16 -17
- package/src/styles/TLColorStyle.test.ts +439 -0
- package/dist-cjs/misc/b64Vecs.js +0 -224
- package/dist-cjs/misc/b64Vecs.js.map +0 -7
- package/dist-esm/misc/b64Vecs.mjs +0 -204
- package/dist-esm/misc/b64Vecs.mjs.map +0 -7
- package/src/misc/b64Vecs.ts +0 -308
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { describe, expect, it, test } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
defaultColorNames,
|
|
4
|
+
DefaultColorStyle,
|
|
5
|
+
DefaultColorThemePalette,
|
|
6
|
+
DefaultLabelColorStyle,
|
|
7
|
+
getColorValue,
|
|
8
|
+
getDefaultColorTheme,
|
|
9
|
+
isDefaultThemeColor,
|
|
10
|
+
TLDefaultColorStyle,
|
|
11
|
+
TLDefaultColorThemeColor,
|
|
12
|
+
} from './TLColorStyle'
|
|
13
|
+
|
|
14
|
+
describe('TLColorStyle', () => {
|
|
15
|
+
describe('defaultColorNames', () => {
|
|
16
|
+
it('should contain expected default colors', () => {
|
|
17
|
+
expect(defaultColorNames).toEqual([
|
|
18
|
+
'black',
|
|
19
|
+
'grey',
|
|
20
|
+
'light-violet',
|
|
21
|
+
'violet',
|
|
22
|
+
'blue',
|
|
23
|
+
'light-blue',
|
|
24
|
+
'yellow',
|
|
25
|
+
'orange',
|
|
26
|
+
'green',
|
|
27
|
+
'light-green',
|
|
28
|
+
'light-red',
|
|
29
|
+
'red',
|
|
30
|
+
'white',
|
|
31
|
+
])
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('DefaultColorThemePalette', () => {
|
|
36
|
+
it('should contain all default colors for both themes', () => {
|
|
37
|
+
const lightTheme = DefaultColorThemePalette.lightMode
|
|
38
|
+
const darkTheme = DefaultColorThemePalette.darkMode
|
|
39
|
+
|
|
40
|
+
defaultColorNames.forEach((colorName) => {
|
|
41
|
+
expect(lightTheme).toHaveProperty(colorName)
|
|
42
|
+
expect(darkTheme).toHaveProperty(colorName)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('getDefaultColorTheme', () => {
|
|
48
|
+
it('should return light theme when isDarkMode is false', () => {
|
|
49
|
+
const result = getDefaultColorTheme({ isDarkMode: false })
|
|
50
|
+
expect(result).toBe(DefaultColorThemePalette.lightMode)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should return dark theme when isDarkMode is true', () => {
|
|
54
|
+
const result = getDefaultColorTheme({ isDarkMode: true })
|
|
55
|
+
expect(result).toBe(DefaultColorThemePalette.darkMode)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('DefaultColorStyle', () => {
|
|
60
|
+
it('should validate all default color names', () => {
|
|
61
|
+
defaultColorNames.forEach((color) => {
|
|
62
|
+
expect(() => DefaultColorStyle.validate(color)).not.toThrow()
|
|
63
|
+
expect(DefaultColorStyle.validate(color)).toBe(color)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should reject invalid color names', () => {
|
|
68
|
+
const invalidColors = ['invalid', 'pink', 'purple', 'cyan', '']
|
|
69
|
+
|
|
70
|
+
invalidColors.forEach((color) => {
|
|
71
|
+
expect(() => DefaultColorStyle.validate(color)).toThrow()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('DefaultLabelColorStyle', () => {
|
|
77
|
+
it('should be a StyleProp with correct configuration', () => {
|
|
78
|
+
expect(DefaultLabelColorStyle.id).toBe('tldraw:labelColor')
|
|
79
|
+
expect(DefaultLabelColorStyle.defaultValue).toBe('black')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should validate all default color names', () => {
|
|
83
|
+
defaultColorNames.forEach((color) => {
|
|
84
|
+
expect(() => DefaultLabelColorStyle.validate(color)).not.toThrow()
|
|
85
|
+
expect(DefaultLabelColorStyle.validate(color)).toBe(color)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should reject invalid color names', () => {
|
|
90
|
+
const invalidColors = ['invalid', 'pink', 'purple', 'cyan', '']
|
|
91
|
+
|
|
92
|
+
invalidColors.forEach((color) => {
|
|
93
|
+
expect(() => DefaultLabelColorStyle.validate(color)).toThrow()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should have values property with all default colors', () => {
|
|
98
|
+
expect(DefaultLabelColorStyle.values).toEqual(defaultColorNames)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should be separate from DefaultColorStyle', () => {
|
|
102
|
+
expect(DefaultColorStyle.id).not.toBe(DefaultLabelColorStyle.id)
|
|
103
|
+
expect(DefaultColorStyle).not.toBe(DefaultLabelColorStyle)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should allow different default values from DefaultColorStyle', () => {
|
|
107
|
+
const originalColorDefault = DefaultColorStyle.defaultValue
|
|
108
|
+
const originalLabelDefault = DefaultLabelColorStyle.defaultValue
|
|
109
|
+
|
|
110
|
+
DefaultColorStyle.setDefaultValue('red')
|
|
111
|
+
DefaultLabelColorStyle.setDefaultValue('blue')
|
|
112
|
+
|
|
113
|
+
expect(DefaultColorStyle.defaultValue).toBe('red')
|
|
114
|
+
expect(DefaultLabelColorStyle.defaultValue).toBe('blue')
|
|
115
|
+
expect(DefaultColorStyle.defaultValue).not.toBe(DefaultLabelColorStyle.defaultValue)
|
|
116
|
+
|
|
117
|
+
// Restore originals
|
|
118
|
+
DefaultColorStyle.setDefaultValue(originalColorDefault)
|
|
119
|
+
DefaultLabelColorStyle.setDefaultValue(originalLabelDefault)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe('isDefaultThemeColor', () => {
|
|
124
|
+
it('should return true for all default color names', () => {
|
|
125
|
+
defaultColorNames.forEach((color) => {
|
|
126
|
+
expect(isDefaultThemeColor(color)).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should return false for invalid color names', () => {
|
|
131
|
+
const invalidColors = ['invalid', 'pink', 'purple', 'cyan', '', 'custom-color']
|
|
132
|
+
|
|
133
|
+
invalidColors.forEach((color) => {
|
|
134
|
+
expect(isDefaultThemeColor(color as TLDefaultColorStyle)).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should return false for hex colors', () => {
|
|
139
|
+
const hexColors = ['#ff0000', '#00ff00', '#0000ff', '#ffffff', '#000000']
|
|
140
|
+
|
|
141
|
+
hexColors.forEach((color) => {
|
|
142
|
+
expect(isDefaultThemeColor(color as TLDefaultColorStyle)).toBe(false)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should return false for CSS color names not in defaults', () => {
|
|
147
|
+
// Use CSS color names that are NOT in the default color list
|
|
148
|
+
const cssColors = ['aqua', 'fuchsia', 'lime', 'navy', 'silver']
|
|
149
|
+
|
|
150
|
+
cssColors.forEach((color) => {
|
|
151
|
+
expect(isDefaultThemeColor(color as TLDefaultColorStyle)).toBe(false)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('should handle edge cases', () => {
|
|
156
|
+
expect(isDefaultThemeColor(null as any)).toBe(false)
|
|
157
|
+
expect(isDefaultThemeColor(undefined as any)).toBe(false)
|
|
158
|
+
expect(isDefaultThemeColor(123 as any)).toBe(false)
|
|
159
|
+
expect(isDefaultThemeColor({} as any)).toBe(false)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should be case sensitive', () => {
|
|
163
|
+
expect(isDefaultThemeColor('Black' as TLDefaultColorStyle)).toBe(false)
|
|
164
|
+
expect(isDefaultThemeColor('RED' as TLDefaultColorStyle)).toBe(false)
|
|
165
|
+
expect(isDefaultThemeColor('Blue' as TLDefaultColorStyle)).toBe(false)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should work as a type guard', () => {
|
|
169
|
+
const color: TLDefaultColorStyle = 'red'
|
|
170
|
+
|
|
171
|
+
if (isDefaultThemeColor(color)) {
|
|
172
|
+
// TypeScript should narrow the type here
|
|
173
|
+
expect(defaultColorNames.includes(color)).toBe(true)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('getColorValue', () => {
|
|
179
|
+
const lightTheme = DefaultColorThemePalette.lightMode
|
|
180
|
+
const darkTheme = DefaultColorThemePalette.darkMode
|
|
181
|
+
|
|
182
|
+
it('should return correct color values for default colors', () => {
|
|
183
|
+
expect(getColorValue(lightTheme, 'red', 'solid')).toBe('#e03131')
|
|
184
|
+
expect(getColorValue(lightTheme, 'blue', 'fill')).toBe('#4465e9')
|
|
185
|
+
expect(getColorValue(lightTheme, 'black', 'solid')).toBe('#1d1d1d')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should work with different variants', () => {
|
|
189
|
+
const variants: (keyof TLDefaultColorThemeColor)[] = [
|
|
190
|
+
'solid',
|
|
191
|
+
'semi',
|
|
192
|
+
'pattern',
|
|
193
|
+
'fill',
|
|
194
|
+
'frameHeadingStroke',
|
|
195
|
+
'frameHeadingFill',
|
|
196
|
+
'frameStroke',
|
|
197
|
+
'frameFill',
|
|
198
|
+
'frameText',
|
|
199
|
+
'noteFill',
|
|
200
|
+
'noteText',
|
|
201
|
+
'highlightSrgb',
|
|
202
|
+
'highlightP3',
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
variants.forEach((variant) => {
|
|
206
|
+
const result = getColorValue(lightTheme, 'red', variant)
|
|
207
|
+
expect(typeof result).toBe('string')
|
|
208
|
+
expect(result.length).toBeGreaterThan(0)
|
|
209
|
+
expect(result).toBe(lightTheme.red[variant])
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('should return different values for light and dark themes', () => {
|
|
214
|
+
// Use colors/variants that actually differ between themes
|
|
215
|
+
expect(getColorValue(lightTheme, 'black', 'solid')).not.toBe(
|
|
216
|
+
getColorValue(darkTheme, 'black', 'solid')
|
|
217
|
+
)
|
|
218
|
+
expect(getColorValue(lightTheme, 'white', 'solid')).not.toBe(
|
|
219
|
+
getColorValue(darkTheme, 'white', 'solid')
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should pass through non-default colors unchanged', () => {
|
|
224
|
+
const customColors = ['#ff0000', '#00ff00', '#0000ff', 'custom-color', 'rgb(255, 0, 0)']
|
|
225
|
+
|
|
226
|
+
customColors.forEach((color) => {
|
|
227
|
+
expect(getColorValue(lightTheme, color as TLDefaultColorStyle, 'solid')).toBe(color)
|
|
228
|
+
expect(getColorValue(darkTheme, color as TLDefaultColorStyle, 'fill')).toBe(color)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should work with all default colors', () => {
|
|
233
|
+
defaultColorNames.forEach((colorName) => {
|
|
234
|
+
const lightResult = getColorValue(lightTheme, colorName, 'solid')
|
|
235
|
+
const darkResult = getColorValue(darkTheme, colorName, 'solid')
|
|
236
|
+
|
|
237
|
+
expect(typeof lightResult).toBe('string')
|
|
238
|
+
expect(typeof darkResult).toBe('string')
|
|
239
|
+
expect(lightResult).toBe(lightTheme[colorName].solid)
|
|
240
|
+
expect(darkResult).toBe(darkTheme[colorName].solid)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('should handle edge cases gracefully', () => {
|
|
245
|
+
// Test with empty string
|
|
246
|
+
expect(getColorValue(lightTheme, '' as TLDefaultColorStyle, 'solid')).toBe('')
|
|
247
|
+
|
|
248
|
+
// Test with null/undefined (they should pass through)
|
|
249
|
+
expect(getColorValue(lightTheme, null as any, 'solid')).toBe(null)
|
|
250
|
+
expect(getColorValue(lightTheme, undefined as any, 'solid')).toBe(undefined)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should work consistently across all variants for a color', () => {
|
|
254
|
+
const color = 'blue'
|
|
255
|
+
const variants: (keyof TLDefaultColorThemeColor)[] = [
|
|
256
|
+
'solid',
|
|
257
|
+
'semi',
|
|
258
|
+
'pattern',
|
|
259
|
+
'fill',
|
|
260
|
+
'frameHeadingStroke',
|
|
261
|
+
'frameHeadingFill',
|
|
262
|
+
'frameStroke',
|
|
263
|
+
'frameFill',
|
|
264
|
+
'frameText',
|
|
265
|
+
'noteFill',
|
|
266
|
+
'noteText',
|
|
267
|
+
'highlightSrgb',
|
|
268
|
+
'highlightP3',
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
variants.forEach((variant) => {
|
|
272
|
+
const lightResult = getColorValue(lightTheme, color, variant)
|
|
273
|
+
const darkResult = getColorValue(darkTheme, color, variant)
|
|
274
|
+
|
|
275
|
+
expect(lightResult).toBe(lightTheme[color][variant])
|
|
276
|
+
expect(darkResult).toBe(darkTheme[color][variant])
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should maintain consistency with isDefaultThemeColor', () => {
|
|
281
|
+
const testColors = [...defaultColorNames, '#ff0000', 'custom', 'invalid']
|
|
282
|
+
|
|
283
|
+
testColors.forEach((color) => {
|
|
284
|
+
const isDefault = isDefaultThemeColor(color as TLDefaultColorStyle)
|
|
285
|
+
const result = getColorValue(lightTheme, color as TLDefaultColorStyle, 'solid')
|
|
286
|
+
|
|
287
|
+
if (isDefault) {
|
|
288
|
+
// Should return the theme color value
|
|
289
|
+
expect(result).toBe(
|
|
290
|
+
(lightTheme[color as keyof typeof lightTheme] as TLDefaultColorThemeColor).solid
|
|
291
|
+
)
|
|
292
|
+
} else {
|
|
293
|
+
// Should pass through unchanged
|
|
294
|
+
expect(result).toBe(color)
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
describe('type safety and integration', () => {
|
|
301
|
+
it('should have proper TypeScript types', () => {
|
|
302
|
+
// Test that TLDefaultColorStyle is properly typed
|
|
303
|
+
const validColor: TLDefaultColorStyle = 'red'
|
|
304
|
+
expect(DefaultColorStyle.validate(validColor)).toBe('red')
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('should work with both color style props', () => {
|
|
308
|
+
const colorValue: TLDefaultColorStyle = 'blue'
|
|
309
|
+
const labelColorValue: TLDefaultColorStyle = 'white'
|
|
310
|
+
|
|
311
|
+
expect(DefaultColorStyle.validate(colorValue)).toBe('blue')
|
|
312
|
+
expect(DefaultLabelColorStyle.validate(labelColorValue)).toBe('white')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should integrate properly with theme system', () => {
|
|
316
|
+
const theme = getDefaultColorTheme({ isDarkMode: false })
|
|
317
|
+
const color: TLDefaultColorStyle = 'red'
|
|
318
|
+
|
|
319
|
+
if (isDefaultThemeColor(color)) {
|
|
320
|
+
const colorValue = getColorValue(theme, color, 'solid')
|
|
321
|
+
expect(typeof colorValue).toBe('string')
|
|
322
|
+
expect(colorValue).toBe(theme.red.solid)
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('should maintain consistent behavior across all exports', () => {
|
|
327
|
+
// Test that all parts work together correctly
|
|
328
|
+
const theme = getDefaultColorTheme({ isDarkMode: false })
|
|
329
|
+
|
|
330
|
+
defaultColorNames.forEach((colorName) => {
|
|
331
|
+
// Should validate in both style props
|
|
332
|
+
expect(DefaultColorStyle.validate(colorName)).toBe(colorName)
|
|
333
|
+
expect(DefaultLabelColorStyle.validate(colorName)).toBe(colorName)
|
|
334
|
+
|
|
335
|
+
// Should be recognized as default theme color
|
|
336
|
+
expect(isDefaultThemeColor(colorName)).toBe(true)
|
|
337
|
+
|
|
338
|
+
// Should resolve to theme color value
|
|
339
|
+
const colorValue = getColorValue(theme, colorName, 'solid')
|
|
340
|
+
expect(colorValue).toBe(theme[colorName].solid)
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('error handling and edge cases', () => {
|
|
346
|
+
it('should handle malformed theme objects gracefully', () => {
|
|
347
|
+
// Create a partial theme for testing edge cases
|
|
348
|
+
const partialTheme = {
|
|
349
|
+
...DefaultColorThemePalette.lightMode,
|
|
350
|
+
red: {
|
|
351
|
+
...DefaultColorThemePalette.lightMode.red,
|
|
352
|
+
solid: undefined,
|
|
353
|
+
} as any,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// getColorValue should still work but return undefined for this case
|
|
357
|
+
expect(getColorValue(partialTheme, 'red', 'solid')).toBe(undefined)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should handle very large color names', () => {
|
|
361
|
+
const longColorName = 'a'.repeat(1000)
|
|
362
|
+
expect(isDefaultThemeColor(longColorName as TLDefaultColorStyle)).toBe(false)
|
|
363
|
+
expect(
|
|
364
|
+
getColorValue(
|
|
365
|
+
DefaultColorThemePalette.lightMode,
|
|
366
|
+
longColorName as TLDefaultColorStyle,
|
|
367
|
+
'solid'
|
|
368
|
+
)
|
|
369
|
+
).toBe(longColorName)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should handle special characters in color values', () => {
|
|
373
|
+
const specialColors = ['color-with-dashes', 'color_with_underscores', 'color.with.dots']
|
|
374
|
+
|
|
375
|
+
specialColors.forEach((color) => {
|
|
376
|
+
expect(isDefaultThemeColor(color as TLDefaultColorStyle)).toBe(false)
|
|
377
|
+
expect(
|
|
378
|
+
getColorValue(DefaultColorThemePalette.lightMode, color as TLDefaultColorStyle, 'solid')
|
|
379
|
+
).toBe(color)
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test('should maintain immutability of exported objects', () => {
|
|
384
|
+
const originalLightTheme = DefaultColorThemePalette.lightMode
|
|
385
|
+
const originalDarkTheme = DefaultColorThemePalette.darkMode
|
|
386
|
+
const originalColorNames = defaultColorNames
|
|
387
|
+
|
|
388
|
+
// These should be the same references
|
|
389
|
+
expect(getDefaultColorTheme({ isDarkMode: false })).toBe(originalLightTheme)
|
|
390
|
+
expect(getDefaultColorTheme({ isDarkMode: true })).toBe(originalDarkTheme)
|
|
391
|
+
|
|
392
|
+
// Arrays should maintain their values
|
|
393
|
+
expect(defaultColorNames).toEqual(originalColorNames)
|
|
394
|
+
expect(DefaultColorStyle.values).toEqual(originalColorNames)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('performance considerations', () => {
|
|
399
|
+
it('should validate colors efficiently', () => {
|
|
400
|
+
const start = performance.now()
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < 1000; i++) {
|
|
403
|
+
defaultColorNames.forEach((color) => {
|
|
404
|
+
DefaultColorStyle.validate(color)
|
|
405
|
+
isDefaultThemeColor(color)
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const end = performance.now()
|
|
410
|
+
expect(end - start).toBeLessThan(100) // Should be fast
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should resolve color values efficiently', () => {
|
|
414
|
+
const theme = DefaultColorThemePalette.lightMode
|
|
415
|
+
const start = performance.now()
|
|
416
|
+
|
|
417
|
+
for (let i = 0; i < 1000; i++) {
|
|
418
|
+
defaultColorNames.forEach((color) => {
|
|
419
|
+
getColorValue(theme, color, 'solid')
|
|
420
|
+
getColorValue(theme, color, 'fill')
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const end = performance.now()
|
|
425
|
+
expect(end - start).toBeLessThan(50) // Should be very fast
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should handle theme switching efficiently', () => {
|
|
429
|
+
const start = performance.now()
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < 1000; i++) {
|
|
432
|
+
getDefaultColorTheme({ isDarkMode: i % 2 === 0 })
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const end = performance.now()
|
|
436
|
+
expect(end - start).toBeLessThan(10) // Should be extremely fast (just returns references)
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
})
|
package/dist-cjs/misc/b64Vecs.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var b64Vecs_exports = {};
|
|
20
|
-
__export(b64Vecs_exports, {
|
|
21
|
-
b64Vecs: () => b64Vecs
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(b64Vecs_exports);
|
|
24
|
-
const POINT_B64_LENGTH = 8;
|
|
25
|
-
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
26
|
-
const B64_LOOKUP = new Uint8Array(128);
|
|
27
|
-
for (let i = 0; i < 64; i++) {
|
|
28
|
-
B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i;
|
|
29
|
-
}
|
|
30
|
-
const POW2 = new Float64Array(31);
|
|
31
|
-
for (let i = 0; i < 31; i++) {
|
|
32
|
-
POW2[i] = Math.pow(2, i - 15);
|
|
33
|
-
}
|
|
34
|
-
const POW2_SUBNORMAL = Math.pow(2, -14) / 1024;
|
|
35
|
-
const MANTISSA = new Float64Array(1024);
|
|
36
|
-
for (let i = 0; i < 1024; i++) {
|
|
37
|
-
MANTISSA[i] = 1 + i / 1024;
|
|
38
|
-
}
|
|
39
|
-
function uint16ArrayToBase64(uint16Array) {
|
|
40
|
-
const uint8Array = new Uint8Array(
|
|
41
|
-
uint16Array.buffer,
|
|
42
|
-
uint16Array.byteOffset,
|
|
43
|
-
uint16Array.byteLength
|
|
44
|
-
);
|
|
45
|
-
let result = "";
|
|
46
|
-
for (let i = 0; i < uint8Array.length; i += 3) {
|
|
47
|
-
const byte1 = uint8Array[i];
|
|
48
|
-
const byte2 = uint8Array[i + 1];
|
|
49
|
-
const byte3 = uint8Array[i + 2];
|
|
50
|
-
const bitmap = byte1 << 16 | byte2 << 8 | byte3;
|
|
51
|
-
result += BASE64_CHARS[bitmap >> 18 & 63] + BASE64_CHARS[bitmap >> 12 & 63] + BASE64_CHARS[bitmap >> 6 & 63] + BASE64_CHARS[bitmap & 63];
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
function base64ToUint16Array(base64) {
|
|
56
|
-
const numBytes = Math.floor(base64.length * 3 / 4);
|
|
57
|
-
const bytes = new Uint8Array(numBytes);
|
|
58
|
-
let byteIndex = 0;
|
|
59
|
-
for (let i = 0; i < base64.length; i += 4) {
|
|
60
|
-
const c0 = B64_LOOKUP[base64.charCodeAt(i)];
|
|
61
|
-
const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)];
|
|
62
|
-
const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)];
|
|
63
|
-
const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)];
|
|
64
|
-
const bitmap = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
65
|
-
bytes[byteIndex++] = bitmap >> 16 & 255;
|
|
66
|
-
bytes[byteIndex++] = bitmap >> 8 & 255;
|
|
67
|
-
bytes[byteIndex++] = bitmap & 255;
|
|
68
|
-
}
|
|
69
|
-
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
|
|
70
|
-
}
|
|
71
|
-
function float16BitsToNumber(bits) {
|
|
72
|
-
const sign = bits >> 15;
|
|
73
|
-
const exp = bits >> 10 & 31;
|
|
74
|
-
const frac = bits & 1023;
|
|
75
|
-
if (exp === 0) {
|
|
76
|
-
return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL;
|
|
77
|
-
}
|
|
78
|
-
if (exp === 31) {
|
|
79
|
-
return frac ? NaN : sign ? -Infinity : Infinity;
|
|
80
|
-
}
|
|
81
|
-
const magnitude = POW2[exp] * MANTISSA[frac];
|
|
82
|
-
return sign ? -magnitude : magnitude;
|
|
83
|
-
}
|
|
84
|
-
function numberToFloat16Bits(value) {
|
|
85
|
-
if (value === 0) return Object.is(value, -0) ? 32768 : 0;
|
|
86
|
-
if (!Number.isFinite(value)) {
|
|
87
|
-
if (Number.isNaN(value)) return 32256;
|
|
88
|
-
return value > 0 ? 31744 : 64512;
|
|
89
|
-
}
|
|
90
|
-
const sign = value < 0 ? 1 : 0;
|
|
91
|
-
value = Math.abs(value);
|
|
92
|
-
const exp = Math.floor(Math.log2(value));
|
|
93
|
-
let expBiased = exp + 15;
|
|
94
|
-
if (expBiased >= 31) {
|
|
95
|
-
return sign << 15 | 31744;
|
|
96
|
-
}
|
|
97
|
-
if (expBiased <= 0) {
|
|
98
|
-
const frac2 = Math.round(value * Math.pow(2, 14) * 1024);
|
|
99
|
-
return sign << 15 | frac2 & 1023;
|
|
100
|
-
}
|
|
101
|
-
const mantissa = value / Math.pow(2, exp) - 1;
|
|
102
|
-
let frac = Math.round(mantissa * 1024);
|
|
103
|
-
if (frac >= 1024) {
|
|
104
|
-
frac = 0;
|
|
105
|
-
expBiased++;
|
|
106
|
-
if (expBiased >= 31) {
|
|
107
|
-
return sign << 15 | 31744;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return sign << 15 | expBiased << 10 | frac;
|
|
111
|
-
}
|
|
112
|
-
class b64Vecs {
|
|
113
|
-
/**
|
|
114
|
-
* Encode a single point (x, y, z) to 8 base64 characters.
|
|
115
|
-
* Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
|
|
116
|
-
*
|
|
117
|
-
* @param x - The x coordinate
|
|
118
|
-
* @param y - The y coordinate
|
|
119
|
-
* @param z - The z coordinate
|
|
120
|
-
* @returns An 8-character base64 string representing the point
|
|
121
|
-
*/
|
|
122
|
-
static encodePoint(x, y, z) {
|
|
123
|
-
const xBits = numberToFloat16Bits(x);
|
|
124
|
-
const yBits = numberToFloat16Bits(y);
|
|
125
|
-
const zBits = numberToFloat16Bits(z);
|
|
126
|
-
const b0 = xBits & 255;
|
|
127
|
-
const b1 = xBits >> 8 & 255;
|
|
128
|
-
const b2 = yBits & 255;
|
|
129
|
-
const b3 = yBits >> 8 & 255;
|
|
130
|
-
const b4 = zBits & 255;
|
|
131
|
-
const b5 = zBits >> 8 & 255;
|
|
132
|
-
const bitmap1 = b0 << 16 | b1 << 8 | b2;
|
|
133
|
-
const bitmap2 = b3 << 16 | b4 << 8 | b5;
|
|
134
|
-
return BASE64_CHARS[bitmap1 >> 18 & 63] + BASE64_CHARS[bitmap1 >> 12 & 63] + BASE64_CHARS[bitmap1 >> 6 & 63] + BASE64_CHARS[bitmap1 & 63] + BASE64_CHARS[bitmap2 >> 18 & 63] + BASE64_CHARS[bitmap2 >> 12 & 63] + BASE64_CHARS[bitmap2 >> 6 & 63] + BASE64_CHARS[bitmap2 & 63];
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Convert an array of VecModels to a base64 string for compact storage.
|
|
138
|
-
* Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
|
|
139
|
-
* undefined, it defaults to 0.5.
|
|
140
|
-
*
|
|
141
|
-
* @param points - An array of VecModel objects to encode
|
|
142
|
-
* @returns A base64-encoded string containing all points
|
|
143
|
-
*/
|
|
144
|
-
static encodePoints(points) {
|
|
145
|
-
const uint16s = new Uint16Array(points.length * 3);
|
|
146
|
-
for (let i = 0; i < points.length; i++) {
|
|
147
|
-
const p = points[i];
|
|
148
|
-
uint16s[i * 3] = numberToFloat16Bits(p.x);
|
|
149
|
-
uint16s[i * 3 + 1] = numberToFloat16Bits(p.y);
|
|
150
|
-
uint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5);
|
|
151
|
-
}
|
|
152
|
-
return uint16ArrayToBase64(uint16s);
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Convert a base64 string back to an array of VecModels.
|
|
156
|
-
* Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
|
|
157
|
-
*
|
|
158
|
-
* @param base64 - The base64-encoded string containing point data
|
|
159
|
-
* @returns An array of VecModel objects decoded from the string
|
|
160
|
-
*/
|
|
161
|
-
static decodePoints(base64) {
|
|
162
|
-
const uint16s = base64ToUint16Array(base64);
|
|
163
|
-
const result = [];
|
|
164
|
-
for (let i = 0; i < uint16s.length; i += 3) {
|
|
165
|
-
result.push({
|
|
166
|
-
x: float16BitsToNumber(uint16s[i]),
|
|
167
|
-
y: float16BitsToNumber(uint16s[i + 1]),
|
|
168
|
-
z: float16BitsToNumber(uint16s[i + 2])
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
return result;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Decode a single point (8 base64 chars) starting at the given offset.
|
|
175
|
-
* Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.
|
|
176
|
-
*
|
|
177
|
-
* @param b64Points - The base64-encoded string containing point data
|
|
178
|
-
* @param charOffset - The character offset where the point starts (must be a multiple of 8)
|
|
179
|
-
* @returns A VecModel object with x, y, and z coordinates
|
|
180
|
-
* @internal
|
|
181
|
-
*/
|
|
182
|
-
static decodePointAt(b64Points, charOffset) {
|
|
183
|
-
const c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)];
|
|
184
|
-
const c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)];
|
|
185
|
-
const c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)];
|
|
186
|
-
const c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)];
|
|
187
|
-
const c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)];
|
|
188
|
-
const c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)];
|
|
189
|
-
const c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)];
|
|
190
|
-
const c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)];
|
|
191
|
-
const bitmap1 = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
192
|
-
const bitmap2 = c4 << 18 | c5 << 12 | c6 << 6 | c7;
|
|
193
|
-
const xBits = bitmap1 >> 16 & 255 | bitmap1 & 65280;
|
|
194
|
-
const yBits = bitmap1 & 255 | bitmap2 >> 8 & 65280;
|
|
195
|
-
const zBits = bitmap2 >> 8 & 255 | bitmap2 << 8 & 65280;
|
|
196
|
-
return {
|
|
197
|
-
x: float16BitsToNumber(xBits),
|
|
198
|
-
y: float16BitsToNumber(yBits),
|
|
199
|
-
z: float16BitsToNumber(zBits)
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Get the first point from a base64-encoded string of points.
|
|
204
|
-
*
|
|
205
|
-
* @param b64Points - The base64-encoded string containing point data
|
|
206
|
-
* @returns The first point as a VecModel, or null if the string is too short
|
|
207
|
-
* @public
|
|
208
|
-
*/
|
|
209
|
-
static decodeFirstPoint(b64Points) {
|
|
210
|
-
if (b64Points.length < POINT_B64_LENGTH) return null;
|
|
211
|
-
return b64Vecs.decodePointAt(b64Points, 0);
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Get the last point from a base64-encoded string of points.
|
|
215
|
-
*
|
|
216
|
-
* @param b64Points - The base64-encoded string containing point data
|
|
217
|
-
* @returns The last point as a VecModel, or null if the string is too short
|
|
218
|
-
*/
|
|
219
|
-
static decodeLastPoint(b64Points) {
|
|
220
|
-
if (b64Points.length < POINT_B64_LENGTH) return null;
|
|
221
|
-
return b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
//# sourceMappingURL=b64Vecs.js.map
|