@tldraw/editor 3.12.0-canary.3acee343372d → 3.12.0-canary.423f9b4f2a86

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 (71) hide show
  1. package/dist-cjs/index.d.ts +45 -5
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/TldrawEditor.js +4 -0
  4. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  5. package/dist-cjs/lib/components/Shape.js +10 -14
  6. package/dist-cjs/lib/components/Shape.js.map +2 -2
  7. package/dist-cjs/lib/editor/Editor.js +40 -22
  8. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  9. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +1 -1
  10. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  11. package/dist-cjs/lib/editor/managers/FontManager.js +1 -1
  12. package/dist-cjs/lib/editor/managers/FontManager.js.map +2 -2
  13. package/dist-cjs/lib/editor/tools/StateNode.js +4 -1
  14. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  15. package/dist-cjs/lib/exports/StyleEmbedder.js +19 -5
  16. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  17. package/dist-cjs/lib/exports/cssRules.js +127 -0
  18. package/dist-cjs/lib/exports/cssRules.js.map +7 -0
  19. package/dist-cjs/lib/exports/parseCss.js +0 -69
  20. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  21. package/dist-cjs/lib/hooks/useCanvasEvents.js +12 -7
  22. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +3 -3
  23. package/dist-cjs/lib/hooks/useGestureEvents.js +12 -6
  24. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  25. package/dist-cjs/lib/utils/debug-flags.js +2 -1
  26. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  27. package/dist-cjs/version.js +3 -3
  28. package/dist-cjs/version.js.map +1 -1
  29. package/dist-esm/index.d.mts +45 -5
  30. package/dist-esm/index.mjs +1 -1
  31. package/dist-esm/lib/TldrawEditor.mjs +4 -0
  32. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  33. package/dist-esm/lib/components/Shape.mjs +11 -15
  34. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  35. package/dist-esm/lib/editor/Editor.mjs +40 -22
  36. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  37. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +1 -1
  38. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  39. package/dist-esm/lib/editor/managers/FontManager.mjs +1 -1
  40. package/dist-esm/lib/editor/managers/FontManager.mjs.map +2 -2
  41. package/dist-esm/lib/editor/tools/StateNode.mjs +4 -1
  42. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  43. package/dist-esm/lib/exports/StyleEmbedder.mjs +21 -12
  44. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  45. package/dist-esm/lib/exports/cssRules.mjs +107 -0
  46. package/dist-esm/lib/exports/cssRules.mjs.map +7 -0
  47. package/dist-esm/lib/exports/parseCss.mjs +0 -69
  48. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  49. package/dist-esm/lib/hooks/useCanvasEvents.mjs +12 -7
  50. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +3 -3
  51. package/dist-esm/lib/hooks/useGestureEvents.mjs +12 -6
  52. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  53. package/dist-esm/lib/utils/debug-flags.mjs +2 -1
  54. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  55. package/dist-esm/version.mjs +3 -3
  56. package/dist-esm/version.mjs.map +1 -1
  57. package/editor.css +17 -11
  58. package/package.json +7 -7
  59. package/src/lib/TldrawEditor.tsx +29 -3
  60. package/src/lib/components/Shape.tsx +15 -19
  61. package/src/lib/editor/Editor.ts +71 -23
  62. package/src/lib/editor/derivations/notVisibleShapes.ts +1 -1
  63. package/src/lib/editor/managers/FontManager.ts +1 -1
  64. package/src/lib/editor/tools/StateNode.ts +6 -1
  65. package/src/lib/exports/StyleEmbedder.ts +25 -15
  66. package/src/lib/exports/cssRules.ts +126 -0
  67. package/src/lib/exports/parseCss.ts +0 -79
  68. package/src/lib/hooks/useCanvasEvents.ts +14 -7
  69. package/src/lib/hooks/useGestureEvents.ts +12 -6
  70. package/src/lib/utils/debug-flags.ts +1 -0
  71. package/src/version.ts +3 -3
@@ -241,10 +241,33 @@ export interface TLEditorOptions {
241
241
  fontAssetUrls?: { [key: string]: string | undefined }
242
242
  /**
243
243
  * A predicate that should return true if the given shape should be hidden.
244
+ *
245
+ * @deprecated Use {@link Editor#getShapeVisibility} instead.
246
+ *
244
247
  * @param shape - The shape to check.
245
248
  * @param editor - The editor instance.
246
249
  */
247
250
  isShapeHidden?(shape: TLShape, editor: Editor): boolean
251
+
252
+ /**
253
+ * Provides a way to hide shapes.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * getShapeVisibility={(shape, editor) => shape.meta.hidden ? 'hidden' : 'inherit'}
258
+ * ```
259
+ *
260
+ * - `'inherit' | undefined` - (default) The shape will be visible unless its parent is hidden.
261
+ * - `'hidden'` - The shape will be hidden.
262
+ * - `'visible'` - The shape will be visible.
263
+ *
264
+ * @param shape - The shape to check.
265
+ * @param editor - The editor instance.
266
+ */
267
+ getShapeVisibility?(
268
+ shape: TLShape,
269
+ editor: Editor
270
+ ): 'visible' | 'hidden' | 'inherit' | null | undefined
248
271
  }
249
272
 
250
273
  /**
@@ -281,12 +304,21 @@ export class Editor extends EventEmitter<TLEventMap> {
281
304
  autoFocus,
282
305
  inferDarkMode,
283
306
  options,
307
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
284
308
  isShapeHidden,
309
+ getShapeVisibility,
285
310
  fontAssetUrls,
286
311
  }: TLEditorOptions) {
287
312
  super()
313
+ assert(
314
+ !(isShapeHidden && getShapeVisibility),
315
+ 'Cannot use both isShapeHidden and getShapeVisibility'
316
+ )
288
317
 
289
- this._isShapeHiddenPredicate = isShapeHidden
318
+ this._getShapeVisibility = isShapeHidden
319
+ ? // eslint-disable-next-line @typescript-eslint/no-deprecated
320
+ (shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
321
+ : getShapeVisibility
290
322
 
291
323
  this.options = { ...defaultTldrawOptions, ...options }
292
324
 
@@ -773,18 +805,22 @@ export class Editor extends EventEmitter<TLEventMap> {
773
805
  }
774
806
  }
775
807
 
776
- private readonly _isShapeHiddenPredicate?: (shape: TLShape, editor: Editor) => boolean
808
+ private readonly _getShapeVisibility?: TLEditorOptions['getShapeVisibility']
777
809
  @computed
778
810
  private getIsShapeHiddenCache() {
779
- if (!this._isShapeHiddenPredicate) return null
811
+ if (!this._getShapeVisibility) return null
780
812
  return this.store.createComputedCache<boolean, TLShape>('isShapeHidden', (shape: TLShape) => {
781
- const hiddenParent = this.findShapeAncestor(shape, (p) => this.isShapeHidden(p))
782
- if (hiddenParent) return true
783
- return this._isShapeHiddenPredicate!(shape, this) ?? false
813
+ const visibility = this._getShapeVisibility!(shape, this)
814
+ const isParentHidden = PageRecordType.isId(shape.parentId)
815
+ ? false
816
+ : this.isShapeHidden(shape.parentId)
817
+
818
+ if (isParentHidden) return visibility !== 'visible'
819
+ return visibility === 'hidden'
784
820
  })
785
821
  }
786
822
  isShapeHidden(shapeOrId: TLShape | TLShapeId): boolean {
787
- if (!this._isShapeHiddenPredicate) return false
823
+ if (!this._getShapeVisibility) return false
788
824
  return !!this.getIsShapeHiddenCache!()!.get(
789
825
  typeof shapeOrId === 'string' ? shapeOrId : shapeOrId.id
790
826
  )
@@ -1216,7 +1252,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1216
1252
  run(fn: () => void, opts?: TLEditorRunOptions): this {
1217
1253
  const previousIgnoreShapeLock = this._shouldIgnoreShapeLock
1218
1254
  this._shouldIgnoreShapeLock = opts?.ignoreShapeLock ?? previousIgnoreShapeLock
1219
-
1220
1255
  try {
1221
1256
  this.history.batch(fn, opts)
1222
1257
  } finally {
@@ -3712,7 +3747,15 @@ export class Editor extends EventEmitter<TLEventMap> {
3712
3747
  const addShapeById = (id: TLShapeId, opacity: number, isAncestorErasing: boolean) => {
3713
3748
  const shape = this.getShape(id)
3714
3749
  if (!shape) return
3715
- if (this.isShapeHidden(shape)) return
3750
+
3751
+ if (this.isShapeHidden(shape)) {
3752
+ // process children just in case they are overriding the hidden state
3753
+ const isErasing = isAncestorErasing || erasingShapeIds.includes(id)
3754
+ for (const childId of this.getSortedChildIdsForParent(id)) {
3755
+ addShapeById(childId, opacity, isErasing)
3756
+ }
3757
+ return
3758
+ }
3716
3759
 
3717
3760
  opacity *= shape.opacity
3718
3761
  let isShapeErasing = false
@@ -8139,8 +8182,18 @@ export class Editor extends EventEmitter<TLEventMap> {
8139
8182
  if (!currentTool) return styles
8140
8183
 
8141
8184
  if (currentTool.shapeType) {
8142
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
8143
- styles.applyValue(style, this.getStyleForNextShape(style))
8185
+ if (
8186
+ currentTool.shapeType === 'frame' &&
8187
+ !(this.getShapeUtil('frame')!.options as any).showColors
8188
+ ) {
8189
+ for (const style of this.styleProps[currentTool.shapeType].keys()) {
8190
+ if (style.id === 'tldraw:color') continue
8191
+ styles.applyValue(style, this.getStyleForNextShape(style))
8192
+ }
8193
+ } else {
8194
+ for (const style of this.styleProps[currentTool.shapeType].keys()) {
8195
+ styles.applyValue(style, this.getStyleForNextShape(style))
8196
+ }
8144
8197
  }
8145
8198
  }
8146
8199
 
@@ -9849,12 +9902,12 @@ export class Editor extends EventEmitter<TLEventMap> {
9849
9902
 
9850
9903
  const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
9851
9904
 
9852
- const { panSpeed, zoomSpeed } = cameraOptions
9905
+ const { panSpeed } = cameraOptions
9853
9906
  this._setCamera(
9854
9907
  new Vec(
9855
- cx + (dx * panSpeed) / cz - x / cz + x / (z * zoomSpeed),
9856
- cy + (dy * panSpeed) / cz - y / cz + y / (z * zoomSpeed),
9857
- z * zoomSpeed
9908
+ cx + (dx * panSpeed) / cz - x / cz + x / z,
9909
+ cy + (dy * panSpeed) / cz - y / cz + y / z,
9910
+ z
9858
9911
  ),
9859
9912
  { immediate: true }
9860
9913
  )
@@ -9929,14 +9982,9 @@ export class Editor extends EventEmitter<TLEventMap> {
9929
9982
  }
9930
9983
 
9931
9984
  const zoom = cz + (delta ?? 0) * zoomSpeed * cz
9932
- this._setCamera(
9933
- new Vec(
9934
- cx + (x / zoom - x) - (x / cz - x),
9935
- cy + (y / zoom - y) - (y / cz - y),
9936
- zoom
9937
- ),
9938
- { immediate: true }
9939
- )
9985
+ this._setCamera(new Vec(cx + x / zoom - x / cz, cy + y / zoom - y / cz, zoom), {
9986
+ immediate: true,
9987
+ })
9940
9988
  this.maybeTrackPerformance('Zooming')
9941
9989
  return
9942
9990
  }
@@ -31,7 +31,7 @@ export const notVisibleShapes = (editor: Editor) => {
31
31
  })
32
32
  return notVisibleShapes
33
33
  }
34
- return computed<Set<TLShapeId>>('getCulledShapes', (prevValue) => {
34
+ return computed<Set<TLShapeId>>('notVisibleShapes', (prevValue) => {
35
35
  if (isUninitialized(prevValue)) {
36
36
  return fromScratch(editor)
37
37
  }
@@ -94,7 +94,7 @@ export class FontManager {
94
94
  const shapeUtil = this.editor.getShapeUtil(shape)
95
95
  return shapeUtil.getFontFaces(shape)
96
96
  },
97
- { areResultsEqual: areArraysShallowEqual }
97
+ { areResultsEqual: areArraysShallowEqual, areRecordsEqual: (a, b) => a.props === b.props }
98
98
  )
99
99
 
100
100
  this.shapeFontLoadStateCache = editor.store.createCache<(FontState | null)[], TLShape>(
@@ -38,6 +38,7 @@ export interface TLStateNodeConstructor {
38
38
  initial?: string
39
39
  children?(): TLStateNodeConstructor[]
40
40
  isLockable: boolean
41
+ useCoalescedEvents: boolean
41
42
  }
42
43
 
43
44
  /** @public */
@@ -47,7 +48,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
47
48
  public editor: Editor,
48
49
  parent?: StateNode
49
50
  ) {
50
- const { id, children, initial, isLockable } = this.constructor as TLStateNodeConstructor
51
+ const { id, children, initial, isLockable, useCoalescedEvents } = this
52
+ .constructor as TLStateNodeConstructor
51
53
 
52
54
  this.id = id
53
55
  this._isActive = atom<boolean>('toolIsActive' + this.id, false)
@@ -83,6 +85,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
83
85
  }
84
86
  }
85
87
  this.isLockable = isLockable
88
+ this.useCoalescedEvents = useCoalescedEvents
86
89
  this.performanceTracker = new PerformanceTracker()
87
90
  }
88
91
 
@@ -90,6 +93,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
90
93
  static initial?: string
91
94
  static children?: () => TLStateNodeConstructor[]
92
95
  static isLockable = true
96
+ static useCoalescedEvents = false
93
97
 
94
98
  id: string
95
99
  type: 'branch' | 'leaf' | 'root'
@@ -97,6 +101,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
97
101
  initial?: string
98
102
  children?: Record<string, StateNode>
99
103
  isLockable: boolean
104
+ useCoalescedEvents: boolean
100
105
  parent: StateNode
101
106
 
102
107
  /**
@@ -1,5 +1,6 @@
1
- import { assertExists, objectMapValues, uniqueId } from '@tldraw/utils'
1
+ import { assertExists, getOwnProperty, objectMapValues, uniqueId } from '@tldraw/utils'
2
2
  import { FontEmbedder } from './FontEmbedder'
3
+ import { ReadonlyStyles, Styles, cssRules } from './cssRules'
3
4
  import {
4
5
  elementStyle,
5
6
  getComputedStyle,
@@ -7,15 +8,8 @@ import {
7
8
  getRenderedChildren,
8
9
  } from './domUtils'
9
10
  import { resourceToDataUrl } from './fetchCache'
10
- import {
11
- isPropertyCoveredByCurrentColor,
12
- isPropertyInherited,
13
- parseCssValueUrls,
14
- shouldIncludeCssProperty,
15
- } from './parseCss'
16
-
17
- type Styles = { [K in string]?: string }
18
- type ReadonlyStyles = { readonly [K in string]?: string }
11
+ import { parseCssValueUrls, shouldIncludeCssProperty } from './parseCss'
12
+
19
13
  const NO_STYLES = {} as const
20
14
 
21
15
  interface ElementStyleInfo {
@@ -239,15 +233,22 @@ function styleFromComputedStyleMap(
239
233
  { defaultStyles, parentStyles }: ReadStyleOpts
240
234
  ) {
241
235
  const styles: Record<string, string> = {}
236
+ const currentColor = style.get('color')?.toString() || ''
237
+ const ruleOptions = {
238
+ currentColor,
239
+ parentStyles,
240
+ defaultStyles,
241
+ getStyle: (property: string) => style.get(property)?.toString() ?? '',
242
+ }
242
243
  for (const property of style.keys()) {
243
244
  if (!shouldIncludeCssProperty(property)) continue
244
245
 
245
246
  const value = style.get(property)!.toString()
246
247
 
247
248
  if (defaultStyles[property] === value) continue
248
- if (parentStyles[property] === value && isPropertyInherited(property)) continue
249
- if (isPropertyCoveredByCurrentColor(style.get('color')?.toString() || '', property, value))
250
- continue
249
+
250
+ const rule = getOwnProperty(cssRules, property)
251
+ if (rule && rule(value, property, ruleOptions)) continue
251
252
 
252
253
  styles[property] = value
253
254
  }
@@ -260,14 +261,23 @@ function styleFromComputedStyle(
260
261
  { defaultStyles, parentStyles }: ReadStyleOpts
261
262
  ) {
262
263
  const styles: Record<string, string> = {}
264
+ const currentColor = style.color
265
+ const ruleOptions = {
266
+ currentColor,
267
+ parentStyles,
268
+ defaultStyles,
269
+ getStyle: (property: string) => style.getPropertyValue(property),
270
+ }
271
+
263
272
  for (const property in style) {
264
273
  if (!shouldIncludeCssProperty(property)) continue
265
274
 
266
275
  const value = style.getPropertyValue(property)
267
276
 
268
277
  if (defaultStyles[property] === value) continue
269
- if (parentStyles[property] === value && isPropertyInherited(property)) continue
270
- if (isPropertyCoveredByCurrentColor(style.color, property, value)) continue
278
+
279
+ const rule = getOwnProperty(cssRules, property)
280
+ if (rule && rule(value, property, ruleOptions)) continue
271
281
 
272
282
  styles[property] = value
273
283
  }
@@ -0,0 +1,126 @@
1
+ export type Styles = { [K in string]?: string }
2
+ export type ReadonlyStyles = { readonly [K in string]?: string }
3
+
4
+ type CanSkipRule = (
5
+ value: string,
6
+ property: string,
7
+ options: {
8
+ getStyle(property: string): string
9
+ parentStyles: ReadonlyStyles
10
+ defaultStyles: ReadonlyStyles
11
+ currentColor: string
12
+ }
13
+ ) => boolean
14
+
15
+ const isCoveredByCurrentColor: CanSkipRule = (value, property, { currentColor }) => {
16
+ return value === 'currentColor' || value === currentColor
17
+ }
18
+
19
+ const isInherited: CanSkipRule = (value, property, { parentStyles }) => {
20
+ return parentStyles[property] === value
21
+ }
22
+
23
+ // see comment below about why we exclude border styles
24
+ const isExcludedBorder =
25
+ (borderDirection: string): CanSkipRule =>
26
+ (value, property, { getStyle }) => {
27
+ const borderWidth = getStyle(`border-${borderDirection}-width`)
28
+ const borderStyle = getStyle(`border-${borderDirection}-style`)
29
+
30
+ if (borderWidth === '0px') return true
31
+ if (borderStyle === 'none') return true
32
+ return false
33
+ }
34
+
35
+ export const cssRules = {
36
+ // currentColor properties:
37
+ 'border-block-end-color': isCoveredByCurrentColor,
38
+ 'border-block-start-color': isCoveredByCurrentColor,
39
+ 'border-bottom-color': isCoveredByCurrentColor,
40
+ 'border-inline-end-color': isCoveredByCurrentColor,
41
+ 'border-inline-start-color': isCoveredByCurrentColor,
42
+ 'border-left-color': isCoveredByCurrentColor,
43
+ 'border-right-color': isCoveredByCurrentColor,
44
+ 'border-top-color': isCoveredByCurrentColor,
45
+ 'caret-color': isCoveredByCurrentColor,
46
+ 'column-rule-color': isCoveredByCurrentColor,
47
+ 'outline-color': isCoveredByCurrentColor,
48
+ 'text-decoration': (value, property, { currentColor }) => {
49
+ return value === 'none solid currentColor' || value === 'none solid ' + currentColor
50
+ },
51
+ 'text-decoration-color': isCoveredByCurrentColor,
52
+ 'text-emphasis-color': isCoveredByCurrentColor,
53
+
54
+ // inherited properties:
55
+ 'border-collapse': isInherited,
56
+ 'border-spacing': isInherited,
57
+ 'caption-side': isInherited,
58
+ // N.B. We shouldn't inherit 'color' because there's some UA styling, e.g. `mark` elements
59
+ // 'color': isInherited,
60
+ cursor: isInherited,
61
+ direction: isInherited,
62
+ 'empty-cells': isInherited,
63
+ 'font-family': isInherited,
64
+ 'font-size': isInherited,
65
+ 'font-style': isInherited,
66
+ 'font-variant': isInherited,
67
+ 'font-weight': isInherited,
68
+ 'font-size-adjust': isInherited,
69
+ 'font-stretch': isInherited,
70
+ font: isInherited,
71
+ 'letter-spacing': isInherited,
72
+ 'line-height': isInherited,
73
+ 'list-style-image': isInherited,
74
+ 'list-style-position': isInherited,
75
+ 'list-style-type': isInherited,
76
+ 'list-style': isInherited,
77
+ orphans: isInherited,
78
+ 'overflow-wrap': isInherited,
79
+ quotes: isInherited,
80
+ 'stroke-linecap': isInherited,
81
+ 'stroke-linejoin': isInherited,
82
+ 'tab-size': isInherited,
83
+ 'text-align': isInherited,
84
+ 'text-align-last': isInherited,
85
+ 'text-indent': isInherited,
86
+ 'text-justify': isInherited,
87
+ 'text-shadow': isInherited,
88
+ 'text-transform': isInherited,
89
+ visibility: isInherited,
90
+ 'white-space': isInherited,
91
+ 'white-space-collapse': isInherited,
92
+ widows: isInherited,
93
+ 'word-break': isInherited,
94
+ 'word-spacing': isInherited,
95
+ 'word-wrap': isInherited,
96
+
97
+ // special border cases - we have a weird case (tailwind seems to trigger this) where all
98
+ // border-styles sometimes get set to 'solid', but the border-width is 0 so they don't render.
99
+ // but in SVGs, **sometimes**, the border-width defaults (i think from a UA style-sheet? but
100
+ // honestly can't tell) to 1.5px so the border displays. we work around this by only including
101
+ // border styles at all if both the border-width and border-style are set to something that
102
+ // would show a border.
103
+ 'border-top': isExcludedBorder('top'),
104
+ 'border-right': isExcludedBorder('right'),
105
+ 'border-bottom': isExcludedBorder('bottom'),
106
+ 'border-left': isExcludedBorder('left'),
107
+ 'border-block-end': isExcludedBorder('block-end'),
108
+ 'border-block-start': isExcludedBorder('block-start'),
109
+ 'border-inline-end': isExcludedBorder('inline-end'),
110
+ 'border-inline-start': isExcludedBorder('inline-start'),
111
+ 'border-top-style': isExcludedBorder('top'),
112
+ 'border-right-style': isExcludedBorder('right'),
113
+ 'border-bottom-style': isExcludedBorder('bottom'),
114
+ 'border-left-style': isExcludedBorder('left'),
115
+ 'border-block-end-style': isExcludedBorder('block-end'),
116
+ 'border-block-start-style': isExcludedBorder('block-start'),
117
+ 'border-inline-end-style': isExcludedBorder('inline-end'),
118
+ 'border-inline-start-style': isExcludedBorder('inline-start'),
119
+ 'border-top-width': isExcludedBorder('top'),
120
+ 'border-right-width': isExcludedBorder('right'),
121
+ 'border-bottom-width': isExcludedBorder('bottom'),
122
+ 'border-left-width': isExcludedBorder('left'),
123
+ 'border-block-end-width': isExcludedBorder('block-end'),
124
+ 'border-block-start-width': isExcludedBorder('block-start'),
125
+ 'border-inline-end-width': isExcludedBorder('inline-end'),
126
+ } satisfies Record<string, CanSkipRule>
@@ -110,82 +110,3 @@ export function parseCssValueUrls(value: string) {
110
110
  url: m[1] || m[2] || m[3],
111
111
  }))
112
112
  }
113
-
114
- const currentColorProperties = new Set([
115
- 'border-block-end-color',
116
- 'border-block-start-color',
117
- 'border-bottom-color',
118
- 'border-inline-end-color',
119
- 'border-inline-start-color',
120
- 'border-left-color',
121
- 'border-right-color',
122
- 'border-top-color',
123
- 'caret-color',
124
- 'column-rule-color',
125
- 'outline-color',
126
- 'text-decoration',
127
- 'text-decoration-color',
128
- 'text-emphasis-color',
129
- ])
130
-
131
- export function isPropertyCoveredByCurrentColor(
132
- currentColor: string,
133
- property: string,
134
- value: string
135
- ) {
136
- if (currentColorProperties.has(property)) {
137
- return (
138
- value === 'currentColor' ||
139
- value === currentColor ||
140
- (property === 'text-decoration' && value === `none solid ${currentColor}`)
141
- )
142
- }
143
- }
144
-
145
- const inheritedProperties = new Set([
146
- 'border-collapse',
147
- 'border-spacing',
148
- 'caption-side',
149
- // N.B. We shouldn't inherit 'color' because there's some UA styling, e.g. `mark` elements
150
- // 'color',
151
- 'cursor',
152
- 'direction',
153
- 'empty-cells',
154
- 'font-family',
155
- 'font-size',
156
- 'font-style',
157
- 'font-variant',
158
- 'font-weight',
159
- 'font-size-adjust',
160
- 'font-stretch',
161
- 'font',
162
- 'letter-spacing',
163
- 'line-height',
164
- 'list-style-image',
165
- 'list-style-position',
166
- 'list-style-type',
167
- 'list-style',
168
- 'orphans',
169
- 'overflow-wrap',
170
- 'quotes',
171
- 'stroke-linecap',
172
- 'stroke-linejoin',
173
- 'tab-size',
174
- 'text-align',
175
- 'text-align-last',
176
- 'text-indent',
177
- 'text-justify',
178
- 'text-shadow',
179
- 'text-transform',
180
- 'visibility',
181
- 'white-space',
182
- 'white-space-collapse',
183
- 'widows',
184
- 'word-break',
185
- 'word-spacing',
186
- 'word-wrap',
187
- ])
188
-
189
- export function isPropertyInherited(property: string) {
190
- return inheritedProperties.has(property)
191
- }
@@ -1,3 +1,4 @@
1
+ import { useValue } from '@tldraw/state-react'
1
2
  import React, { useMemo } from 'react'
2
3
  import { RIGHT_MOUSE_BUTTON } from '../constants'
3
4
  import {
@@ -11,6 +12,7 @@ import { useEditor } from './useEditor'
11
12
 
12
13
  export function useCanvasEvents() {
13
14
  const editor = useEditor()
15
+ const currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])
14
16
 
15
17
  const events = useMemo(
16
18
  function canvasEvents() {
@@ -49,12 +51,17 @@ export function useCanvasEvents() {
49
51
  lastX = e.clientX
50
52
  lastY = e.clientY
51
53
 
52
- editor.dispatch({
53
- type: 'pointer',
54
- target: 'canvas',
55
- name: 'pointer_move',
56
- ...getPointerInfo(e),
57
- })
54
+ // For tools that benefit from a higher fidelity of events,
55
+ // we dispatch the coalesced events.
56
+ const events = currentTool.useCoalescedEvents ? e.nativeEvent.getCoalescedEvents() : [e]
57
+ for (const singleEvent of events) {
58
+ editor.dispatch({
59
+ type: 'pointer',
60
+ target: 'canvas',
61
+ name: 'pointer_move',
62
+ ...getPointerInfo(singleEvent),
63
+ })
64
+ }
58
65
  }
59
66
 
60
67
  function onPointerUp(e: React.PointerEvent) {
@@ -159,7 +166,7 @@ export function useCanvasEvents() {
159
166
  onClick,
160
167
  }
161
168
  },
162
- [editor]
169
+ [editor, currentTool]
163
170
  )
164
171
 
165
172
  return events
@@ -135,7 +135,6 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
135
135
 
136
136
  let initDistanceBetweenFingers = 1 // the distance between the two fingers when the pinch starts
137
137
  let initZoom = 1 // the browser's zoom level when the pinch starts
138
- let currZoom = 1 // the current zoom level according to the pinch gesture recognizer
139
138
  let currDistanceBetweenFingers = 0
140
139
  const initPointBetweenFingers = new Vec()
141
140
  const prevPointBetweenFingers = new Vec()
@@ -239,7 +238,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
239
238
 
240
239
  switch (pinchState) {
241
240
  case 'zooming': {
242
- currZoom = offset[0]
241
+ const currZoom = offset[0] ** editor.getCameraOptions().zoomSpeed
243
242
 
244
243
  editor.dispatch({
245
244
  type: 'pinch',
@@ -278,7 +277,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
278
277
  if (event instanceof WheelEvent) return
279
278
  if (!(event.target === elm || elm?.contains(event.target as Node))) return
280
279
 
281
- const scale = offset[0]
280
+ const scale = offset[0] ** editor.getCameraOptions().zoomSpeed
282
281
 
283
282
  pinchState = 'not sure'
284
283
 
@@ -309,14 +308,21 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
309
308
  target: ref,
310
309
  eventOptions: { passive: false },
311
310
  pinch: {
312
- from: () => [editor.getZoomLevel(), 0], // Return the camera z to use when pinch starts
311
+ from: () => {
312
+ const { zoomSpeed } = editor.getCameraOptions()
313
+ const level = editor.getZoomLevel() ** (1 / zoomSpeed)
314
+ return [level, 0]
315
+ }, // Return the camera z to use when pinch starts
313
316
  scaleBounds: () => {
314
317
  const baseZoom = editor.getBaseZoom()
315
- const zoomSteps = editor.getCameraOptions().zoomSteps
318
+ const { zoomSteps, zoomSpeed } = editor.getCameraOptions()
316
319
  const zoomMin = zoomSteps[0] * baseZoom
317
320
  const zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom
318
321
 
319
- return { from: editor.getZoomLevel(), max: zoomMax, min: zoomMin }
322
+ return {
323
+ max: zoomMax ** (1 / zoomSpeed),
324
+ min: zoomMin ** (1 / zoomSpeed),
325
+ }
320
326
  },
321
327
  },
322
328
  })
@@ -53,6 +53,7 @@ export const debugFlags = {
53
53
  debugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),
54
54
  hideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),
55
55
  editOnType: createDebugValue('editOnType', { defaults: { all: false } }),
56
+ a11y: createDebugValue('a11y', { defaults: { all: false } }),
56
57
  } as const
57
58
 
58
59
  declare global {
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.12.0-canary.3acee343372d'
4
+ export const version = '3.12.0-canary.423f9b4f2a86'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-03-24T12:11:35.483Z',
8
- patch: '2025-03-24T12:11:35.483Z',
7
+ minor: '2025-04-03T13:02:18.145Z',
8
+ patch: '2025-04-03T13:02:18.145Z',
9
9
  }