@tldraw/editor 3.14.2 → 3.15.0-canary.0444ba5c83f1

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 (42) hide show
  1. package/dist-cjs/index.d.ts +17 -3
  2. package/dist-cjs/index.js +2 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLUserPreferences.js +7 -1
  5. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  6. package/dist-cjs/lib/editor/Editor.js.map +1 -1
  7. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +101 -96
  8. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +7 -2
  10. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  11. package/dist-cjs/lib/hooks/useEditor.js +1 -4
  12. package/dist-cjs/lib/hooks/useEditor.js.map +2 -2
  13. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
  14. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  15. package/dist-cjs/version.js +3 -3
  16. package/dist-cjs/version.js.map +1 -1
  17. package/dist-esm/index.d.mts +17 -3
  18. package/dist-esm/index.mjs +8 -2
  19. package/dist-esm/index.mjs.map +2 -2
  20. package/dist-esm/lib/config/TLUserPreferences.mjs +7 -1
  21. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  22. package/dist-esm/lib/editor/Editor.mjs.map +1 -1
  23. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +101 -96
  24. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  25. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +7 -2
  26. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  27. package/dist-esm/lib/hooks/useEditor.mjs +1 -4
  28. package/dist-esm/lib/hooks/useEditor.mjs.map +2 -2
  29. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
  30. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  31. package/dist-esm/version.mjs +3 -3
  32. package/dist-esm/version.mjs.map +1 -1
  33. package/package.json +7 -7
  34. package/src/index.ts +7 -1
  35. package/src/lib/config/TLUserPreferences.ts +7 -0
  36. package/src/lib/editor/Editor.ts +1 -1
  37. package/src/lib/editor/managers/TextManager/TextManager.ts +128 -108
  38. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -0
  39. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
  40. package/src/lib/hooks/useEditor.tsx +6 -5
  41. package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
  42. package/src/version.ts +3 -3
package/src/index.ts CHANGED
@@ -287,7 +287,13 @@ export {
287
287
  type ContainerProviderProps,
288
288
  } from './lib/hooks/useContainer'
289
289
  export { getCursor } from './lib/hooks/useCursor'
290
- export { EditorContext, useEditor, useMaybeEditor } from './lib/hooks/useEditor'
290
+ export {
291
+ EditorContext,
292
+ EditorProvider,
293
+ useEditor,
294
+ useMaybeEditor,
295
+ type EditorProviderProps,
296
+ } from './lib/hooks/useEditor'
291
297
  export { useEditorComponents } from './lib/hooks/useEditorComponents'
292
298
  export type { TLEditorComponents } from './lib/hooks/useEditorComponents'
293
299
  export { useEvent, useReactiveEvent } from './lib/hooks/useEvent'
@@ -17,6 +17,7 @@ export interface TLUserPreferences {
17
17
  // N.B. These are duplicated in TLdrawAppUser.
18
18
  locale?: string | null
19
19
  animationSpeed?: number | null
20
+ areKeyboardShortcutsEnabled?: boolean | null
20
21
  edgeScrollSpeed?: number | null
21
22
  colorScheme?: 'light' | 'dark' | 'system'
22
23
  isSnapMode?: boolean | null
@@ -44,6 +45,7 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
44
45
  // N.B. These are duplicated in TLdrawAppUser.
45
46
  locale: T.string.nullable().optional(),
46
47
  animationSpeed: T.number.nullable().optional(),
48
+ areKeyboardShortcutsEnabled: T.boolean.nullable().optional(),
47
49
  edgeScrollSpeed: T.number.nullable().optional(),
48
50
  colorScheme: T.literalEnum('light', 'dark', 'system').optional(),
49
51
  isSnapMode: T.boolean.nullable().optional(),
@@ -61,6 +63,7 @@ const Versions = {
61
63
  AddDynamicSizeMode: 6,
62
64
  AllowSystemColorScheme: 7,
63
65
  AddPasteAtCursor: 8,
66
+ AddKeyboardShortcuts: 9,
64
67
  } as const
65
68
 
66
69
  const CURRENT_VERSION = Math.max(...Object.values(Versions))
@@ -96,6 +99,9 @@ function migrateSnapshot(data: { version: number; user: any }) {
96
99
  if (data.version < Versions.AddPasteAtCursor) {
97
100
  data.user.isPasteAtCursorMode = false
98
101
  }
102
+ if (data.version < Versions.AddKeyboardShortcuts) {
103
+ data.user.areKeyboardShortcutsEnabled = true
104
+ }
99
105
 
100
106
  // finally
101
107
  data.version = CURRENT_VERSION
@@ -139,6 +145,7 @@ export const defaultUserPreferences = Object.freeze({
139
145
  // N.B. These are duplicated in TLdrawAppUser.
140
146
  edgeScrollSpeed: 1,
141
147
  animationSpeed: userPrefersReducedMotion() ? 0 : 1,
148
+ areKeyboardShortcutsEnabled: true,
142
149
  isSnapMode: false,
143
150
  isWrapMode: false,
144
151
  isDynamicSizeMode: false,
@@ -7859,7 +7859,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7859
7859
 
7860
7860
  const prevParentId = partial.parentId
7861
7861
 
7862
- // a shape cannot be it's own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7862
+ // a shape cannot be its own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7863
7863
  if (parentId === partial.id) {
7864
7864
  parentId = focusedGroupId
7865
7865
  }
@@ -1,5 +1,4 @@
1
1
  import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
2
- import { objectMapKeys } from '@tldraw/utils'
3
2
  import { Editor } from '../../Editor'
4
3
 
5
4
  const fixNewLines = /\r?\n|\r/g
@@ -61,18 +60,10 @@ export interface TLMeasureTextSpanOpts {
61
60
 
62
61
  const spaceCharacterRegex = /\s/
63
62
 
64
- const initialDefaultStyles = Object.freeze({
65
- 'overflow-wrap': 'break-word',
66
- 'word-break': 'auto',
67
- width: null,
68
- height: null,
69
- 'max-width': null,
70
- 'min-width': null,
71
- })
72
-
73
63
  /** @public */
74
64
  export class TextManager {
75
65
  private elm: HTMLDivElement
66
+ private defaultStyles: Record<string, string | null>
76
67
 
77
68
  constructor(public editor: Editor) {
78
69
  const elm = document.createElement('div')
@@ -82,34 +73,31 @@ export class TextManager {
82
73
  elm.tabIndex = -1
83
74
  this.editor.getContainer().appendChild(elm)
84
75
 
85
- this.elm = elm
86
-
87
- for (const key of objectMapKeys(initialDefaultStyles)) {
88
- elm.style.setProperty(key, initialDefaultStyles[key])
76
+ // we need to save the default styles so that we can restore them when we're done
77
+ // these must be the css names, not the js names for the styles
78
+ this.defaultStyles = {
79
+ 'overflow-wrap': 'break-word',
80
+ 'word-break': 'auto',
81
+ width: null,
82
+ height: null,
83
+ 'max-width': null,
84
+ 'min-width': null,
89
85
  }
90
- }
91
86
 
92
- private setElementStyles(styles: Record<string, string | undefined>) {
93
- const stylesToReinstate = {} as any
94
- for (const key of objectMapKeys(styles)) {
95
- if (typeof styles[key] === 'string') {
96
- const oldValue = this.elm.style.getPropertyValue(key)
97
- if (oldValue === styles[key]) continue
98
- stylesToReinstate[key] = oldValue
99
- this.elm.style.setProperty(key, styles[key])
100
- }
101
- }
102
- return () => {
103
- for (const key of objectMapKeys(stylesToReinstate)) {
104
- this.elm.style.setProperty(key, stylesToReinstate[key])
105
- }
106
- }
87
+ this.elm = elm
107
88
  }
108
89
 
109
90
  dispose() {
110
91
  return this.elm.remove()
111
92
  }
112
93
 
94
+ private resetElmStyles() {
95
+ const { elm, defaultStyles } = this
96
+ for (const key in defaultStyles) {
97
+ elm.style.setProperty(key, defaultStyles[key])
98
+ }
99
+ }
100
+
113
101
  measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
114
102
  const div = document.createElement('div')
115
103
  div.textContent = normalizeTextForDom(textToMeasure)
@@ -119,36 +107,54 @@ export class TextManager {
119
107
  measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
120
108
  const { elm } = this
121
109
 
122
- const newStyles = {
123
- 'font-family': opts.fontFamily,
124
- 'font-style': opts.fontStyle,
125
- 'font-weight': opts.fontWeight,
126
- 'font-size': opts.fontSize + 'px',
127
- 'line-height': opts.lineHeight.toString(),
128
- padding: opts.padding,
129
- 'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,
130
- 'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,
131
- 'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : undefined,
132
- ...opts.otherStyles,
110
+ if (opts.otherStyles) {
111
+ for (const key in opts.otherStyles) {
112
+ if (!this.defaultStyles[key]) {
113
+ // we need to save the original style so that we can restore it when we're done
114
+ this.defaultStyles[key] = elm.style.getPropertyValue(key)
115
+ }
116
+ }
133
117
  }
134
118
 
135
- const restoreStyles = this.setElementStyles(newStyles)
119
+ elm.innerHTML = html
136
120
 
137
- try {
138
- elm.innerHTML = html
121
+ // Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
122
+ this.resetElmStyles()
139
123
 
140
- const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
141
- const rect = elm.getBoundingClientRect()
124
+ elm.style.setProperty('font-family', opts.fontFamily)
125
+ elm.style.setProperty('font-style', opts.fontStyle)
126
+ elm.style.setProperty('font-weight', opts.fontWeight)
127
+ elm.style.setProperty('font-size', opts.fontSize + 'px')
128
+ elm.style.setProperty('line-height', opts.lineHeight.toString())
129
+ elm.style.setProperty('padding', opts.padding)
142
130
 
143
- return {
144
- x: 0,
145
- y: 0,
146
- w: rect.width,
147
- h: rect.height,
148
- scrollWidth,
131
+ if (opts.maxWidth) {
132
+ elm.style.setProperty('max-width', opts.maxWidth + 'px')
133
+ }
134
+
135
+ if (opts.minWidth) {
136
+ elm.style.setProperty('min-width', opts.minWidth + 'px')
137
+ }
138
+
139
+ if (opts.disableOverflowWrapBreaking) {
140
+ elm.style.setProperty('overflow-wrap', 'normal')
141
+ }
142
+
143
+ if (opts.otherStyles) {
144
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
145
+ elm.style.setProperty(key, value)
149
146
  }
150
- } finally {
151
- restoreStyles()
147
+ }
148
+
149
+ const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
150
+ const rect = elm.getBoundingClientRect()
151
+
152
+ return {
153
+ x: 0,
154
+ y: 0,
155
+ w: rect.width,
156
+ h: rect.height,
157
+ scrollWidth,
152
158
  }
153
159
  }
154
160
 
@@ -268,68 +274,82 @@ export class TextManager {
268
274
 
269
275
  const { elm } = this
270
276
 
277
+ if (opts.otherStyles) {
278
+ for (const key in opts.otherStyles) {
279
+ if (!this.defaultStyles[key]) {
280
+ // we need to save the original style so that we can restore it when we're done
281
+ this.defaultStyles[key] = elm.style.getPropertyValue(key)
282
+ }
283
+ }
284
+ }
285
+
286
+ this.resetElmStyles()
287
+
288
+ elm.style.setProperty('font-family', opts.fontFamily)
289
+ elm.style.setProperty('font-style', opts.fontStyle)
290
+ elm.style.setProperty('font-weight', opts.fontWeight)
291
+ elm.style.setProperty('font-size', opts.fontSize + 'px')
292
+ elm.style.setProperty('line-height', opts.lineHeight.toString())
293
+
294
+ const elementWidth = Math.ceil(opts.width - opts.padding * 2)
295
+ elm.style.setProperty('width', `${elementWidth}px`)
296
+ elm.style.setProperty('height', 'min-content')
297
+ elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
298
+
271
299
  const shouldTruncateToFirstLine =
272
300
  opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
273
- const elementWidth = Math.ceil(opts.width - opts.padding * 2)
274
- const newStyles = {
275
- 'font-family': opts.fontFamily,
276
- 'font-style': opts.fontStyle,
277
- 'font-weight': opts.fontWeight,
278
- 'font-size': opts.fontSize + 'px',
279
- 'line-height': opts.lineHeight.toString(),
280
- width: `${elementWidth}px`,
281
- height: 'min-content',
282
- 'text-align': textAlignmentsForLtr[opts.textAlign],
283
- 'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : undefined,
284
- 'word-break': shouldTruncateToFirstLine ? 'break-all' : undefined,
285
- ...opts.otherStyles,
301
+
302
+ if (shouldTruncateToFirstLine) {
303
+ elm.style.setProperty('overflow-wrap', 'anywhere')
304
+ elm.style.setProperty('word-break', 'break-all')
286
305
  }
287
- const restoreStyles = this.setElementStyles(newStyles)
288
306
 
289
- try {
290
- const normalizedText = normalizeTextForDom(textToMeasure)
307
+ if (opts.otherStyles) {
308
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
309
+ elm.style.setProperty(key, value)
310
+ }
311
+ }
291
312
 
292
- // Render the text into the measurement element:
293
- elm.textContent = normalizedText
313
+ const normalizedText = normalizeTextForDom(textToMeasure)
294
314
 
295
- // actually measure the text:
296
- const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
297
- shouldTruncateToFirstLine,
298
- })
315
+ // Render the text into the measurement element:
316
+ elm.textContent = normalizedText
299
317
 
300
- if (opts.overflow === 'truncate-ellipsis' && didTruncate) {
301
- // we need to measure the ellipsis to know how much space it takes up
302
- elm.textContent = '…'
303
- const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
304
-
305
- // then, we need to subtract that space from the width we have and measure again:
306
- elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
307
- elm.textContent = normalizedText
308
- const truncatedSpans = this.measureElementTextNodeSpans(elm, {
309
- shouldTruncateToFirstLine: true,
310
- }).spans
311
-
312
- // Finally, we add in our ellipsis at the end of the last span. We
313
- // have to do this after measuring, not before, because adding the
314
- // ellipsis changes how whitespace might be getting collapsed by the
315
- // browser.
316
- const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
317
- truncatedSpans.push({
318
- text: '…',
319
- box: {
320
- x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
321
- y: lastSpan.box.y,
322
- w: ellipsisWidth,
323
- h: lastSpan.box.h,
324
- },
325
- })
326
-
327
- return truncatedSpans
328
- }
318
+ // actually measure the text:
319
+ const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
320
+ shouldTruncateToFirstLine,
321
+ })
329
322
 
330
- return spans
331
- } finally {
332
- restoreStyles()
323
+ if (opts.overflow === 'truncate-ellipsis' && didTruncate) {
324
+ // we need to measure the ellipsis to know how much space it takes up
325
+ elm.textContent = '…'
326
+ const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
327
+
328
+ // then, we need to subtract that space from the width we have and measure again:
329
+ elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
330
+ elm.textContent = normalizedText
331
+ const truncatedSpans = this.measureElementTextNodeSpans(elm, {
332
+ shouldTruncateToFirstLine: true,
333
+ }).spans
334
+
335
+ // Finally, we add in our ellipsis at the end of the last span. We
336
+ // have to do this after measuring, not before, because adding the
337
+ // ellipsis changes how whitespace might be getting collapsed by the
338
+ // browser.
339
+ const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
340
+ truncatedSpans.push({
341
+ text: '…',
342
+ box: {
343
+ x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
344
+ y: lastSpan.box.y,
345
+ w: ellipsisWidth,
346
+ h: lastSpan.box.h,
347
+ },
348
+ })
349
+
350
+ return truncatedSpans
333
351
  }
352
+
353
+ return spans
334
354
  }
335
355
  }
@@ -24,6 +24,7 @@ describe('UserPreferencesManager', () => {
24
24
  color: '#FF802B',
25
25
  locale: 'en',
26
26
  animationSpeed: 1,
27
+ areKeyboardShortcutsEnabled: true,
27
28
  edgeScrollSpeed: 1,
28
29
  colorScheme: 'light',
29
30
  isSnapMode: false,
@@ -229,6 +230,7 @@ describe('UserPreferencesManager', () => {
229
230
  locale: mockUserPreferences.locale,
230
231
  color: mockUserPreferences.color,
231
232
  animationSpeed: mockUserPreferences.animationSpeed,
233
+ areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
232
234
  isSnapMode: mockUserPreferences.isSnapMode,
233
235
  colorScheme: mockUserPreferences.colorScheme,
234
236
  isDarkMode: false, // light mode
@@ -362,6 +364,21 @@ describe('UserPreferencesManager', () => {
362
364
  })
363
365
  })
364
366
 
367
+ describe('getAreKeyboardShortcutsEnabled', () => {
368
+ it('should return user keyboard shortcuts', () => {
369
+ expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
370
+ mockUserPreferences.areKeyboardShortcutsEnabled
371
+ )
372
+ })
373
+
374
+ it('should return default keyboard shortcuts when null', () => {
375
+ userPreferencesAtom.set({ ...mockUserPreferences, areKeyboardShortcutsEnabled: null })
376
+ expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
377
+ defaultUserPreferences.areKeyboardShortcutsEnabled
378
+ )
379
+ })
380
+ })
381
+
365
382
  describe('getEdgeScrollSpeed', () => {
366
383
  it('should return user edge scroll speed', () => {
367
384
  expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
@@ -483,6 +500,7 @@ describe('UserPreferencesManager', () => {
483
500
  color: null,
484
501
  locale: null,
485
502
  animationSpeed: null,
503
+ areKeyboardShortcutsEnabled: null,
486
504
  edgeScrollSpeed: null,
487
505
  isSnapMode: null,
488
506
  isWrapMode: null,
@@ -496,6 +514,9 @@ describe('UserPreferencesManager', () => {
496
514
  expect(userPreferencesManager.getColor()).toBe(defaultUserPreferences.color)
497
515
  expect(userPreferencesManager.getLocale()).toBe(defaultUserPreferences.locale)
498
516
  expect(userPreferencesManager.getAnimationSpeed()).toBe(defaultUserPreferences.animationSpeed)
517
+ expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
518
+ defaultUserPreferences.areKeyboardShortcutsEnabled
519
+ )
499
520
  expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
500
521
  defaultUserPreferences.edgeScrollSpeed
501
522
  )
@@ -43,6 +43,7 @@ export class UserPreferencesManager {
43
43
  locale: this.getLocale(),
44
44
  color: this.getColor(),
45
45
  animationSpeed: this.getAnimationSpeed(),
46
+ areKeyboardShortcutsEnabled: this.getAreKeyboardShortcutsEnabled(),
46
47
  isSnapMode: this.getIsSnapMode(),
47
48
  colorScheme: this.user.userPreferences.get().colorScheme,
48
49
  isDarkMode: this.getIsDarkMode(),
@@ -75,6 +76,13 @@ export class UserPreferencesManager {
75
76
  return this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed
76
77
  }
77
78
 
79
+ @computed getAreKeyboardShortcutsEnabled() {
80
+ return (
81
+ this.user.userPreferences.get().areKeyboardShortcutsEnabled ??
82
+ defaultUserPreferences.areKeyboardShortcutsEnabled
83
+ )
84
+ }
85
+
78
86
  @computed getId() {
79
87
  return this.user.userPreferences.get().id
80
88
  }
@@ -21,13 +21,14 @@ export function useMaybeEditor(): Editor | null {
21
21
  return React.useContext(EditorContext)
22
22
  }
23
23
 
24
- export function EditorProvider({
25
- editor,
26
- children,
27
- }: {
24
+ /** @public */
25
+ export interface EditorProviderProps {
28
26
  editor: Editor
29
27
  children: React.ReactNode
30
- }) {
28
+ }
29
+
30
+ /** @public @react */
31
+ export function EditorProvider({ editor, children }: EditorProviderProps) {
31
32
  return (
32
33
  <EditorContext.Provider value={editor}>
33
34
  <IdProvider>{children}</IdProvider>
@@ -266,7 +266,6 @@ export class TLLocalSyncClient {
266
266
 
267
267
  private isPersisting = false
268
268
  private didLastWriteError = false
269
- // eslint-disable-next-line no-restricted-globals
270
269
  private scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null
271
270
 
272
271
  /**
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '3.14.2'
4
+ export const version = '3.15.0-canary.0444ba5c83f1'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-07-03T08:34:52.269Z',
8
- patch: '2025-07-10T09:54:52.889Z',
7
+ minor: '2025-07-04T19:53:57.672Z',
8
+ patch: '2025-07-04T19:53:57.672Z',
9
9
  }