@tldraw/editor 3.15.0-canary.db14db4f5395 → 3.15.0-next.d30ed5ad740e

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 (46) hide show
  1. package/dist-cjs/index.d.ts +42 -40
  2. package/dist-cjs/index.js +16 -16
  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/managers/TextManager/TextManager.js +96 -101
  7. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  8. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +7 -2
  9. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  10. package/dist-cjs/lib/primitives/intersect.js +4 -4
  11. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  12. package/dist-cjs/lib/primitives/utils.js +4 -0
  13. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  14. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
  15. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  16. package/dist-cjs/version.js +3 -3
  17. package/dist-cjs/version.js.map +1 -1
  18. package/dist-esm/index.d.mts +42 -40
  19. package/dist-esm/index.mjs +41 -41
  20. package/dist-esm/index.mjs.map +2 -2
  21. package/dist-esm/lib/config/TLUserPreferences.mjs +7 -1
  22. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  23. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +96 -101
  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/primitives/intersect.mjs +5 -5
  28. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  29. package/dist-esm/lib/primitives/utils.mjs +4 -0
  30. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  31. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
  32. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  33. package/dist-esm/version.mjs +3 -3
  34. package/dist-esm/version.mjs.map +1 -1
  35. package/package.json +7 -7
  36. package/src/index.ts +62 -62
  37. package/src/lib/config/TLUserPreferences.ts +7 -0
  38. package/src/lib/editor/managers/TextManager/TextManager.ts +108 -128
  39. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -0
  40. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
  41. package/src/lib/license/LicenseManager.test.ts +1 -1
  42. package/src/lib/primitives/intersect.test.ts +946 -0
  43. package/src/lib/primitives/intersect.ts +12 -5
  44. package/src/lib/primitives/utils.ts +11 -0
  45. package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
  46. package/src/version.ts +3 -3
package/src/index.ts CHANGED
@@ -18,27 +18,6 @@ export * from '@tldraw/utils'
18
18
  // eslint-disable-next-line local/no-export-star
19
19
  export * from '@tldraw/validate'
20
20
 
21
- export {
22
- ErrorScreen,
23
- LoadingScreen,
24
- TldrawEditor,
25
- useOnMount,
26
- type LoadingScreenProps,
27
- type TLOnMountHandler,
28
- type TldrawEditorBaseProps,
29
- type TldrawEditorProps,
30
- type TldrawEditorStoreProps,
31
- type TldrawEditorWithStoreProps,
32
- type TldrawEditorWithoutStoreProps,
33
- } from './lib/TldrawEditor'
34
- export {
35
- ErrorBoundary,
36
- OptionalErrorBoundary,
37
- type TLErrorBoundaryProps,
38
- } from './lib/components/ErrorBoundary'
39
- export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
40
- export { MenuClickCapture } from './lib/components/MenuClickCapture'
41
- export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
42
21
  export { DefaultBackground } from './lib/components/default-components/DefaultBackground'
43
22
  export { DefaultBrush, type TLBrushProps } from './lib/components/default-components/DefaultBrush'
44
23
  export {
@@ -94,6 +73,26 @@ export {
94
73
  } from './lib/components/default-components/DefaultSnapIndictor'
95
74
  export { DefaultSpinner } from './lib/components/default-components/DefaultSpinner'
96
75
  export { DefaultSvgDefs } from './lib/components/default-components/DefaultSvgDefs'
76
+ export {
77
+ ErrorBoundary,
78
+ OptionalErrorBoundary,
79
+ type TLErrorBoundaryProps,
80
+ } from './lib/components/ErrorBoundary'
81
+ export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
82
+ export { MenuClickCapture } from './lib/components/MenuClickCapture'
83
+ export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
84
+ export {
85
+ createTLSchemaFromUtils,
86
+ createTLStore,
87
+ inlineBase64AssetStore,
88
+ type TLStoreBaseOptions,
89
+ type TLStoreEventInfo,
90
+ type TLStoreOptions,
91
+ type TLStoreSchemaOptions,
92
+ } from './lib/config/createTLStore'
93
+ export { createTLUser, useTldrawUser, type TLUser } from './lib/config/createTLUser'
94
+ export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
95
+ export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes'
97
96
  export {
98
97
  getSnapshot,
99
98
  loadSnapshot,
@@ -101,42 +100,23 @@ export {
101
100
  type TLLoadSnapshotOptions,
102
101
  } from './lib/config/TLEditorSnapshot'
103
102
  export {
104
- TAB_ID,
105
103
  createSessionStateSnapshotSignal,
106
104
  extractSessionStateFromLegacySnapshot,
107
105
  loadSessionStateSnapshotIntoStore,
106
+ TAB_ID,
108
107
  type TLLoadSessionStateSnapshotOptions,
109
108
  type TLSessionStateSnapshot,
110
109
  } from './lib/config/TLSessionStateSnapshot'
111
110
  export {
112
- USER_COLORS,
113
111
  defaultUserPreferences,
114
112
  getFreshUserPreferences,
115
113
  getUserPreferences,
116
114
  setUserPreferences,
115
+ USER_COLORS,
117
116
  userTypeValidator,
118
117
  type TLUserPreferences,
119
118
  } from './lib/config/TLUserPreferences'
120
- export {
121
- createTLSchemaFromUtils,
122
- createTLStore,
123
- inlineBase64AssetStore,
124
- type TLStoreBaseOptions,
125
- type TLStoreEventInfo,
126
- type TLStoreOptions,
127
- type TLStoreSchemaOptions,
128
- } from './lib/config/createTLStore'
129
- export { createTLUser, useTldrawUser, type TLUser } from './lib/config/createTLUser'
130
- export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
131
- export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes'
132
119
  export { DEFAULT_ANIMATION_OPTIONS, DEFAULT_CAMERA_OPTIONS, SIDES } from './lib/constants'
133
- export {
134
- Editor,
135
- type TLEditorOptions,
136
- type TLEditorRunOptions,
137
- type TLRenderingShape,
138
- type TLResizeShapeOptions,
139
- } from './lib/editor/Editor'
140
120
  export {
141
121
  BindingUtil,
142
122
  type BindingOnChangeOptions,
@@ -147,6 +127,13 @@ export {
147
127
  type BindingOnShapeIsolateOptions,
148
128
  type TLBindingUtilConstructor,
149
129
  } from './lib/editor/bindings/BindingUtil'
130
+ export {
131
+ Editor,
132
+ type TLEditorOptions,
133
+ type TLEditorRunOptions,
134
+ type TLRenderingShape,
135
+ type TLResizeShapeOptions,
136
+ } from './lib/editor/Editor'
150
137
  export { ClickManager, type TLClickState } from './lib/editor/managers/ClickManager/ClickManager'
151
138
  export { EdgeScrollManager } from './lib/editor/managers/EdgeScrollManager/EdgeScrollManager'
152
139
  export {
@@ -179,6 +166,7 @@ export {
179
166
  } from './lib/editor/managers/TextManager/TextManager'
180
167
  export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
181
168
  export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseBoxShapeUtil'
169
+ export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
182
170
  export {
183
171
  ShapeUtil,
184
172
  type TLCropInfo,
@@ -195,7 +183,6 @@ export {
195
183
  type TLShapeUtilCanvasSvgDef,
196
184
  type TLShapeUtilConstructor,
197
185
  } from './lib/editor/shapes/ShapeUtil'
198
- export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
199
186
  export {
200
187
  getPerfectDashProps,
201
188
  type PerfectDashTerminal,
@@ -205,22 +192,16 @@ export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
205
192
  export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
206
193
  export { maybeSnapToGrid } from './lib/editor/tools/BaseBoxShapeTool/children/Pointing'
207
194
  export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
208
- export {
209
- useDelaySvgExport,
210
- useSvgExportContext,
211
- type SvgExportContext,
212
- type SvgExportDef,
213
- } from './lib/editor/types/SvgExportContext'
214
195
  export { type TLContent } from './lib/editor/types/clipboard-types'
215
196
  export { type TLEventMap, type TLEventMapHandler } from './lib/editor/types/emit-types'
216
197
  export {
217
198
  EVENT_NAME_MAP,
218
199
  type TLBaseEventInfo,
219
- type TLCLickEventName,
220
200
  type TLCancelEvent,
221
201
  type TLCancelEventInfo,
222
202
  type TLClickEvent,
223
203
  type TLClickEventInfo,
204
+ type TLCLickEventName,
224
205
  type TLCompleteEvent,
225
206
  type TLCompleteEventInfo,
226
207
  type TLEnterEventHandler,
@@ -289,6 +270,12 @@ export {
289
270
  type TLResizeHandle,
290
271
  type TLSelectionHandle,
291
272
  } from './lib/editor/types/selection-types'
273
+ export {
274
+ useDelaySvgExport,
275
+ useSvgExportContext,
276
+ type SvgExportContext,
277
+ type SvgExportDef,
278
+ } from './lib/editor/types/SvgExportContext'
292
279
  export { getSvgAsImage } from './lib/exports/getSvgAsImage'
293
280
  export { tlenv } from './lib/globals/environment'
294
281
  export { tlmenus } from './lib/globals/menus'
@@ -352,8 +339,6 @@ export {
352
339
  type SelectionEdge,
353
340
  type SelectionHandle,
354
341
  } from './lib/primitives/Box'
355
- export { Mat, type MatLike, type MatModel } from './lib/primitives/Mat'
356
- export { Vec, type VecLike } from './lib/primitives/Vec'
357
342
  export { EASINGS } from './lib/primitives/easings'
358
343
  export { Arc2d } from './lib/primitives/geometry/Arc2d'
359
344
  export { Circle2d } from './lib/primitives/geometry/Circle2d'
@@ -388,11 +373,8 @@ export {
388
373
  polygonIntersectsPolyline,
389
374
  polygonsIntersect,
390
375
  } from './lib/primitives/intersect'
376
+ export { Mat, type MatLike, type MatModel } from './lib/primitives/Mat'
391
377
  export {
392
- HALF_PI,
393
- PI,
394
- PI2,
395
- SIN,
396
378
  angleDistance,
397
379
  approximately,
398
380
  areAnglesCompatible,
@@ -409,23 +391,36 @@ export {
409
391
  getPointOnCircle,
410
392
  getPointsOnArc,
411
393
  getPolygonVertices,
394
+ HALF_PI,
412
395
  isSafeFloat,
413
396
  perimeterOfEllipse,
397
+ PI,
398
+ PI2,
414
399
  pointInPolygon,
415
400
  precise,
416
401
  radiansToDegrees,
417
402
  rangeIntersection,
418
403
  shortAngleDist,
404
+ SIN,
419
405
  snapAngle,
420
406
  toDomPrecision,
421
407
  toFixed,
422
408
  toPrecision,
423
409
  } from './lib/primitives/utils'
410
+ export { Vec, type VecLike } from './lib/primitives/Vec'
424
411
  export {
425
- ReadonlySharedStyleMap,
426
- SharedStyleMap,
427
- type SharedStyle,
428
- } from './lib/utils/SharedStylesMap'
412
+ ErrorScreen,
413
+ LoadingScreen,
414
+ TldrawEditor,
415
+ useOnMount,
416
+ type LoadingScreenProps,
417
+ type TldrawEditorBaseProps,
418
+ type TldrawEditorProps,
419
+ type TldrawEditorStoreProps,
420
+ type TldrawEditorWithoutStoreProps,
421
+ type TldrawEditorWithStoreProps,
422
+ type TLOnMountHandler,
423
+ } from './lib/TldrawEditor'
429
424
  export { dataUrlToFile, getDefaultCdnBaseUrl } from './lib/utils/assets'
430
425
  export { clampToBrowserMaxCanvasSize, type CanvasMaxSize } from './lib/utils/browserCanvasMaxSize'
431
426
  export {
@@ -461,9 +456,9 @@ export {
461
456
  getFontsFromRichText,
462
457
  type RichTextFontVisitor,
463
458
  type RichTextFontVisitorState,
464
- type TLTextOptions,
465
459
  type TiptapEditor,
466
460
  type TiptapNode,
461
+ type TLTextOptions,
467
462
  } from './lib/utils/richText'
468
463
  export {
469
464
  applyRotationToSnapshotShapes,
@@ -471,9 +466,14 @@ export {
471
466
  type TLRotationSnapshot,
472
467
  } from './lib/utils/rotation'
473
468
  export { runtime, setRuntimeOverrides } from './lib/utils/runtime'
469
+ export {
470
+ ReadonlySharedStyleMap,
471
+ SharedStyleMap,
472
+ type SharedStyle,
473
+ } from './lib/utils/SharedStylesMap'
474
+ export { hardReset } from './lib/utils/sync/hardReset'
474
475
  export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
475
476
  export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus'
476
- export { hardReset } from './lib/utils/sync/hardReset'
477
477
  export { uniq } from './lib/utils/uniq'
478
478
  export { openWindow } from './lib/utils/window-open'
479
479
 
@@ -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,
@@ -1,4 +1,5 @@
1
1
  import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
2
+ import { objectMapKeys } from '@tldraw/utils'
2
3
  import { Editor } from '../../Editor'
3
4
 
4
5
  const fixNewLines = /\r?\n|\r/g
@@ -60,10 +61,18 @@ export interface TLMeasureTextSpanOpts {
60
61
 
61
62
  const spaceCharacterRegex = /\s/
62
63
 
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
+
63
73
  /** @public */
64
74
  export class TextManager {
65
75
  private elm: HTMLDivElement
66
- private defaultStyles: Record<string, string | null>
67
76
 
68
77
  constructor(public editor: Editor) {
69
78
  const elm = document.createElement('div')
@@ -73,31 +82,34 @@ export class TextManager {
73
82
  elm.tabIndex = -1
74
83
  this.editor.getContainer().appendChild(elm)
75
84
 
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,
85
+ this.elm = elm
86
+
87
+ for (const key of objectMapKeys(initialDefaultStyles)) {
88
+ elm.style.setProperty(key, initialDefaultStyles[key])
85
89
  }
90
+ }
86
91
 
87
- this.elm = elm
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
+ }
88
107
  }
89
108
 
90
109
  dispose() {
91
110
  return this.elm.remove()
92
111
  }
93
112
 
94
- private resetElmStyles() {
95
- const { elm, defaultStyles } = this
96
- for (const key in defaultStyles) {
97
- elm.style.setProperty(key, defaultStyles[key])
98
- }
99
- }
100
-
101
113
  measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
102
114
  const div = document.createElement('div')
103
115
  div.textContent = normalizeTextForDom(textToMeasure)
@@ -107,54 +119,36 @@ export class TextManager {
107
119
  measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
108
120
  const { elm } = this
109
121
 
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
- }
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,
117
133
  }
118
134
 
119
- elm.innerHTML = html
135
+ const restoreStyles = this.setElementStyles(newStyles)
120
136
 
121
- // Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
122
- this.resetElmStyles()
137
+ try {
138
+ elm.innerHTML = html
123
139
 
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)
140
+ const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
141
+ const rect = elm.getBoundingClientRect()
130
142
 
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)
143
+ return {
144
+ x: 0,
145
+ y: 0,
146
+ w: rect.width,
147
+ h: rect.height,
148
+ scrollWidth,
146
149
  }
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,
150
+ } finally {
151
+ restoreStyles()
158
152
  }
159
153
  }
160
154
 
@@ -274,82 +268,68 @@ export class TextManager {
274
268
 
275
269
  const { elm } = this
276
270
 
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
-
299
271
  const shouldTruncateToFirstLine =
300
272
  opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
301
-
302
- if (shouldTruncateToFirstLine) {
303
- elm.style.setProperty('overflow-wrap', 'anywhere')
304
- elm.style.setProperty('word-break', 'break-all')
305
- }
306
-
307
- if (opts.otherStyles) {
308
- for (const [key, value] of Object.entries(opts.otherStyles)) {
309
- elm.style.setProperty(key, value)
310
- }
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,
311
286
  }
287
+ const restoreStyles = this.setElementStyles(newStyles)
312
288
 
313
- const normalizedText = normalizeTextForDom(textToMeasure)
314
-
315
- // Render the text into the measurement element:
316
- elm.textContent = normalizedText
317
-
318
- // actually measure the text:
319
- const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
320
- shouldTruncateToFirstLine,
321
- })
289
+ try {
290
+ const normalizedText = normalizeTextForDom(textToMeasure)
322
291
 
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`)
292
+ // Render the text into the measurement element:
330
293
  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
- },
294
+
295
+ // actually measure the text:
296
+ const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
297
+ shouldTruncateToFirstLine,
348
298
  })
349
299
 
350
- return truncatedSpans
351
- }
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
+ }
352
329
 
353
- return spans
330
+ return spans
331
+ } finally {
332
+ restoreStyles()
333
+ }
354
334
  }
355
335
  }
@@ -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
  }
@@ -417,7 +417,7 @@ function importPrivateKey(pemContents: string) {
417
417
  // base64 decode the string to get the binary data
418
418
  const binaryDerString = atob(pemContents)
419
419
  // convert from a binary string to an ArrayBuffer
420
- const binaryDer = str2ab(binaryDerString) as Uint8Array
420
+ const binaryDer = str2ab(binaryDerString)
421
421
 
422
422
  return crypto.subtle.importKey(
423
423
  'pkcs8',