@tldraw/editor 3.13.0-canary.bec6f90d283a → 3.13.0-canary.c3ce2eeb1729
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 +31 -14
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js +2 -1
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +12 -8
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +27 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +14 -12
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +22 -10
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +16 -15
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +8 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
- package/dist-cjs/lib/utils/dom.js +3 -3
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
- package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
- package/dist-cjs/lib/utils/rotation.js +5 -5
- package/dist-cjs/lib/utils/rotation.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 +31 -14
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs +2 -1
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +12 -8
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +27 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +14 -12
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +22 -10
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +16 -15
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +8 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
- package/dist-esm/lib/utils/dom.mjs +3 -3
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
- package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
- package/dist-esm/lib/utils/rotation.mjs +5 -5
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +15 -0
- package/package.json +7 -7
- package/src/lib/TldrawEditor.tsx +6 -1
- package/src/lib/components/Shape.tsx +14 -10
- package/src/lib/components/default-components/DefaultCanvas.tsx +32 -2
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +25 -14
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
- package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
- package/src/lib/editor/Editor.ts +22 -10
- package/src/lib/editor/managers/TextManager.ts +12 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +13 -1
- package/src/lib/exports/getSvgJsx.tsx +16 -7
- package/src/lib/hooks/useDocumentEvents.ts +7 -2
- package/src/lib/hooks/useEditorComponents.tsx +32 -28
- package/src/lib/license/LicenseManager.test.ts +40 -0
- package/src/lib/license/LicenseManager.ts +13 -1
- package/src/lib/options.ts +4 -0
- package/src/lib/utils/areShapesContentEqual.ts +4 -0
- package/src/lib/utils/dom.ts +4 -4
- package/src/lib/utils/nearestMultiple.ts +13 -0
- package/src/lib/utils/rotation.ts +8 -6
- package/src/version.ts +3 -3
|
@@ -157,8 +157,10 @@ My browser: ${navigator.userAgent}`
|
|
|
157
157
|
<h2>Are you sure?</h2>
|
|
158
158
|
<p>Resetting your data will delete your drawing and cannot be undone.</p>
|
|
159
159
|
<div className="tl-error-boundary__content__actions">
|
|
160
|
-
<button onClick={() => setShouldShowResetConfirmation(false)}>
|
|
161
|
-
|
|
160
|
+
<button className="tlui-button" onClick={() => setShouldShowResetConfirmation(false)}>
|
|
161
|
+
Cancel
|
|
162
|
+
</button>
|
|
163
|
+
<button className="tlui-button tl-error-boundary__reset" onClick={resetLocalState}>
|
|
162
164
|
Reset data
|
|
163
165
|
</button>
|
|
164
166
|
</div>
|
|
@@ -166,16 +168,23 @@ My browser: ${navigator.userAgent}`
|
|
|
166
168
|
) : (
|
|
167
169
|
<>
|
|
168
170
|
<h2>Something went wrong</h2>
|
|
169
|
-
<p>Please refresh
|
|
171
|
+
<p>Please refresh your browser.</p>
|
|
172
|
+
<p>
|
|
173
|
+
If the issue continues after refreshing, you may need to reset the tldraw data stored
|
|
174
|
+
on your device.
|
|
175
|
+
</p>
|
|
170
176
|
<p>
|
|
171
|
-
|
|
172
|
-
<a href={url.toString()}>GitHub issue</a> or ask for help on{' '}
|
|
173
|
-
<a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
|
|
174
|
-
Discord
|
|
175
|
-
</a>
|
|
176
|
-
. If you are still stuck, you can reset the tldraw data on your machine. This may
|
|
177
|
-
erase the project you were working on, so try to get help first.
|
|
177
|
+
<strong>Note:</strong> Resetting will erase your current project and any unsaved work.
|
|
178
178
|
</p>
|
|
179
|
+
{process.env.NODE_ENV !== 'production' && (
|
|
180
|
+
<p>
|
|
181
|
+
If you're developing with the SDK and need help, join us on{' '}
|
|
182
|
+
<a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
|
|
183
|
+
Discord
|
|
184
|
+
</a>
|
|
185
|
+
.
|
|
186
|
+
</p>
|
|
187
|
+
)}
|
|
179
188
|
{shouldShowError && (
|
|
180
189
|
<>
|
|
181
190
|
Message:
|
|
@@ -187,22 +196,24 @@ My browser: ${navigator.userAgent}`
|
|
|
187
196
|
<pre>
|
|
188
197
|
<code>{errorStack ?? errorMessage}</code>
|
|
189
198
|
</pre>
|
|
190
|
-
<button onClick={copyError}>
|
|
199
|
+
<button className="tlui-button" onClick={copyError}>
|
|
200
|
+
{didCopy ? 'Copied!' : 'Copy'}
|
|
201
|
+
</button>
|
|
191
202
|
</div>
|
|
192
203
|
</>
|
|
193
204
|
)}
|
|
194
205
|
<div className="tl-error-boundary__content__actions">
|
|
195
|
-
<button onClick={() => setShouldShowError(!shouldShowError)}>
|
|
206
|
+
<button className="tlui-button" onClick={() => setShouldShowError(!shouldShowError)}>
|
|
196
207
|
{shouldShowError ? 'Hide details' : 'Show details'}
|
|
197
208
|
</button>
|
|
198
209
|
<div className="tl-error-boundary__content__actions__group">
|
|
199
210
|
<button
|
|
200
|
-
className="tl-error-boundary__reset"
|
|
211
|
+
className="tlui-button tl-error-boundary__reset"
|
|
201
212
|
onClick={() => setShouldShowResetConfirmation(true)}
|
|
202
213
|
>
|
|
203
214
|
Reset data
|
|
204
215
|
</button>
|
|
205
|
-
<button className="tl-error-boundary__refresh" onClick={refresh}>
|
|
216
|
+
<button className="tlui-button tl-error-boundary__refresh" onClick={refresh}>
|
|
206
217
|
Refresh Page
|
|
207
218
|
</button>
|
|
208
219
|
</div>
|
|
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
|
|
|
9
9
|
import { OptionalErrorBoundary } from '../ErrorBoundary'
|
|
10
10
|
|
|
11
11
|
// need an extra layer of indirection here to allow hooks to be used inside the indicator render
|
|
12
|
-
const EvenInnererIndicator = memo(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const EvenInnererIndicator = memo(
|
|
13
|
+
({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
|
|
14
|
+
return useStateTracking('Indicator: ' + shape.type, () =>
|
|
15
|
+
// always fetch the latest shape from the store even if the props/meta have not changed, to avoid
|
|
16
|
+
// calling the render method with stale data.
|
|
17
|
+
util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
(prevProps, nextProps) => {
|
|
21
|
+
return (
|
|
22
|
+
prevProps.shape.props === nextProps.shape.props &&
|
|
23
|
+
prevProps.shape.meta === nextProps.shape.meta
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
)
|
|
19
27
|
|
|
20
28
|
const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
|
|
21
29
|
const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
|
|
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
|
|
|
61
69
|
useQuickReactor(
|
|
62
70
|
'indicator transform',
|
|
63
71
|
() => {
|
|
72
|
+
if (hidden) return
|
|
64
73
|
const elm = rIndicator.current
|
|
65
74
|
if (!elm) return
|
|
66
75
|
const pageTransform = editor.getShapePageTransform(shapeId)
|
|
67
76
|
if (!pageTransform) return
|
|
68
77
|
elm.style.setProperty('transform', pageTransform.toCssString())
|
|
69
78
|
},
|
|
70
|
-
[editor, shapeId]
|
|
79
|
+
[editor, shapeId, hidden]
|
|
71
80
|
)
|
|
72
81
|
|
|
73
82
|
useLayoutEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @public @react */
|
|
2
2
|
export function DefaultSpinner() {
|
|
3
3
|
return (
|
|
4
|
-
<svg width={16} height={16} viewBox="0 0 16 16">
|
|
4
|
+
<svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
|
|
5
5
|
<g strokeWidth={2} fill="none" fillRule="evenodd">
|
|
6
6
|
<circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
|
|
7
7
|
<path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
|
|
|
129
129
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
130
130
|
import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
|
|
131
131
|
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
|
132
|
+
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
132
133
|
import { dataUrlToFile } from '../utils/assets'
|
|
133
134
|
import { debugFlags } from '../utils/debug-flags'
|
|
134
135
|
import {
|
|
@@ -1705,8 +1706,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1705
1706
|
* @readonly
|
|
1706
1707
|
*/
|
|
1707
1708
|
@computed getSelectedShapes(): TLShape[] {
|
|
1708
|
-
|
|
1709
|
-
return compact(selectedShapeIds.map((id) => this.store.get(id)))
|
|
1709
|
+
return compact(this.getSelectedShapeIds().map((id) => this.store.get(id)))
|
|
1710
1710
|
}
|
|
1711
1711
|
|
|
1712
1712
|
/**
|
|
@@ -4575,7 +4575,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4575
4575
|
this.fonts.trackFontsForShape(shape)
|
|
4576
4576
|
return this.getShapeUtil(shape).getGeometry(shape, opts)
|
|
4577
4577
|
},
|
|
4578
|
-
{ areRecordsEqual:
|
|
4578
|
+
{ areRecordsEqual: areShapesContentEqual }
|
|
4579
4579
|
)
|
|
4580
4580
|
}
|
|
4581
4581
|
return this._shapeGeometryCaches[context].get(
|
|
@@ -4623,9 +4623,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4623
4623
|
|
|
4624
4624
|
/** @internal */
|
|
4625
4625
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4626
|
-
return this.store.createComputedCache(
|
|
4627
|
-
|
|
4628
|
-
|
|
4626
|
+
return this.store.createComputedCache(
|
|
4627
|
+
'handles',
|
|
4628
|
+
(shape) => {
|
|
4629
|
+
return this.getShapeUtil(shape).getHandles?.(shape)
|
|
4630
|
+
},
|
|
4631
|
+
{
|
|
4632
|
+
areRecordsEqual: areShapesContentEqual,
|
|
4633
|
+
}
|
|
4634
|
+
)
|
|
4629
4635
|
}
|
|
4630
4636
|
|
|
4631
4637
|
/**
|
|
@@ -5846,9 +5852,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5846
5852
|
@computed
|
|
5847
5853
|
private _getBindingsIndexCache() {
|
|
5848
5854
|
const index = bindingsIndex(this)
|
|
5849
|
-
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5850
|
-
|
|
5851
|
-
|
|
5855
|
+
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5856
|
+
'bindingsIndex',
|
|
5857
|
+
(shape) => {
|
|
5858
|
+
return index.get().get(shape.id)
|
|
5859
|
+
},
|
|
5860
|
+
// we can ignore the shape equality check here because the index is
|
|
5861
|
+
// computed incrementally based on what bindings are in the store
|
|
5862
|
+
{ areRecordsEqual: () => true }
|
|
5863
|
+
)
|
|
5852
5864
|
}
|
|
5853
5865
|
|
|
5854
5866
|
/**
|
|
@@ -10215,7 +10227,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10215
10227
|
|
|
10216
10228
|
// If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
|
|
10217
10229
|
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
|
10218
|
-
if (
|
|
10230
|
+
if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
|
|
10219
10231
|
|
|
10220
10232
|
switch (behavior) {
|
|
10221
10233
|
case 'zoom': {
|
|
@@ -32,6 +32,7 @@ export interface TLMeasureTextSpanOpts {
|
|
|
32
32
|
fontStyle: string
|
|
33
33
|
lineHeight: number
|
|
34
34
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
35
|
+
otherStyles?: Record<string, string>
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const spaceCharacterRegex = /\s/
|
|
@@ -86,6 +87,7 @@ export class TextManager {
|
|
|
86
87
|
*/
|
|
87
88
|
maxWidth: null | number
|
|
88
89
|
minWidth?: null | number
|
|
90
|
+
otherStyles?: Record<string, string>
|
|
89
91
|
padding: string
|
|
90
92
|
disableOverflowWrapBreaking?: boolean
|
|
91
93
|
}
|
|
@@ -112,6 +114,11 @@ export class TextManager {
|
|
|
112
114
|
'overflow-wrap',
|
|
113
115
|
opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
|
|
114
116
|
)
|
|
117
|
+
if (opts.otherStyles) {
|
|
118
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
119
|
+
wrapperElm.style.setProperty(key, value)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
115
122
|
|
|
116
123
|
const scrollWidth = wrapperElm.scrollWidth
|
|
117
124
|
const rect = wrapperElm.getBoundingClientRect()
|
|
@@ -256,6 +263,11 @@ export class TextManager {
|
|
|
256
263
|
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
257
264
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
258
265
|
elm.style.setProperty('font-style', opts.fontStyle)
|
|
266
|
+
if (opts.otherStyles) {
|
|
267
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
268
|
+
elm.style.setProperty(key, value)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
259
271
|
|
|
260
272
|
const shouldTruncateToFirstLine =
|
|
261
273
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -19,6 +19,7 @@ import { TLFontFace } from '../managers/FontManager'
|
|
|
19
19
|
import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'
|
|
20
20
|
import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'
|
|
21
21
|
import { SvgExportContext } from '../types/SvgExportContext'
|
|
22
|
+
import { TLClickEventInfo } from '../types/event-types'
|
|
22
23
|
import { TLResizeHandle } from '../types/selection-types'
|
|
23
24
|
|
|
24
25
|
/** @public */
|
|
@@ -671,10 +672,21 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
671
672
|
* A callback called when a shape's edge is double clicked.
|
|
672
673
|
*
|
|
673
674
|
* @param shape - The shape.
|
|
675
|
+
* @param info - Info about the edge.
|
|
674
676
|
* @returns A change to apply to the shape, or void.
|
|
675
677
|
* @public
|
|
676
678
|
*/
|
|
677
|
-
onDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void
|
|
679
|
+
onDoubleClickEdge?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* A callback called when a shape's corner is double clicked.
|
|
683
|
+
*
|
|
684
|
+
* @param shape - The shape.
|
|
685
|
+
* @param info - Info about the corner.
|
|
686
|
+
* @returns A change to apply to the shape, or void.
|
|
687
|
+
* @public
|
|
688
|
+
*/
|
|
689
|
+
onDoubleClickCorner?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
|
|
678
690
|
|
|
679
691
|
/**
|
|
680
692
|
* A callback called when a shape is double clicked.
|
|
@@ -365,6 +365,21 @@ function SvgExport({
|
|
|
365
365
|
onMount()
|
|
366
366
|
}, [onMount, shapeElements])
|
|
367
367
|
|
|
368
|
+
let backgroundColor = background ? theme.background : 'transparent'
|
|
369
|
+
|
|
370
|
+
if (singleFrameShapeId && background) {
|
|
371
|
+
const frameShapeUtil = editor.getShapeUtil('frame') as any as
|
|
372
|
+
| undefined
|
|
373
|
+
| { options: { showColors: boolean } }
|
|
374
|
+
if (frameShapeUtil?.options.showColors) {
|
|
375
|
+
const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
|
|
376
|
+
const color = theme[shape.props.color]
|
|
377
|
+
backgroundColor = color.frame.fill
|
|
378
|
+
} else {
|
|
379
|
+
backgroundColor = theme.solid
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
return (
|
|
369
384
|
<SvgExportContextProvider editor={editor} context={exportContext}>
|
|
370
385
|
<svg
|
|
@@ -375,13 +390,7 @@ function SvgExport({
|
|
|
375
390
|
viewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}
|
|
376
391
|
strokeLinecap="round"
|
|
377
392
|
strokeLinejoin="round"
|
|
378
|
-
style={{
|
|
379
|
-
backgroundColor: background
|
|
380
|
-
? singleFrameShapeId
|
|
381
|
-
? theme.solid
|
|
382
|
-
: theme.background
|
|
383
|
-
: 'transparent',
|
|
384
|
-
}}
|
|
393
|
+
style={{ backgroundColor }}
|
|
385
394
|
data-color-mode={isDarkMode ? 'dark' : 'light'}
|
|
386
395
|
className={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}
|
|
387
396
|
>
|
|
@@ -11,6 +11,7 @@ export function useDocumentEvents() {
|
|
|
11
11
|
const editor = useEditor()
|
|
12
12
|
const container = useContainer()
|
|
13
13
|
|
|
14
|
+
const isEditing = useValue('isEditing', () => editor.getEditingShapeId(), [editor])
|
|
14
15
|
const isAppFocused = useValue('isFocused', () => editor.getIsFocused(), [editor])
|
|
15
16
|
|
|
16
17
|
// Prevent the browser's default drag and drop behavior on our container (UI, etc)
|
|
@@ -125,7 +126,11 @@ export function useDocumentEvents() {
|
|
|
125
126
|
if (areShortcutsDisabled(editor)) {
|
|
126
127
|
return
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
// isEditing here sounds like it's about text editing
|
|
130
|
+
// but more specifically, this is so you can tab into an
|
|
131
|
+
// embed that's being 'edited'. In our world,
|
|
132
|
+
// editing an embed, means it's interactive.
|
|
133
|
+
if (hasSelectedShapes && !isEditing) {
|
|
129
134
|
// This is used in tandem with shape navigation.
|
|
130
135
|
preventDefault(e)
|
|
131
136
|
}
|
|
@@ -289,7 +294,7 @@ export function useDocumentEvents() {
|
|
|
289
294
|
container.removeEventListener('keydown', handleKeyDown)
|
|
290
295
|
container.removeEventListener('keyup', handleKeyUp)
|
|
291
296
|
}
|
|
292
|
-
}, [editor, container, isAppFocused])
|
|
297
|
+
}, [editor, container, isAppFocused, isEditing])
|
|
293
298
|
}
|
|
294
299
|
|
|
295
300
|
function areShortcutsDisabled(editor: Editor) {
|
|
@@ -51,29 +51,30 @@ import { useShallowObjectIdentity } from './useIdentity'
|
|
|
51
51
|
/** @public */
|
|
52
52
|
export interface TLEditorComponents {
|
|
53
53
|
Background?: ComponentType | null
|
|
54
|
-
SvgDefs?: ComponentType | null
|
|
55
54
|
Brush?: ComponentType<TLBrushProps> | null
|
|
56
|
-
ZoomBrush?: ComponentType<TLBrushProps> | null
|
|
57
|
-
ShapeIndicators?: ComponentType | null
|
|
58
|
-
ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
|
|
59
|
-
Cursor?: ComponentType<TLCursorProps> | null
|
|
60
55
|
Canvas?: ComponentType<TLCanvasComponentProps> | null
|
|
61
56
|
CollaboratorBrush?: ComponentType<TLBrushProps> | null
|
|
62
57
|
CollaboratorCursor?: ComponentType<TLCursorProps> | null
|
|
63
58
|
CollaboratorHint?: ComponentType<TLCollaboratorHintProps> | null
|
|
59
|
+
CollaboratorScribble?: ComponentType<TLScribbleProps> | null
|
|
64
60
|
CollaboratorShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
|
|
61
|
+
Cursor?: ComponentType<TLCursorProps> | null
|
|
65
62
|
Grid?: ComponentType<TLGridProps> | null
|
|
66
|
-
Scribble?: ComponentType<TLScribbleProps> | null
|
|
67
|
-
CollaboratorScribble?: ComponentType<TLScribbleProps> | null
|
|
68
|
-
SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
|
|
69
|
-
Handles?: ComponentType<TLHandlesProps> | null
|
|
70
63
|
Handle?: ComponentType<TLHandleProps> | null
|
|
71
|
-
|
|
72
|
-
SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
|
|
73
|
-
SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
|
|
74
|
-
OnTheCanvas?: ComponentType | null
|
|
64
|
+
Handles?: ComponentType<TLHandlesProps> | null
|
|
75
65
|
InFrontOfTheCanvas?: ComponentType | null
|
|
76
66
|
LoadingScreen?: ComponentType | null
|
|
67
|
+
OnTheCanvas?: ComponentType | null
|
|
68
|
+
Overlays?: ComponentType | null
|
|
69
|
+
Scribble?: ComponentType<TLScribbleProps> | null
|
|
70
|
+
SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
|
|
71
|
+
SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
|
|
72
|
+
ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
|
|
73
|
+
ShapeIndicators?: ComponentType | null
|
|
74
|
+
SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
|
|
75
|
+
Spinner?: ComponentType | null
|
|
76
|
+
SvgDefs?: ComponentType | null
|
|
77
|
+
ZoomBrush?: ComponentType<TLBrushProps> | null
|
|
77
78
|
|
|
78
79
|
// These will always have defaults
|
|
79
80
|
ErrorFallback?: TLErrorFallbackComponent
|
|
@@ -96,32 +97,35 @@ export function EditorComponentsProvider({
|
|
|
96
97
|
const value = useMemo(
|
|
97
98
|
(): Required<TLEditorComponents> => ({
|
|
98
99
|
Background: DefaultBackground,
|
|
99
|
-
SvgDefs: DefaultSvgDefs,
|
|
100
100
|
Brush: DefaultBrush,
|
|
101
|
-
|
|
101
|
+
Canvas: DefaultCanvas,
|
|
102
102
|
CollaboratorBrush: DefaultBrush,
|
|
103
|
-
Cursor: DefaultCursor,
|
|
104
103
|
CollaboratorCursor: DefaultCursor,
|
|
105
104
|
CollaboratorHint: DefaultCollaboratorHint,
|
|
105
|
+
CollaboratorScribble: DefaultScribble,
|
|
106
106
|
CollaboratorShapeIndicator: DefaultShapeIndicator,
|
|
107
|
+
Cursor: DefaultCursor,
|
|
107
108
|
Grid: DefaultGrid,
|
|
109
|
+
Handle: DefaultHandle,
|
|
110
|
+
Handles: DefaultHandles,
|
|
111
|
+
InFrontOfTheCanvas: null,
|
|
112
|
+
LoadingScreen: DefaultLoadingScreen,
|
|
113
|
+
OnTheCanvas: null,
|
|
114
|
+
Overlays: null,
|
|
108
115
|
Scribble: DefaultScribble,
|
|
116
|
+
SelectionBackground: DefaultSelectionBackground,
|
|
117
|
+
SelectionForeground: DefaultSelectionForeground,
|
|
118
|
+
ShapeIndicator: DefaultShapeIndicator,
|
|
119
|
+
ShapeIndicators: DefaultShapeIndicators,
|
|
109
120
|
SnapIndicator: DefaultSnapIndicator,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
Spinner: DefaultSpinner,
|
|
122
|
+
SvgDefs: DefaultSvgDefs,
|
|
123
|
+
ZoomBrush: DefaultBrush,
|
|
124
|
+
|
|
113
125
|
ErrorFallback: DefaultErrorFallback,
|
|
114
126
|
ShapeErrorFallback: DefaultShapeErrorFallback,
|
|
115
127
|
ShapeIndicatorErrorFallback: DefaultShapeIndicatorErrorFallback,
|
|
116
|
-
|
|
117
|
-
SelectionBackground: DefaultSelectionBackground,
|
|
118
|
-
SelectionForeground: DefaultSelectionForeground,
|
|
119
|
-
ShapeIndicators: DefaultShapeIndicators,
|
|
120
|
-
ShapeIndicator: DefaultShapeIndicator,
|
|
121
|
-
OnTheCanvas: null,
|
|
122
|
-
InFrontOfTheCanvas: null,
|
|
123
|
-
Canvas: DefaultCanvas,
|
|
124
|
-
LoadingScreen: DefaultLoadingScreen,
|
|
128
|
+
|
|
125
129
|
..._overrides,
|
|
126
130
|
}),
|
|
127
131
|
[_overrides]
|
|
@@ -317,6 +317,46 @@ describe('LicenseManager', () => {
|
|
|
317
317
|
expect(result.isDomainValid).toBe(false)
|
|
318
318
|
})
|
|
319
319
|
|
|
320
|
+
it('Succeeds if it is a vscode extension', async () => {
|
|
321
|
+
// @ts-ignore
|
|
322
|
+
delete window.location
|
|
323
|
+
// @ts-ignore
|
|
324
|
+
window.location = new URL(
|
|
325
|
+
'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
329
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['tldraw-org.tldraw-vscode']
|
|
330
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
331
|
+
JSON.stringify(permissiveHostsInfo),
|
|
332
|
+
keyPair
|
|
333
|
+
)
|
|
334
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
335
|
+
permissiveLicenseKey
|
|
336
|
+
)) as ValidLicenseKeyResult
|
|
337
|
+
expect(result.isDomainValid).toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('Fails if it is a vscode extension with the wrong id', async () => {
|
|
341
|
+
// @ts-ignore
|
|
342
|
+
delete window.location
|
|
343
|
+
// @ts-ignore
|
|
344
|
+
window.location = new URL(
|
|
345
|
+
'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
349
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['blah-org.blah-vscode']
|
|
350
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
351
|
+
JSON.stringify(permissiveHostsInfo),
|
|
352
|
+
keyPair
|
|
353
|
+
)
|
|
354
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
355
|
+
permissiveLicenseKey
|
|
356
|
+
)) as ValidLicenseKeyResult
|
|
357
|
+
expect(result.isDomainValid).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
|
|
320
360
|
it('Checks for internal license', async () => {
|
|
321
361
|
const internalLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
322
362
|
internalLicenseInfo[PROPERTIES.FLAGS] = FLAGS.INTERNAL_LICENSE
|
|
@@ -111,7 +111,10 @@ export class LicenseManager {
|
|
|
111
111
|
if (testEnvironment === 'production') return false
|
|
112
112
|
|
|
113
113
|
// If we are using https on a non-localhost domain we assume it's a production env and a development one otherwise
|
|
114
|
-
return
|
|
114
|
+
return (
|
|
115
|
+
!['https:', 'vscode-webview:'].includes(window.location.protocol) ||
|
|
116
|
+
window.location.hostname === 'localhost'
|
|
117
|
+
)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
private async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {
|
|
@@ -250,6 +253,15 @@ export class LicenseManager {
|
|
|
250
253
|
return globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)
|
|
251
254
|
}
|
|
252
255
|
|
|
256
|
+
// VSCode support
|
|
257
|
+
if (window.location.protocol === 'vscode-webview:') {
|
|
258
|
+
const currentUrl = new URL(window.location.href)
|
|
259
|
+
const extensionId = currentUrl.searchParams.get('extensionId')
|
|
260
|
+
if (normalizedHost === extensionId) {
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
return false
|
|
254
266
|
})
|
|
255
267
|
}
|
package/src/lib/options.ts
CHANGED
|
@@ -80,6 +80,10 @@ export interface TldrawOptions {
|
|
|
80
80
|
* nonce to use in the editor's styles.
|
|
81
81
|
*/
|
|
82
82
|
readonly nonce: string | undefined
|
|
83
|
+
/**
|
|
84
|
+
* Branding name of the app, currently only used for adding aria-label for the application.
|
|
85
|
+
*/
|
|
86
|
+
readonly branding?: string
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/** @public */
|
package/src/lib/utils/dom.ts
CHANGED
|
@@ -91,14 +91,14 @@ export const setStyleProperty = (
|
|
|
91
91
|
elm.style.setProperty(property, value as string)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
95
|
-
|
|
96
94
|
/** @internal */
|
|
97
|
-
export function activeElementShouldCaptureKeys() {
|
|
95
|
+
export function activeElementShouldCaptureKeys(allowButtons = false) {
|
|
98
96
|
const { activeElement } = document
|
|
97
|
+
const elements = allowButtons ? ['input', 'textarea'] : ['input', 'select', 'button', 'textarea']
|
|
99
98
|
return !!(
|
|
100
99
|
activeElement &&
|
|
101
100
|
((activeElement as HTMLElement).isContentEditable ||
|
|
102
|
-
|
|
101
|
+
elements.indexOf(activeElement.tagName.toLowerCase()) > -1 ||
|
|
102
|
+
activeElement.classList.contains('tlui-slider__thumb'))
|
|
103
103
|
)
|
|
104
104
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Euclidean algorithm to find the GCD
|
|
2
|
+
function gcd(a: number, b: number): number {
|
|
3
|
+
return b === 0 ? a : gcd(b, a % b)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Returns the lowest value that the given number can be multiplied by to reach an integer
|
|
7
|
+
export function nearestMultiple(float: number) {
|
|
8
|
+
const decimal = float.toString().split('.')[1]
|
|
9
|
+
if (!decimal) return 1
|
|
10
|
+
const denominator = Math.pow(10, decimal.length)
|
|
11
|
+
const numerator = parseInt(decimal, 10)
|
|
12
|
+
return denominator / gcd(numerator, denominator)
|
|
13
|
+
}
|
|
@@ -26,11 +26,13 @@ export function getRotationSnapshot({
|
|
|
26
26
|
return null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const initialPageCenter = rotatedPageBounds.center
|
|
30
|
+
.clone()
|
|
31
|
+
.rotWith(rotatedPageBounds.point, rotation)
|
|
30
32
|
|
|
31
33
|
return {
|
|
32
|
-
|
|
33
|
-
initialCursorAngle:
|
|
34
|
+
initialPageCenter,
|
|
35
|
+
initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
|
|
34
36
|
initialShapesRotation: rotation,
|
|
35
37
|
shapeSnapshots: shapes.map((shape) => ({
|
|
36
38
|
shape,
|
|
@@ -43,7 +45,7 @@ export function getRotationSnapshot({
|
|
|
43
45
|
* @internal
|
|
44
46
|
**/
|
|
45
47
|
export interface TLRotationSnapshot {
|
|
46
|
-
|
|
48
|
+
initialPageCenter: Vec
|
|
47
49
|
initialCursorAngle: number
|
|
48
50
|
initialShapesRotation: number
|
|
49
51
|
shapeSnapshots: {
|
|
@@ -66,7 +68,7 @@ export function applyRotationToSnapshotShapes({
|
|
|
66
68
|
stage: 'start' | 'update' | 'end' | 'one-off'
|
|
67
69
|
centerOverride?: VecLike
|
|
68
70
|
}) {
|
|
69
|
-
const {
|
|
71
|
+
const { initialPageCenter, shapeSnapshots } = snapshot
|
|
70
72
|
|
|
71
73
|
editor.updateShapes(
|
|
72
74
|
shapeSnapshots.map(({ shape, initialPagePoint }) => {
|
|
@@ -77,7 +79,7 @@ export function applyRotationToSnapshotShapes({
|
|
|
77
79
|
? editor.getShapePageTransform(shape.parentId)!
|
|
78
80
|
: Mat.Identity()
|
|
79
81
|
|
|
80
|
-
const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ??
|
|
82
|
+
const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)
|
|
81
83
|
|
|
82
84
|
const newLocalPoint = Mat.applyToPoint(
|
|
83
85
|
// use the current parent transform in case it has moved/resized since the start
|
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.13.0-canary.
|
|
4
|
+
export const version = '3.13.0-canary.c3ce2eeb1729'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-05-07T09:23:48.217Z',
|
|
8
|
+
patch: '2025-05-07T09:23:48.217Z',
|
|
9
9
|
}
|