@tldraw/editor 3.12.0-canary.dfe1ebbad12e → 3.12.0-internal.34d12af75e37
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/index.d.ts +3 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/components/Shape.js +10 -14
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +7 -12
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +1 -1
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager.js +1 -1
- package/dist-cjs/lib/editor/managers/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +4 -1
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +80 -24
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/cssRules.js +127 -0
- package/dist-cjs/lib/exports/cssRules.js.map +7 -0
- package/dist-cjs/lib/exports/exportToSvg.js +4 -1
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/parseCss.js +0 -69
- package/dist-cjs/lib/exports/parseCss.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +12 -7
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +3 -3
- package/dist-cjs/lib/hooks/useGestureEvents.js +12 -6
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/utils/debug-flags.js +2 -1
- package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +3 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/components/Shape.mjs +11 -15
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +7 -12
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +1 -1
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +4 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +82 -31
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/cssRules.mjs +107 -0
- package/dist-esm/lib/exports/cssRules.mjs.map +7 -0
- package/dist-esm/lib/exports/exportToSvg.mjs +4 -1
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/parseCss.mjs +0 -69
- package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +12 -7
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +3 -3
- package/dist-esm/lib/hooks/useGestureEvents.mjs +12 -6
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/utils/debug-flags.mjs +2 -1
- package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/components/Shape.tsx +15 -19
- package/src/lib/editor/Editor.ts +7 -13
- package/src/lib/editor/derivations/notVisibleShapes.ts +1 -1
- package/src/lib/editor/managers/FontManager.ts +1 -1
- package/src/lib/editor/tools/StateNode.ts +6 -1
- package/src/lib/exports/StyleEmbedder.ts +107 -36
- package/src/lib/exports/cssRules.ts +125 -0
- package/src/lib/exports/exportToSvg.tsx +5 -1
- package/src/lib/exports/parseCss.ts +0 -79
- package/src/lib/hooks/useCanvasEvents.ts +14 -7
- package/src/lib/hooks/useGestureEvents.ts +12 -6
- package/src/lib/utils/debug-flags.ts +1 -0
- package/src/version.ts +3 -3
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -1216,7 +1216,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1216
1216
|
run(fn: () => void, opts?: TLEditorRunOptions): this {
|
|
1217
1217
|
const previousIgnoreShapeLock = this._shouldIgnoreShapeLock
|
|
1218
1218
|
this._shouldIgnoreShapeLock = opts?.ignoreShapeLock ?? previousIgnoreShapeLock
|
|
1219
|
-
|
|
1220
1219
|
try {
|
|
1221
1220
|
this.history.batch(fn, opts)
|
|
1222
1221
|
} finally {
|
|
@@ -9859,12 +9858,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9859
9858
|
|
|
9860
9859
|
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
9861
9860
|
|
|
9862
|
-
const { panSpeed
|
|
9861
|
+
const { panSpeed } = cameraOptions
|
|
9863
9862
|
this._setCamera(
|
|
9864
9863
|
new Vec(
|
|
9865
|
-
cx + (dx * panSpeed) / cz - x / cz + x /
|
|
9866
|
-
cy + (dy * panSpeed) / cz - y / cz + y /
|
|
9867
|
-
z
|
|
9864
|
+
cx + (dx * panSpeed) / cz - x / cz + x / z,
|
|
9865
|
+
cy + (dy * panSpeed) / cz - y / cz + y / z,
|
|
9866
|
+
z
|
|
9868
9867
|
),
|
|
9869
9868
|
{ immediate: true }
|
|
9870
9869
|
)
|
|
@@ -9939,14 +9938,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9939
9938
|
}
|
|
9940
9939
|
|
|
9941
9940
|
const zoom = cz + (delta ?? 0) * zoomSpeed * cz
|
|
9942
|
-
this._setCamera(
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
cy + (y / zoom - y) - (y / cz - y),
|
|
9946
|
-
zoom
|
|
9947
|
-
),
|
|
9948
|
-
{ immediate: true }
|
|
9949
|
-
)
|
|
9941
|
+
this._setCamera(new Vec(cx + x / zoom - x / cz, cy + y / zoom - y / cz, zoom), {
|
|
9942
|
+
immediate: true,
|
|
9943
|
+
})
|
|
9950
9944
|
this.maybeTrackPerformance('Zooming')
|
|
9951
9945
|
return
|
|
9952
9946
|
}
|
|
@@ -31,7 +31,7 @@ export const notVisibleShapes = (editor: Editor) => {
|
|
|
31
31
|
})
|
|
32
32
|
return notVisibleShapes
|
|
33
33
|
}
|
|
34
|
-
return computed<Set<TLShapeId>>('
|
|
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
|
|
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 {
|
|
1
|
+
import { assert, 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
|
-
|
|
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 {
|
|
@@ -29,6 +23,18 @@ export class StyleEmbedder {
|
|
|
29
23
|
private readonly styles = new Map<Element, ElementStyleInfo>()
|
|
30
24
|
readonly fonts = new FontEmbedder()
|
|
31
25
|
|
|
26
|
+
async collectDefaultStyles(elements: Element[]) {
|
|
27
|
+
const collected = new Set<string>()
|
|
28
|
+
const promises = []
|
|
29
|
+
for (const element of elements) {
|
|
30
|
+
const tagName = element.tagName.toLowerCase()
|
|
31
|
+
if (collected.has(tagName)) continue
|
|
32
|
+
collected.add(tagName)
|
|
33
|
+
promises.push(populateDefaultStylesForTagName(tagName))
|
|
34
|
+
}
|
|
35
|
+
await Promise.all(promises)
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
readRootElementStyles(rootElement: Element) {
|
|
33
39
|
// when reading a root, we always apply _all_ the styles, even if they match the defaults
|
|
34
40
|
this.readElementStyles(rootElement, {
|
|
@@ -239,15 +245,22 @@ function styleFromComputedStyleMap(
|
|
|
239
245
|
{ defaultStyles, parentStyles }: ReadStyleOpts
|
|
240
246
|
) {
|
|
241
247
|
const styles: Record<string, string> = {}
|
|
248
|
+
const currentColor = style.get('color')?.toString() || ''
|
|
249
|
+
const ruleOptions = {
|
|
250
|
+
currentColor,
|
|
251
|
+
parentStyles,
|
|
252
|
+
defaultStyles,
|
|
253
|
+
getStyle: (property: string) => style.get(property)?.toString() ?? '',
|
|
254
|
+
}
|
|
242
255
|
for (const property of style.keys()) {
|
|
243
256
|
if (!shouldIncludeCssProperty(property)) continue
|
|
244
257
|
|
|
245
258
|
const value = style.get(property)!.toString()
|
|
246
259
|
|
|
247
260
|
if (defaultStyles[property] === value) continue
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
261
|
+
|
|
262
|
+
const rule = getOwnProperty(cssRules, property)
|
|
263
|
+
if (rule && rule(value, property, ruleOptions)) continue
|
|
251
264
|
|
|
252
265
|
styles[property] = value
|
|
253
266
|
}
|
|
@@ -260,14 +273,23 @@ function styleFromComputedStyle(
|
|
|
260
273
|
{ defaultStyles, parentStyles }: ReadStyleOpts
|
|
261
274
|
) {
|
|
262
275
|
const styles: Record<string, string> = {}
|
|
276
|
+
const currentColor = style.color
|
|
277
|
+
const ruleOptions = {
|
|
278
|
+
currentColor,
|
|
279
|
+
parentStyles,
|
|
280
|
+
defaultStyles,
|
|
281
|
+
getStyle: (property: string) => style.getPropertyValue(property),
|
|
282
|
+
}
|
|
283
|
+
|
|
263
284
|
for (const property in style) {
|
|
264
285
|
if (!shouldIncludeCssProperty(property)) continue
|
|
265
286
|
|
|
266
287
|
const value = style.getPropertyValue(property)
|
|
267
288
|
|
|
268
289
|
if (defaultStyles[property] === value) continue
|
|
269
|
-
|
|
270
|
-
|
|
290
|
+
|
|
291
|
+
const rule = getOwnProperty(cssRules, property)
|
|
292
|
+
if (rule && rule(value, property, ruleOptions)) continue
|
|
271
293
|
|
|
272
294
|
styles[property] = value
|
|
273
295
|
}
|
|
@@ -285,43 +307,92 @@ function formatCss(style: ReadonlyStyles) {
|
|
|
285
307
|
// when we're figuring out the default values for a tag, we need read them from a separate document
|
|
286
308
|
// so they're not affected by the current document's styles
|
|
287
309
|
let defaultStyleFrame:
|
|
288
|
-
| {
|
|
310
|
+
| Promise<{
|
|
311
|
+
url: string
|
|
312
|
+
iframe: HTMLIFrameElement
|
|
313
|
+
foreignObject: SVGForeignObjectElement
|
|
314
|
+
document: Document
|
|
315
|
+
}>
|
|
289
316
|
| undefined
|
|
290
|
-
|
|
317
|
+
|
|
318
|
+
const defaultStylesByTagName: Record<
|
|
319
|
+
string,
|
|
320
|
+
| { type: 'resolved'; styles: ReadonlyStyles; promise: Promise<ReadonlyStyles> }
|
|
321
|
+
| { type: 'pending'; promise: Promise<ReadonlyStyles> }
|
|
322
|
+
> = {}
|
|
323
|
+
|
|
291
324
|
function getDefaultStyleFrame() {
|
|
292
325
|
if (!defaultStyleFrame) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
326
|
+
defaultStyleFrame = new Promise((resolve) => {
|
|
327
|
+
const frame = document.createElement('iframe')
|
|
328
|
+
Object.assign(frame.style, {
|
|
329
|
+
position: 'absolute',
|
|
330
|
+
top: '-10000px',
|
|
331
|
+
left: '-10000px',
|
|
332
|
+
width: '1px',
|
|
333
|
+
height: '1px',
|
|
334
|
+
opacity: '0',
|
|
335
|
+
pointerEvents: 'none',
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const emptyFrameBlob = new Blob(
|
|
339
|
+
['<svg xmlns="http://www.w3.org/2000/svg"><foreignObject/></svg>'],
|
|
340
|
+
{ type: 'image/svg+xml' }
|
|
341
|
+
)
|
|
342
|
+
const emptyFrameUrl = URL.createObjectURL(emptyFrameBlob)
|
|
343
|
+
|
|
344
|
+
frame.onload = () => {
|
|
345
|
+
const contentDocument = frame.contentDocument!
|
|
346
|
+
const foreignObject = contentDocument.querySelector('foreignObject')!
|
|
347
|
+
resolve({ url: emptyFrameUrl, iframe: frame, foreignObject, document: contentDocument })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
frame.src = emptyFrameUrl
|
|
351
|
+
document.body.appendChild(frame)
|
|
352
|
+
})
|
|
302
353
|
}
|
|
303
354
|
return defaultStyleFrame
|
|
304
355
|
}
|
|
305
356
|
|
|
306
357
|
function destroyDefaultStyleFrame() {
|
|
307
358
|
if (defaultStyleFrame) {
|
|
308
|
-
|
|
359
|
+
defaultStyleFrame.then(({ url, iframe }) => {
|
|
360
|
+
URL.revokeObjectURL(url)
|
|
361
|
+
document.body.removeChild(iframe)
|
|
362
|
+
})
|
|
309
363
|
defaultStyleFrame = undefined
|
|
310
364
|
}
|
|
311
365
|
}
|
|
312
366
|
|
|
313
367
|
const defaultStyleReadOptions: ReadStyleOpts = { defaultStyles: NO_STYLES, parentStyles: NO_STYLES }
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
368
|
+
function populateDefaultStylesForTagName(tagName: string) {
|
|
369
|
+
const existing = defaultStylesByTagName[tagName]
|
|
370
|
+
if (existing && existing.type === 'resolved') {
|
|
371
|
+
return existing.promise
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (existing && existing.type === 'pending') {
|
|
375
|
+
return existing.promise
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const promise = getDefaultStyleFrame().then(({ foreignObject, document }) => {
|
|
379
|
+
const element = document.createElementNS('http://www.w3.org/1999/xhtml', tagName)
|
|
380
|
+
element.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
|
319
381
|
foreignObject.appendChild(element)
|
|
320
|
-
|
|
382
|
+
const styles = element.computedStyleMap
|
|
321
383
|
? styleFromComputedStyleMap(element.computedStyleMap(), defaultStyleReadOptions)
|
|
322
384
|
: styleFromComputedStyle(getComputedStyle(element), defaultStyleReadOptions)
|
|
323
385
|
foreignObject.removeChild(element)
|
|
324
|
-
defaultStylesByTagName[tagName] =
|
|
325
|
-
|
|
326
|
-
|
|
386
|
+
defaultStylesByTagName[tagName] = { type: 'resolved', styles, promise }
|
|
387
|
+
return styles
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
defaultStylesByTagName[tagName] = { type: 'pending', promise }
|
|
391
|
+
return promise
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function getDefaultStylesForTagName(tagName: string) {
|
|
395
|
+
const existing = defaultStylesByTagName[tagName]
|
|
396
|
+
assert(existing && existing.type === 'resolved', 'default styles must be populated & resolved')
|
|
397
|
+
return existing.styles
|
|
327
398
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
const isExcludedBorder =
|
|
24
|
+
(borderDirection: string): CanSkipRule =>
|
|
25
|
+
(value, property, { getStyle }) => {
|
|
26
|
+
const borderWidth = getStyle(`border-${borderDirection}-width`)
|
|
27
|
+
const borderStyle = getStyle(`border-${borderDirection}-style`)
|
|
28
|
+
|
|
29
|
+
if (borderWidth === '0px') return true
|
|
30
|
+
if (borderStyle === 'none') return true
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const cssRules = {
|
|
35
|
+
// currentColor properties:
|
|
36
|
+
'border-block-end-color': isCoveredByCurrentColor,
|
|
37
|
+
'border-block-start-color': isCoveredByCurrentColor,
|
|
38
|
+
'border-bottom-color': isCoveredByCurrentColor,
|
|
39
|
+
'border-inline-end-color': isCoveredByCurrentColor,
|
|
40
|
+
'border-inline-start-color': isCoveredByCurrentColor,
|
|
41
|
+
'border-left-color': isCoveredByCurrentColor,
|
|
42
|
+
'border-right-color': isCoveredByCurrentColor,
|
|
43
|
+
'border-top-color': isCoveredByCurrentColor,
|
|
44
|
+
'caret-color': isCoveredByCurrentColor,
|
|
45
|
+
'column-rule-color': isCoveredByCurrentColor,
|
|
46
|
+
'outline-color': isCoveredByCurrentColor,
|
|
47
|
+
'text-decoration': (value, property, { currentColor }) => {
|
|
48
|
+
return value === 'none solid currentColor' || value === 'none solid ' + currentColor
|
|
49
|
+
},
|
|
50
|
+
'text-decoration-color': isCoveredByCurrentColor,
|
|
51
|
+
'text-emphasis-color': isCoveredByCurrentColor,
|
|
52
|
+
|
|
53
|
+
// inherited properties:
|
|
54
|
+
'border-collapse': isInherited,
|
|
55
|
+
'border-spacing': isInherited,
|
|
56
|
+
'caption-side': isInherited,
|
|
57
|
+
// N.B. We shouldn't inherit 'color' because there's some UA styling, e.g. `mark` elements
|
|
58
|
+
// 'color': isInherited,
|
|
59
|
+
cursor: isInherited,
|
|
60
|
+
direction: isInherited,
|
|
61
|
+
'empty-cells': isInherited,
|
|
62
|
+
'font-family': isInherited,
|
|
63
|
+
'font-size': isInherited,
|
|
64
|
+
'font-style': isInherited,
|
|
65
|
+
'font-variant': isInherited,
|
|
66
|
+
'font-weight': isInherited,
|
|
67
|
+
'font-size-adjust': isInherited,
|
|
68
|
+
'font-stretch': isInherited,
|
|
69
|
+
font: isInherited,
|
|
70
|
+
'letter-spacing': isInherited,
|
|
71
|
+
'line-height': isInherited,
|
|
72
|
+
'list-style-image': isInherited,
|
|
73
|
+
'list-style-position': isInherited,
|
|
74
|
+
'list-style-type': isInherited,
|
|
75
|
+
'list-style': isInherited,
|
|
76
|
+
orphans: isInherited,
|
|
77
|
+
'overflow-wrap': isInherited,
|
|
78
|
+
quotes: isInherited,
|
|
79
|
+
'stroke-linecap': isInherited,
|
|
80
|
+
'stroke-linejoin': isInherited,
|
|
81
|
+
'tab-size': isInherited,
|
|
82
|
+
'text-align': isInherited,
|
|
83
|
+
'text-align-last': isInherited,
|
|
84
|
+
'text-indent': isInherited,
|
|
85
|
+
'text-justify': isInherited,
|
|
86
|
+
'text-shadow': isInherited,
|
|
87
|
+
'text-transform': isInherited,
|
|
88
|
+
visibility: isInherited,
|
|
89
|
+
'white-space': isInherited,
|
|
90
|
+
'white-space-collapse': isInherited,
|
|
91
|
+
widows: isInherited,
|
|
92
|
+
'word-break': isInherited,
|
|
93
|
+
'word-spacing': isInherited,
|
|
94
|
+
'word-wrap': isInherited,
|
|
95
|
+
|
|
96
|
+
// special border cases - we have a weird case (tailwind seems to trigger this) where all
|
|
97
|
+
// border-styles sometimes get set to 'solid', but the border-width is 0 so they don't render.
|
|
98
|
+
// but in SVGs, **sometimes**, the border-width defaults (i think from a UA style-sheet? but
|
|
99
|
+
// honestly can't tell) to 1.5px so the border displays. we work around this by only including
|
|
100
|
+
// border styles at all if both the border-width and border-style are set to something that
|
|
101
|
+
// would show a border.
|
|
102
|
+
'border-top': isExcludedBorder('top'),
|
|
103
|
+
'border-right': isExcludedBorder('right'),
|
|
104
|
+
'border-bottom': isExcludedBorder('bottom'),
|
|
105
|
+
'border-left': isExcludedBorder('left'),
|
|
106
|
+
'border-block-end': isExcludedBorder('block-end'),
|
|
107
|
+
'border-block-start': isExcludedBorder('block-start'),
|
|
108
|
+
'border-inline-end': isExcludedBorder('inline-end'),
|
|
109
|
+
'border-inline-start': isExcludedBorder('inline-start'),
|
|
110
|
+
'border-top-style': isExcludedBorder('top'),
|
|
111
|
+
'border-right-style': isExcludedBorder('right'),
|
|
112
|
+
'border-bottom-style': isExcludedBorder('bottom'),
|
|
113
|
+
'border-left-style': isExcludedBorder('left'),
|
|
114
|
+
'border-block-end-style': isExcludedBorder('block-end'),
|
|
115
|
+
'border-block-start-style': isExcludedBorder('block-start'),
|
|
116
|
+
'border-inline-end-style': isExcludedBorder('inline-end'),
|
|
117
|
+
'border-inline-start-style': isExcludedBorder('inline-start'),
|
|
118
|
+
'border-top-width': isExcludedBorder('top'),
|
|
119
|
+
'border-right-width': isExcludedBorder('right'),
|
|
120
|
+
'border-bottom-width': isExcludedBorder('bottom'),
|
|
121
|
+
'border-left-width': isExcludedBorder('left'),
|
|
122
|
+
'border-block-end-width': isExcludedBorder('block-end'),
|
|
123
|
+
'border-block-start-width': isExcludedBorder('block-start'),
|
|
124
|
+
'border-inline-end-width': isExcludedBorder('inline-end'),
|
|
125
|
+
} satisfies Record<string, CanSkipRule>
|
|
@@ -101,11 +101,15 @@ async function applyChangesToForeignObjects(svg: SVGSVGElement) {
|
|
|
101
101
|
// urls, and things like videos will be converted to images.
|
|
102
102
|
await Promise.all(foreignObjectChildren.map((el) => embedMedia(el as HTMLElement)))
|
|
103
103
|
|
|
104
|
+
await styleEmbedder.collectDefaultStyles([
|
|
105
|
+
...svg.querySelectorAll('foreignObject.tl-export-embed-styles *'),
|
|
106
|
+
])
|
|
107
|
+
|
|
104
108
|
// read the computed styles of every element (+ it's children & pseudo-elements) in the
|
|
105
109
|
// document. we do this in a single pass before we start embedding any CSS stuff to avoid
|
|
106
110
|
// constantly forcing the browser to recompute styles & layout.
|
|
107
111
|
for (const el of foreignObjectChildren) {
|
|
108
|
-
styleEmbedder.readRootElementStyles(el as HTMLElement)
|
|
112
|
+
await styleEmbedder.readRootElementStyles(el as HTMLElement)
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
// fetch any resources that we need to embed in the CSS, like background images.
|
|
@@ -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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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: () =>
|
|
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()
|
|
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 {
|
|
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-
|
|
4
|
+
export const version = '3.12.0-internal.34d12af75e37'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-04-01T17:24:24.434Z',
|
|
8
|
+
patch: '2025-04-01T17:24:24.434Z',
|
|
9
9
|
}
|