tldraw 4.1.0-canary.ca796f65c3a4 → 4.1.0-canary.caaa99b713a2
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 +21 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/canvas/TldrawCropHandles.js +1 -1
- package/dist-cjs/lib/canvas/TldrawScribble.js +1 -1
- package/dist-cjs/lib/canvas/TldrawSelectionForeground.js +1 -1
- package/dist-cjs/lib/defaultEmbedDefinitions.js +26 -7
- package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
- package/dist-cjs/lib/defaultExternalContentHandlers.js +1 -1
- package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +3 -0
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +14 -2
- package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/line/LineShapeUtil.js +3 -0
- package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +1 -1
- package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +1 -1
- package/dist-cjs/lib/shapes/shared/RichTextLabel.js +1 -1
- package/dist-cjs/lib/shapes/shared/ShapeFill.js +1 -1
- package/dist-cjs/lib/shapes/text/PlainTextArea.js +1 -1
- package/dist-cjs/lib/shapes/text/RichTextArea.js +1 -1
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +1 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Idle.js +1 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Idle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +9 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +1 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
- package/dist-cjs/lib/ui/TldrawUi.js +2 -2
- package/dist-cjs/lib/ui/components/DebugMenu/DefaultDebugMenuContent.js +1 -1
- package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialog.js +1 -1
- package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +1 -1
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +5 -0
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
- package/dist-cjs/lib/ui/components/OfflineIndicator/OfflineIndicator.js +1 -1
- package/dist-cjs/lib/ui/components/SharePanel/PeopleMenu.js +6 -2
- package/dist-cjs/lib/ui/components/SharePanel/PeopleMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js +1 -1
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelDropdownPicker.js +1 -1
- package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +1 -1
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +1 -1
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/Button/TldrawUiButton.js +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiDialog.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/layout.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +1 -1
- package/dist-cjs/lib/ui/context/actions.js +1 -1
- package/dist-cjs/lib/ui/context/breakpoints.js +1 -1
- package/dist-cjs/lib/ui/context/events.js +1 -1
- package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +1 -1
- package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +1 -1
- package/dist-cjs/lib/ui/hooks/useLocalStorageState.js +1 -1
- package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
- package/dist-cjs/lib/ui/hooks/useTranslation/useTranslation.js +1 -1
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-cjs/lib/utils/text/richText.js +4 -4
- package/dist-esm/index.d.mts +21 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/defaultEmbedDefinitions.mjs +26 -7
- package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
- package/dist-esm/lib/defaultExternalContentHandlers.mjs +1 -1
- package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +3 -0
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +13 -1
- package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +3 -0
- package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Idle.mjs +1 -1
- package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Idle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +11 -2
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +1 -1
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +5 -0
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/PeopleMenu.mjs +6 -2
- package/dist-esm/lib/ui/components/SharePanel/PeopleMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +1 -1
- package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +1 -1
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/package.json +11 -11
- package/src/lib/defaultEmbedDefinitions.ts +21 -1
- package/src/lib/defaultExternalContentHandlers.ts +1 -1
- package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +3 -0
- package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +13 -3
- package/src/lib/shapes/line/LineShapeUtil.tsx +3 -0
- package/src/lib/shapes/text/TextShapeTool.test.ts +74 -0
- package/src/lib/tools/SelectTool/childStates/Crop/children/Idle.ts +1 -1
- package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +13 -1
- package/src/lib/tools/SelectTool/childStates/Idle.ts +1 -1
- package/src/lib/ui/components/Minimap/MinimapManager.ts +6 -0
- package/src/lib/ui/components/SharePanel/PeopleMenu.tsx +6 -2
- package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +1 -1
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/utils/embeds/embeds.test.ts +16 -0
- package/src/test/customSnapping.test.tsx +55 -11
- package/tldraw.css +7 -2
|
@@ -35,6 +35,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
35
35
|
}
|
|
36
36
|
return
|
|
37
37
|
},
|
|
38
|
+
embedOnPaste: false,
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
type: 'figma',
|
|
@@ -65,6 +66,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
65
66
|
}
|
|
66
67
|
return
|
|
67
68
|
},
|
|
69
|
+
embedOnPaste: true,
|
|
68
70
|
},
|
|
69
71
|
{
|
|
70
72
|
type: 'google_maps',
|
|
@@ -116,6 +118,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
116
118
|
}
|
|
117
119
|
return
|
|
118
120
|
},
|
|
121
|
+
embedOnPaste: true,
|
|
119
122
|
},
|
|
120
123
|
{
|
|
121
124
|
type: 'val_town',
|
|
@@ -144,6 +147,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
144
147
|
}
|
|
145
148
|
return
|
|
146
149
|
},
|
|
150
|
+
embedOnPaste: true,
|
|
147
151
|
},
|
|
148
152
|
{
|
|
149
153
|
type: 'codesandbox',
|
|
@@ -170,6 +174,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
170
174
|
}
|
|
171
175
|
return
|
|
172
176
|
},
|
|
177
|
+
embedOnPaste: true,
|
|
173
178
|
},
|
|
174
179
|
{
|
|
175
180
|
type: 'codepen',
|
|
@@ -198,6 +203,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
198
203
|
}
|
|
199
204
|
return
|
|
200
205
|
},
|
|
206
|
+
embedOnPaste: true,
|
|
201
207
|
},
|
|
202
208
|
{
|
|
203
209
|
type: 'scratch',
|
|
@@ -206,6 +212,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
206
212
|
width: 520,
|
|
207
213
|
height: 400,
|
|
208
214
|
doesResize: false,
|
|
215
|
+
embedOnPaste: true,
|
|
209
216
|
toEmbedUrl: (url) => {
|
|
210
217
|
const SCRATCH_URL_REGEXP = /https?:\/\/scratch.mit.edu\/projects\/([^/]+)/
|
|
211
218
|
const matches = url.match(SCRATCH_URL_REGEXP)
|
|
@@ -237,6 +244,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
237
244
|
'allow-popups-to-escape-sandbox': true,
|
|
238
245
|
},
|
|
239
246
|
isAspectRatioLocked: true,
|
|
247
|
+
embedOnPaste: true,
|
|
240
248
|
toEmbedUrl: (url) => {
|
|
241
249
|
const urlObj = safeParseUrl(url)
|
|
242
250
|
if (!urlObj) return
|
|
@@ -303,6 +311,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
303
311
|
overridePermissions: {
|
|
304
312
|
'allow-popups-to-escape-sandbox': true,
|
|
305
313
|
},
|
|
314
|
+
embedOnPaste: true,
|
|
306
315
|
toEmbedUrl: (url) => {
|
|
307
316
|
const urlObj = safeParseUrl(url)
|
|
308
317
|
const cidQs = urlObj?.searchParams.get('cid')
|
|
@@ -347,6 +356,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
347
356
|
overridePermissions: {
|
|
348
357
|
'allow-popups-to-escape-sandbox': true,
|
|
349
358
|
},
|
|
359
|
+
embedOnPaste: true,
|
|
350
360
|
toEmbedUrl: (url) => {
|
|
351
361
|
const urlObj = safeParseUrl(url)
|
|
352
362
|
|
|
@@ -381,6 +391,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
381
391
|
width: 720,
|
|
382
392
|
height: 500,
|
|
383
393
|
doesResize: true,
|
|
394
|
+
embedOnPaste: true,
|
|
384
395
|
// Security warning:
|
|
385
396
|
// Gists allow adding .json extensions to the URL which return JSONP.
|
|
386
397
|
// Furthermore, the JSONP can include callbacks that execute arbitrary JavaScript.
|
|
@@ -413,10 +424,12 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
413
424
|
width: 720,
|
|
414
425
|
height: 500,
|
|
415
426
|
doesResize: true,
|
|
427
|
+
embedOnPaste: true,
|
|
416
428
|
toEmbedUrl: (url) => {
|
|
417
429
|
const urlObj = safeParseUrl(url)
|
|
418
430
|
if (urlObj && urlObj.pathname.match(/\/@([^/]+)\/([^/]+)/)) {
|
|
419
|
-
|
|
431
|
+
urlObj.searchParams.append('embed', 'true')
|
|
432
|
+
return urlObj.href
|
|
420
433
|
}
|
|
421
434
|
return
|
|
422
435
|
},
|
|
@@ -440,6 +453,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
440
453
|
width: 720,
|
|
441
454
|
height: 500,
|
|
442
455
|
doesResize: true,
|
|
456
|
+
embedOnPaste: true,
|
|
443
457
|
toEmbedUrl: (url) => {
|
|
444
458
|
const urlObj = safeParseUrl(url)
|
|
445
459
|
if (urlObj && urlObj.pathname.match(/^\/map\//)) {
|
|
@@ -465,6 +479,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
465
479
|
minHeight: 500,
|
|
466
480
|
overrideOutlineRadius: 12,
|
|
467
481
|
doesResize: true,
|
|
482
|
+
embedOnPaste: true,
|
|
468
483
|
toEmbedUrl: (url) => {
|
|
469
484
|
const urlObj = safeParseUrl(url)
|
|
470
485
|
if (urlObj && urlObj.pathname.match(/^\/(artist|album)\//)) {
|
|
@@ -488,6 +503,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
488
503
|
height: 360,
|
|
489
504
|
doesResize: true,
|
|
490
505
|
isAspectRatioLocked: true,
|
|
506
|
+
embedOnPaste: true,
|
|
491
507
|
toEmbedUrl: (url) => {
|
|
492
508
|
const urlObj = safeParseUrl(url)
|
|
493
509
|
if (urlObj && urlObj.hostname === 'vimeo.com') {
|
|
@@ -518,6 +534,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
518
534
|
height: 500,
|
|
519
535
|
doesResize: true,
|
|
520
536
|
isAspectRatioLocked: true,
|
|
537
|
+
embedOnPaste: true,
|
|
521
538
|
toEmbedUrl: (url) => {
|
|
522
539
|
const urlObj = safeParseUrl(url)
|
|
523
540
|
if (urlObj && urlObj.hash.match(/#room=/)) {
|
|
@@ -542,6 +559,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
542
559
|
doesResize: true,
|
|
543
560
|
isAspectRatioLocked: false,
|
|
544
561
|
backgroundColor: '#fff',
|
|
562
|
+
embedOnPaste: true,
|
|
545
563
|
toEmbedUrl: (url) => {
|
|
546
564
|
const urlObj = safeParseUrl(url)
|
|
547
565
|
if (urlObj && urlObj.pathname.match(/^\/@([^/]+)\/([^/]+)\/?$/)) {
|
|
@@ -573,6 +591,7 @@ export const DEFAULT_EMBED_DEFINITIONS = [
|
|
|
573
591
|
width: 700,
|
|
574
592
|
height: 450,
|
|
575
593
|
doesResize: true,
|
|
594
|
+
embedOnPaste: true,
|
|
576
595
|
toEmbedUrl: (url) => {
|
|
577
596
|
const urlObj = safeParseUrl(url)
|
|
578
597
|
if (
|
|
@@ -673,6 +692,7 @@ export interface EmbedDefinition {
|
|
|
673
692
|
readonly overridePermissions?: TLEmbedShapePermissions
|
|
674
693
|
readonly instructionLink?: string
|
|
675
694
|
readonly backgroundColor?: string
|
|
695
|
+
readonly embedOnPaste?: boolean
|
|
676
696
|
// TODO: FIXME this is ugly be required because some embeds have their own border radius for example spotify embeds
|
|
677
697
|
readonly overrideOutlineRadius?: number
|
|
678
698
|
// eslint-disable-next-line @typescript-eslint/method-signature-style
|
|
@@ -557,7 +557,7 @@ export async function defaultHandleExternalUrlContent(
|
|
|
557
557
|
const embedUtil = editor.getShapeUtil('embed') as EmbedShapeUtil | undefined
|
|
558
558
|
const embedInfo = embedUtil?.getEmbedDefinition(url)
|
|
559
559
|
|
|
560
|
-
if (embedInfo) {
|
|
560
|
+
if (embedInfo && embedInfo.definition.embedOnPaste !== false) {
|
|
561
561
|
return editor.putExternalContent({
|
|
562
562
|
type: 'embed',
|
|
563
563
|
url: embedInfo.url,
|
|
@@ -144,6 +144,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|
|
144
144
|
override hideSelectionBoundsFg() {
|
|
145
145
|
return true
|
|
146
146
|
}
|
|
147
|
+
override hideInMinimap() {
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
147
150
|
|
|
148
151
|
override canBeLaidOut(shape: TLArrowShape, info: TLShapeUtilCanBeLaidOutOpts) {
|
|
149
152
|
if (info.type === 'flip') {
|
|
@@ -168,9 +168,19 @@ function BookmarkShapeComponent({ shape }: { shape: TLBookmarkShape }) {
|
|
|
168
168
|
)}
|
|
169
169
|
<div className="tl-bookmark__copy_container">
|
|
170
170
|
{asset?.props.title ? (
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
<a
|
|
172
|
+
className="tl-bookmark__link"
|
|
173
|
+
href={shape.props.url || ''}
|
|
174
|
+
target="_blank"
|
|
175
|
+
rel="noopener noreferrer"
|
|
176
|
+
draggable={false}
|
|
177
|
+
onPointerDown={markAsHandledOnShiftKey}
|
|
178
|
+
onPointerUp={markAsHandledOnShiftKey}
|
|
179
|
+
>
|
|
180
|
+
<h2 className="tl-bookmark__heading">
|
|
181
|
+
{convertCommonTitleHTMLEntities(asset.props.title)}
|
|
182
|
+
</h2>
|
|
183
|
+
</a>
|
|
174
184
|
) : null}
|
|
175
185
|
{asset?.props.description && asset?.props.image ? (
|
|
176
186
|
<p className="tl-bookmark__description">{asset.props.description}</p>
|
|
@@ -48,6 +48,9 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|
|
48
48
|
override hideSelectionBoundsBg() {
|
|
49
49
|
return true
|
|
50
50
|
}
|
|
51
|
+
override hideInMinimap() {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
51
54
|
|
|
52
55
|
override getDefaultProps(): TLLineShape['props'] {
|
|
53
56
|
const [start, end] = getIndices(2)
|
|
@@ -86,6 +86,80 @@ describe('When in idle state', () => {
|
|
|
86
86
|
editor.cancel()
|
|
87
87
|
editor.expectToBeIn('select.idle')
|
|
88
88
|
})
|
|
89
|
+
|
|
90
|
+
it('starts editing selected text shape on Enter key', () => {
|
|
91
|
+
// Create a text shape using the same method as other tests
|
|
92
|
+
expect(editor.getCurrentPageShapes().length).toBe(0)
|
|
93
|
+
editor.setCurrentTool('text')
|
|
94
|
+
editor.pointerDown(0, 0)
|
|
95
|
+
editor.pointerUp()
|
|
96
|
+
editor.expectToBeIn('select.editing_shape')
|
|
97
|
+
|
|
98
|
+
// Update the text shape with some content
|
|
99
|
+
editor.updateShapes<TLTextShape>([
|
|
100
|
+
{
|
|
101
|
+
...editor.getCurrentPageShapes()[0]!,
|
|
102
|
+
type: 'text',
|
|
103
|
+
props: { richText: toRichText('Hello') },
|
|
104
|
+
},
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
// Exit editing mode
|
|
108
|
+
editor.cancel()
|
|
109
|
+
editor.expectToBeIn('select.idle')
|
|
110
|
+
|
|
111
|
+
// Verify the text shape exists and is selected
|
|
112
|
+
expect(editor.getCurrentPageShapes().length).toBe(1)
|
|
113
|
+
const textShape = editor.getCurrentPageShapes()[0]
|
|
114
|
+
expect(textShape.type).toBe('text')
|
|
115
|
+
editor.setSelectedShapes([textShape])
|
|
116
|
+
|
|
117
|
+
// Switch to text tool and press Enter
|
|
118
|
+
editor.setCurrentTool('text')
|
|
119
|
+
editor.expectToBeIn('text.idle')
|
|
120
|
+
editor.keyDown('Enter')
|
|
121
|
+
|
|
122
|
+
// Should transition to editing the selected text shape
|
|
123
|
+
editor.expectToBeIn('select.editing_shape')
|
|
124
|
+
expect(editor.getEditingShapeId()).toBe(textShape.id)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('starts editing selected text shape on numpad Enter key', () => {
|
|
128
|
+
// Create a text shape using the same method as other tests
|
|
129
|
+
expect(editor.getCurrentPageShapes().length).toBe(0)
|
|
130
|
+
editor.setCurrentTool('text')
|
|
131
|
+
editor.pointerDown(0, 0)
|
|
132
|
+
editor.pointerUp()
|
|
133
|
+
editor.expectToBeIn('select.editing_shape')
|
|
134
|
+
|
|
135
|
+
// Update the text shape with some content
|
|
136
|
+
editor.updateShapes<TLTextShape>([
|
|
137
|
+
{
|
|
138
|
+
...editor.getCurrentPageShapes()[0]!,
|
|
139
|
+
type: 'text',
|
|
140
|
+
props: { richText: toRichText('Hello') },
|
|
141
|
+
},
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
// Exit editing mode
|
|
145
|
+
editor.cancel()
|
|
146
|
+
editor.expectToBeIn('select.idle')
|
|
147
|
+
|
|
148
|
+
// Verify the text shape exists and is selected
|
|
149
|
+
expect(editor.getCurrentPageShapes().length).toBe(1)
|
|
150
|
+
const textShape = editor.getCurrentPageShapes()[0]
|
|
151
|
+
expect(textShape.type).toBe('text')
|
|
152
|
+
editor.setSelectedShapes([textShape])
|
|
153
|
+
|
|
154
|
+
// Switch to text tool and press numpad Enter
|
|
155
|
+
editor.setCurrentTool('text')
|
|
156
|
+
editor.expectToBeIn('text.idle')
|
|
157
|
+
editor.keyDown('Enter', { code: 'NumpadEnter' })
|
|
158
|
+
|
|
159
|
+
// Should transition to editing the selected text shape
|
|
160
|
+
editor.expectToBeIn('select.editing_shape')
|
|
161
|
+
expect(editor.getEditingShapeId()).toBe(textShape.id)
|
|
162
|
+
})
|
|
89
163
|
})
|
|
90
164
|
|
|
91
165
|
describe('When in the pointing state', () => {
|
|
@@ -143,7 +143,7 @@ export class Idle extends StateNode {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
override onKeyUp(info: TLKeyboardEventInfo) {
|
|
146
|
-
switch (info.
|
|
146
|
+
switch (info.key) {
|
|
147
147
|
case 'Enter': {
|
|
148
148
|
this.editor.setCroppingShape(null)
|
|
149
149
|
this.editor.setCurrentTool('select.idle', {})
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
snapAngle,
|
|
13
13
|
sortByIndex,
|
|
14
14
|
structuredClone,
|
|
15
|
+
warnOnce,
|
|
15
16
|
} from '@tldraw/editor'
|
|
16
17
|
import { ArrowShapeUtil } from '../../../shapes/arrow/ArrowShapeUtil'
|
|
17
18
|
import { clearArrowTargetState } from '../../../shapes/arrow/arrowTargetState'
|
|
@@ -294,7 +295,18 @@ export class DraggingHandle extends StateNode {
|
|
|
294
295
|
|
|
295
296
|
let nextHandle = { ...initialHandle, x: point.x, y: point.y }
|
|
296
297
|
|
|
297
|
-
|
|
298
|
+
let canSnap = false
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
300
|
+
if (initialHandle.canSnap && initialHandle.snapType) {
|
|
301
|
+
warnOnce(
|
|
302
|
+
'canSnap is deprecated. Cannot use both canSnap and snapType together - snapping disabled. Please use only snapType.'
|
|
303
|
+
)
|
|
304
|
+
} else {
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
306
|
+
canSnap = initialHandle.canSnap || initialHandle.snapType !== undefined
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (canSnap && (isSnapMode ? !ctrlKey : ctrlKey)) {
|
|
298
310
|
// We're snapping
|
|
299
311
|
const pageTransform = editor.getShapePageTransform(shape.id)
|
|
300
312
|
if (!pageTransform) throw Error('Expected a page transform')
|
|
@@ -516,7 +516,7 @@ export class Idle extends StateNode {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
override onKeyUp(info: TLKeyboardEventInfo) {
|
|
519
|
-
switch (info.
|
|
519
|
+
switch (info.key) {
|
|
520
520
|
case 'Enter': {
|
|
521
521
|
// Because Enter onKeyDown can happen outside the canvas (but then focus the canvas potentially),
|
|
522
522
|
// we need to check if the canvas was initially selecting something before continuing.
|
|
@@ -249,6 +249,12 @@ export class MinimapManager {
|
|
|
249
249
|
|
|
250
250
|
const len = geometry.length
|
|
251
251
|
|
|
252
|
+
const shape = this.editor.getShape(shapeId)
|
|
253
|
+
if (shape) {
|
|
254
|
+
const shapeUtil = this.editor.getShapeUtil(shape.type)
|
|
255
|
+
if (shapeUtil.hideInMinimap?.(shape)) continue
|
|
256
|
+
}
|
|
257
|
+
|
|
252
258
|
if (selectedShapes.has(shapeId)) {
|
|
253
259
|
appendVertices(this.gl.selectedShapes, selectedShapeOffset, geometry)
|
|
254
260
|
selectedShapeOffset += len
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useContainer, useEditor, usePeerIds, useValue } from '@tldraw/editor'
|
|
2
2
|
import { Popover as _Popover } from 'radix-ui'
|
|
3
3
|
import { ReactNode } from 'react'
|
|
4
|
+
import { PORTRAIT_BREAKPOINT } from '../../constants'
|
|
5
|
+
import { useBreakpoint } from '../../context/breakpoints'
|
|
4
6
|
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
|
|
5
7
|
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
|
6
8
|
import { PeopleMenuAvatar } from './PeopleMenuAvatar'
|
|
@@ -25,6 +27,8 @@ export function PeopleMenu({ children }: PeopleMenuProps) {
|
|
|
25
27
|
const userName = useValue('user', () => editor.user.getName(), [editor])
|
|
26
28
|
|
|
27
29
|
const [isOpen, onOpenChange] = useMenuIsOpen('people menu')
|
|
30
|
+
const breakpoint = useBreakpoint()
|
|
31
|
+
const maxAvatars = breakpoint <= PORTRAIT_BREAKPOINT.MOBILE_XS ? 1 : 5
|
|
28
32
|
|
|
29
33
|
if (!userIds.length) return null
|
|
30
34
|
|
|
@@ -33,7 +37,7 @@ export function PeopleMenu({ children }: PeopleMenuProps) {
|
|
|
33
37
|
<_Popover.Trigger dir="ltr" asChild>
|
|
34
38
|
<button className="tlui-people-menu__avatars-button" title={msg('people-menu.title')}>
|
|
35
39
|
<div className="tlui-people-menu__avatars">
|
|
36
|
-
{userIds.slice(-
|
|
40
|
+
{userIds.slice(-maxAvatars).map((userId) => (
|
|
37
41
|
<PeopleMenuAvatar key={userId} userId={userId} />
|
|
38
42
|
))}
|
|
39
43
|
{userIds.length > 0 && (
|
|
@@ -46,7 +50,7 @@ export function PeopleMenu({ children }: PeopleMenuProps) {
|
|
|
46
50
|
{userName?.[0] ?? ''}
|
|
47
51
|
</div>
|
|
48
52
|
)}
|
|
49
|
-
{userIds.length >
|
|
53
|
+
{userIds.length > maxAvatars && <PeopleMenuMore count={userIds.length - maxAvatars} />}
|
|
50
54
|
</div>
|
|
51
55
|
</button>
|
|
52
56
|
</_Popover.Trigger>
|
|
@@ -132,7 +132,7 @@ export const StylePanelButtonPicker = memo(function StylePanelButtonPicker<T ext
|
|
|
132
132
|
<TldrawUiToolbarToggleGroup
|
|
133
133
|
data-testid={`style.${uiType}`}
|
|
134
134
|
type="single"
|
|
135
|
-
value={value.type === 'shared' ? value.value :
|
|
135
|
+
value={value.type === 'shared' ? value.value : null}
|
|
136
136
|
asChild
|
|
137
137
|
>
|
|
138
138
|
<Layout>
|
package/src/lib/ui/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 = '4.1.0-canary.
|
|
4
|
+
export const version = '4.1.0-canary.caaa99b713a2'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-10-10T09:10:50.472Z',
|
|
8
|
+
patch: '2025-10-10T09:10:50.472Z',
|
|
9
9
|
}
|
|
@@ -275,6 +275,14 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
|
|
|
275
275
|
embedUrl: `https://replit.com/@omar/Blob-Generator?embed=true`,
|
|
276
276
|
},
|
|
277
277
|
},
|
|
278
|
+
{
|
|
279
|
+
url: 'https://replit.com/@omar/Blob-Generator#index.html',
|
|
280
|
+
match: true,
|
|
281
|
+
output: {
|
|
282
|
+
type: 'replit',
|
|
283
|
+
embedUrl: `https://replit.com/@omar/Blob-Generator?embed=true#index.html`,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
278
286
|
{
|
|
279
287
|
url: 'https://replit.com/foobar',
|
|
280
288
|
match: false,
|
|
@@ -599,6 +607,14 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
|
|
|
599
607
|
url: `https://replit.com/@omar/Blob-Generator`,
|
|
600
608
|
},
|
|
601
609
|
},
|
|
610
|
+
{
|
|
611
|
+
embedUrl: 'https://replit.com/@omar/Blob-Generator?embed=true#index.html',
|
|
612
|
+
match: true,
|
|
613
|
+
output: {
|
|
614
|
+
type: 'replit',
|
|
615
|
+
url: `https://replit.com/@omar/Blob-Generator#index.html`,
|
|
616
|
+
},
|
|
617
|
+
},
|
|
602
618
|
{
|
|
603
619
|
embedUrl: 'https://replit.com/@omar/Blob-Generator',
|
|
604
620
|
match: false,
|
|
@@ -173,6 +173,7 @@ describe('custom handle snapping', () => {
|
|
|
173
173
|
handlePoints: VecModel[] | 'default'
|
|
174
174
|
selfSnapOutline: VecModel[] | 'default'
|
|
175
175
|
selfSnapPoints: VecModel[] | 'default'
|
|
176
|
+
handleSnapType?: 'point' | 'align'
|
|
176
177
|
}
|
|
177
178
|
>
|
|
178
179
|
class TestShapeUtil extends BaseBoxShapeUtil<TestShape> {
|
|
@@ -213,17 +214,23 @@ describe('custom handle snapping', () => {
|
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
override getHandles(shape: TestShape): TLHandle[] {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
217
|
+
const handle: TLHandle = {
|
|
218
|
+
id: 'handle',
|
|
219
|
+
label: 'handle',
|
|
220
|
+
type: 'vertex',
|
|
221
|
+
x: shape.props.ownHandle.x,
|
|
222
|
+
y: shape.props.ownHandle.y,
|
|
223
|
+
index: ZERO_INDEX_KEY,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (shape.props.handleSnapType) {
|
|
227
|
+
handle.snapType = shape.props.handleSnapType
|
|
228
|
+
} else {
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
230
|
+
handle.canSnap = true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return [handle]
|
|
227
234
|
}
|
|
228
235
|
override onHandleDrag(shape: TestShape, { handle }: TLHandleDragInfo<TestShape>) {
|
|
229
236
|
return { ...shape, props: { ...shape.props, ownHandle: { x: handle.x, y: handle.y } } }
|
|
@@ -495,5 +502,42 @@ describe('custom handle snapping', () => {
|
|
|
495
502
|
expect(ownHandlePosition()).toMatchObject({ x: 20, y: 50 })
|
|
496
503
|
})
|
|
497
504
|
})
|
|
505
|
+
|
|
506
|
+
describe('with snapType set to align', () => {
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
editor.updateShape<TestShape>({
|
|
509
|
+
id: ids.test,
|
|
510
|
+
type: 'test',
|
|
511
|
+
props: {
|
|
512
|
+
selfSnapPoints: [
|
|
513
|
+
{ x: 20, y: 50 },
|
|
514
|
+
{ x: 60, y: 10 },
|
|
515
|
+
],
|
|
516
|
+
handleSnapType: 'align',
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
test('snaps to the y axis', () => {
|
|
522
|
+
startDraggingOwnHandle()
|
|
523
|
+
editor.pointerMove(18, 0, undefined, { ctrlKey: true })
|
|
524
|
+
expect(editor.snaps.getIndicators()).toHaveLength(1)
|
|
525
|
+
expect(ownHandlePosition()).toMatchObject({ x: 20, y: 0 })
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
test('snaps to the x axis', () => {
|
|
529
|
+
startDraggingOwnHandle()
|
|
530
|
+
editor.pointerMove(0, 48, undefined, { ctrlKey: true })
|
|
531
|
+
expect(editor.snaps.getIndicators()).toHaveLength(1)
|
|
532
|
+
expect(ownHandlePosition()).toMatchObject({ x: 0, y: 50 })
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
test('snaps to both axes', () => {
|
|
536
|
+
startDraggingOwnHandle()
|
|
537
|
+
editor.pointerMove(18, 9, undefined, { ctrlKey: true })
|
|
538
|
+
expect(editor.snaps.getIndicators()).toHaveLength(2)
|
|
539
|
+
expect(ownHandlePosition()).toMatchObject({ x: 20, y: 10 })
|
|
540
|
+
})
|
|
541
|
+
})
|
|
498
542
|
})
|
|
499
543
|
})
|
package/tldraw.css
CHANGED
|
@@ -1325,6 +1325,10 @@ input,
|
|
|
1325
1325
|
flex: 1;
|
|
1326
1326
|
}
|
|
1327
1327
|
|
|
1328
|
+
.tl-bookmark__copy_container:has(.tl-bookmark__link:only-child) {
|
|
1329
|
+
justify-content: center;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1328
1332
|
.tl-bookmark__heading,
|
|
1329
1333
|
.tl-bookmark__description,
|
|
1330
1334
|
.tl-bookmark__link {
|
|
@@ -1357,7 +1361,7 @@ input,
|
|
|
1357
1361
|
line-clamp: 3;
|
|
1358
1362
|
text-overflow: ellipsis;
|
|
1359
1363
|
display: -webkit-box;
|
|
1360
|
-
color: var(--tl-color-text-
|
|
1364
|
+
color: var(--tl-color-text-1);
|
|
1361
1365
|
margin: var(--tl-space-2) 0px;
|
|
1362
1366
|
}
|
|
1363
1367
|
|
|
@@ -1369,11 +1373,12 @@ input,
|
|
|
1369
1373
|
font-size: 12px;
|
|
1370
1374
|
pointer-events: all;
|
|
1371
1375
|
display: flex;
|
|
1372
|
-
color: var(--tl-color-text-
|
|
1376
|
+
color: var(--tl-color-text-1);
|
|
1373
1377
|
align-items: center;
|
|
1374
1378
|
cursor: var(--tl-cursor-pointer);
|
|
1375
1379
|
width: fit-content;
|
|
1376
1380
|
max-width: 100%;
|
|
1381
|
+
text-decoration: none;
|
|
1377
1382
|
}
|
|
1378
1383
|
|
|
1379
1384
|
.tl-bookmark__link > span {
|