tldraw 4.1.0-canary.27316c511408 → 4.1.0-canary.291aa5ae529a
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 +35 -10
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- 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 +25 -30
- package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
- package/dist-cjs/lib/defaultExternalContentHandlers.js +9 -32
- 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 +44 -102
- package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/bookmark/bookmarks.js +138 -0
- package/dist-cjs/lib/shapes/bookmark/bookmarks.js.map +7 -0
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +25 -3
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.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 +24 -30
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- 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 +35 -10
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/defaultEmbedDefinitions.mjs +25 -30
- package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
- package/dist-esm/lib/defaultExternalContentHandlers.mjs +9 -32
- 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 +46 -101
- package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/bookmark/bookmarks.mjs +124 -0
- package/dist-esm/lib/shapes/bookmark/bookmarks.mjs.map +7 -0
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +26 -3
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.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/context/actions.mjs +23 -29
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- 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/index.ts +1 -0
- package/src/lib/defaultEmbedDefinitions.ts +20 -24
- package/src/lib/defaultExternalContentHandlers.ts +11 -36
- package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +3 -0
- package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +51 -135
- package/src/lib/shapes/bookmark/bookmarks.ts +170 -0
- package/src/lib/shapes/embed/EmbedShapeUtil.tsx +28 -2
- 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/context/actions.tsx +27 -31
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/utils/embeds/embeds.test.ts +16 -34
- package/src/test/bookmark-shapes.test.ts +129 -7
- package/src/test/customSnapping.test.tsx +55 -11
- package/tldraw.css +7 -2
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
BaseBoxShapeUtil,
|
|
5
5
|
HTMLContainer,
|
|
6
|
+
Rectangle2d,
|
|
6
7
|
TLEmbedShape,
|
|
7
8
|
TLEmbedShapeProps,
|
|
8
9
|
TLResizeInfo,
|
|
@@ -24,6 +25,8 @@ import {
|
|
|
24
25
|
embedShapePermissionDefaults,
|
|
25
26
|
} from '../../defaultEmbedDefinitions'
|
|
26
27
|
import { TLEmbedResult, getEmbedInfo } from '../../utils/embeds/embeds'
|
|
28
|
+
import { BookmarkIndicatorComponent, BookmarkShapeComponent } from '../bookmark/BookmarkShapeUtil'
|
|
29
|
+
import { BOOKMARK_JUST_URL_HEIGHT, BOOKMARK_WIDTH } from '../bookmark/bookmarks'
|
|
27
30
|
import { getRotatedBoxShadow } from '../shared/rotated-box-shadow'
|
|
28
31
|
|
|
29
32
|
const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => {
|
|
@@ -82,6 +85,18 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
override getGeometry(shape: TLEmbedShape) {
|
|
89
|
+
const embedInfo = this.getEmbedDefinition(shape.props.url)
|
|
90
|
+
if (!embedInfo?.definition) {
|
|
91
|
+
return new Rectangle2d({
|
|
92
|
+
width: BOOKMARK_WIDTH,
|
|
93
|
+
height: BOOKMARK_JUST_URL_HEIGHT,
|
|
94
|
+
isFilled: true,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
return super.getGeometry(shape)
|
|
98
|
+
}
|
|
99
|
+
|
|
85
100
|
override isAspectRatioLocked(shape: TLEmbedShape) {
|
|
86
101
|
const embedInfo = this.getEmbedDefinition(shape.props.url)
|
|
87
102
|
return embedInfo?.definition.isAspectRatioLocked ?? false
|
|
@@ -206,20 +221,31 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
|
|
|
206
221
|
background: embedInfo?.definition.backgroundColor,
|
|
207
222
|
}}
|
|
208
223
|
/>
|
|
209
|
-
) :
|
|
224
|
+
) : (
|
|
225
|
+
<BookmarkShapeComponent
|
|
226
|
+
url={url}
|
|
227
|
+
h={h}
|
|
228
|
+
rotation={pageRotation}
|
|
229
|
+
assetId={null}
|
|
230
|
+
showImageContainer={false}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
210
233
|
</HTMLContainer>
|
|
211
234
|
)
|
|
212
235
|
}
|
|
213
236
|
|
|
214
237
|
override indicator(shape: TLEmbedShape) {
|
|
215
238
|
const embedInfo = this.getEmbedDefinition(shape.props.url)
|
|
216
|
-
|
|
239
|
+
|
|
240
|
+
return embedInfo?.definition ? (
|
|
217
241
|
<rect
|
|
218
242
|
width={toDomPrecision(shape.props.w)}
|
|
219
243
|
height={toDomPrecision(shape.props.h)}
|
|
220
244
|
rx={embedInfo?.definition.overrideOutlineRadius ?? 8}
|
|
221
245
|
ry={embedInfo?.definition.overrideOutlineRadius ?? 8}
|
|
222
246
|
/>
|
|
247
|
+
) : (
|
|
248
|
+
<BookmarkIndicatorComponent w={BOOKMARK_WIDTH} h={BOOKMARK_JUST_URL_HEIGHT} />
|
|
223
249
|
)
|
|
224
250
|
}
|
|
225
251
|
override getInterpolatedProps(
|
|
@@ -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>
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Editor,
|
|
6
6
|
HALF_PI,
|
|
7
7
|
PageRecordType,
|
|
8
|
+
Result,
|
|
8
9
|
TLBookmarkShape,
|
|
9
10
|
TLEmbedShape,
|
|
10
11
|
TLFrameShape,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
useMaybeEditor,
|
|
25
26
|
} from '@tldraw/editor'
|
|
26
27
|
import * as React from 'react'
|
|
28
|
+
import { createBookmarkFromUrl } from '../../shapes/bookmark/bookmarks'
|
|
27
29
|
import { fitFrameToContent, removeFrame } from '../../utils/frames/frames'
|
|
28
30
|
import { generateShapeAnnouncementMessage } from '../components/A11y'
|
|
29
31
|
import { EditLinkDialog } from '../components/EditLinkDialog'
|
|
@@ -387,45 +389,39 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|
|
387
389
|
{
|
|
388
390
|
id: 'convert-to-bookmark',
|
|
389
391
|
label: 'action.convert-to-bookmark',
|
|
390
|
-
onSelect(source) {
|
|
392
|
+
async onSelect(source) {
|
|
391
393
|
if (!canApplySelectionAction()) return
|
|
392
394
|
if (mustGoBackToSelectToolFirst()) return
|
|
393
395
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const shapes = editor.getSelectedShapes()
|
|
396
|
+
trackEvent('convert-to-bookmark', { source })
|
|
397
|
+
const shapes = editor.getSelectedShapes()
|
|
397
398
|
|
|
398
|
-
|
|
399
|
-
const deleteList: TLShapeId[] = []
|
|
400
|
-
for (const shape of shapes) {
|
|
401
|
-
if (!shape || !editor.isShapeOfType<TLEmbedShape>(shape, 'embed') || !shape.props.url)
|
|
402
|
-
continue
|
|
399
|
+
const markId = editor.markHistoryStoppingPoint('convert shapes to bookmark')
|
|
403
400
|
|
|
404
|
-
|
|
405
|
-
newPos.rot(-shape.rotation)
|
|
406
|
-
newPos.add(new Vec(shape.props.w / 2 - 300 / 2, shape.props.h / 2 - 320 / 2)) // see bookmark shape util
|
|
407
|
-
newPos.rot(shape.rotation)
|
|
408
|
-
const partial: TLShapePartial<TLBookmarkShape> = {
|
|
409
|
-
id: createShapeId(),
|
|
410
|
-
type: 'bookmark',
|
|
411
|
-
rotation: shape.rotation,
|
|
412
|
-
x: newPos.x,
|
|
413
|
-
y: newPos.y,
|
|
414
|
-
opacity: 1,
|
|
415
|
-
props: {
|
|
416
|
-
url: shape.props.url,
|
|
417
|
-
},
|
|
418
|
-
}
|
|
401
|
+
const creationPromises: Promise<Result<any, any>>[] = []
|
|
419
402
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
403
|
+
for (const shape of shapes) {
|
|
404
|
+
if (!shape || !editor.isShapeOfType<TLEmbedShape>(shape, 'embed') || !shape.props.url)
|
|
405
|
+
continue
|
|
423
406
|
|
|
424
|
-
editor.
|
|
407
|
+
const center = editor.getShapePageBounds(shape)?.center
|
|
425
408
|
|
|
426
|
-
|
|
427
|
-
editor.deleteShapes(
|
|
428
|
-
|
|
409
|
+
if (!center) continue
|
|
410
|
+
editor.deleteShapes([shape.id])
|
|
411
|
+
|
|
412
|
+
creationPromises.push(
|
|
413
|
+
createBookmarkFromUrl(editor, { url: shape.props.url, center }).then((res) => {
|
|
414
|
+
if (!res.ok) {
|
|
415
|
+
throw new Error(res.error)
|
|
416
|
+
}
|
|
417
|
+
return res
|
|
418
|
+
})
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
await Promise.all(creationPromises).catch((error) => {
|
|
423
|
+
editor.bailToMark(markId)
|
|
424
|
+
console.error(error)
|
|
429
425
|
})
|
|
430
426
|
},
|
|
431
427
|
},
|
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.291aa5ae529a'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-10-14T14:47:38.579Z',
|
|
8
|
+
patch: '2025-10-14T14:47:38.579Z',
|
|
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,
|
|
@@ -347,23 +355,6 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
|
|
|
347
355
|
url: 'https://vimeo.com/foobar',
|
|
348
356
|
match: false,
|
|
349
357
|
},
|
|
350
|
-
// excalidraw
|
|
351
|
-
{
|
|
352
|
-
url: 'https://excalidraw.com/#room=asdkjashdkjhaskdjh,sadkjhakjshdkjahd',
|
|
353
|
-
match: true,
|
|
354
|
-
output: {
|
|
355
|
-
type: 'excalidraw',
|
|
356
|
-
embedUrl: `https://excalidraw.com/#room=asdkjashdkjhaskdjh,sadkjhakjshdkjahd`,
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
url: 'https://excalidraw.com',
|
|
361
|
-
match: false,
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
url: 'https://excalidraw.com/help',
|
|
365
|
-
match: false,
|
|
366
|
-
},
|
|
367
358
|
//desmos
|
|
368
359
|
{
|
|
369
360
|
url: 'https://www.desmos.com/calculator/js9hryvejc',
|
|
@@ -599,6 +590,14 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
|
|
|
599
590
|
url: `https://replit.com/@omar/Blob-Generator`,
|
|
600
591
|
},
|
|
601
592
|
},
|
|
593
|
+
{
|
|
594
|
+
embedUrl: 'https://replit.com/@omar/Blob-Generator?embed=true#index.html',
|
|
595
|
+
match: true,
|
|
596
|
+
output: {
|
|
597
|
+
type: 'replit',
|
|
598
|
+
url: `https://replit.com/@omar/Blob-Generator#index.html`,
|
|
599
|
+
},
|
|
600
|
+
},
|
|
602
601
|
{
|
|
603
602
|
embedUrl: 'https://replit.com/@omar/Blob-Generator',
|
|
604
603
|
match: false,
|
|
@@ -671,23 +670,6 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
|
|
|
671
670
|
embedUrl: 'https://vimeo.com/foobar',
|
|
672
671
|
match: false,
|
|
673
672
|
},
|
|
674
|
-
// excalidraw
|
|
675
|
-
{
|
|
676
|
-
embedUrl: 'https://excalidraw.com/#room=asdkjashdkjhaskdjh,sadkjhakjshdkjahd',
|
|
677
|
-
match: true,
|
|
678
|
-
output: {
|
|
679
|
-
type: 'excalidraw',
|
|
680
|
-
url: `https://excalidraw.com/#room=asdkjashdkjhaskdjh,sadkjhakjshdkjahd`,
|
|
681
|
-
},
|
|
682
|
-
},
|
|
683
|
-
{
|
|
684
|
-
embedUrl: 'https://excalidraw.com',
|
|
685
|
-
match: false,
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
embedUrl: 'https://excalidraw.com/help',
|
|
689
|
-
match: false,
|
|
690
|
-
},
|
|
691
673
|
// desmos
|
|
692
674
|
{
|
|
693
675
|
embedUrl: 'https://www.desmos.com/calculator/js9hryvejc?embed',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TLBookmarkShape, createShapeId } from '@tldraw/editor'
|
|
2
|
-
import {
|
|
2
|
+
import { vi } from 'vitest'
|
|
3
|
+
import { createBookmarkFromUrl, getHumanReadableAddress } from '../lib/shapes/bookmark/bookmarks'
|
|
3
4
|
import { TestEditor } from './TestEditor'
|
|
4
5
|
|
|
5
6
|
let editor: TestEditor
|
|
@@ -74,12 +75,12 @@ describe('The URL formatter', () => {
|
|
|
74
75
|
const e = editor.getShape<TLBookmarkShape>(ids.e)!
|
|
75
76
|
const f = editor.getShape<TLBookmarkShape>(ids.f)!
|
|
76
77
|
|
|
77
|
-
expect(getHumanReadableAddress(a)).toBe('github.com')
|
|
78
|
-
expect(getHumanReadableAddress(b)).toBe('github.com')
|
|
79
|
-
expect(getHumanReadableAddress(c)).toBe('github.com')
|
|
80
|
-
expect(getHumanReadableAddress(d)).toBe('github.com')
|
|
81
|
-
expect(getHumanReadableAddress(e)).toBe('github.com')
|
|
82
|
-
expect(getHumanReadableAddress(f)).toBe('github.com')
|
|
78
|
+
expect(getHumanReadableAddress(a.props.url)).toBe('github.com')
|
|
79
|
+
expect(getHumanReadableAddress(b.props.url)).toBe('github.com')
|
|
80
|
+
expect(getHumanReadableAddress(c.props.url)).toBe('github.com')
|
|
81
|
+
expect(getHumanReadableAddress(d.props.url)).toBe('github.com')
|
|
82
|
+
expect(getHumanReadableAddress(e.props.url)).toBe('github.com')
|
|
83
|
+
expect(getHumanReadableAddress(f.props.url)).toBe('github.com')
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
it("Doesn't resize bookmarks", () => {
|
|
@@ -132,3 +133,124 @@ describe('The URL formatter', () => {
|
|
|
132
133
|
expect(newBookmark.props.h).toBe(320)
|
|
133
134
|
})
|
|
134
135
|
})
|
|
136
|
+
|
|
137
|
+
describe('createBookmarkFromUrl', () => {
|
|
138
|
+
it('creates a bookmark shape with unfurled metadata', async () => {
|
|
139
|
+
const url = 'https://example.com'
|
|
140
|
+
const center = { x: 100, y: 200 }
|
|
141
|
+
|
|
142
|
+
// Mock the asset creation to return a test asset
|
|
143
|
+
const mockAsset = {
|
|
144
|
+
id: 'asset:test-asset-id' as any,
|
|
145
|
+
typeName: 'asset' as const,
|
|
146
|
+
type: 'bookmark' as const,
|
|
147
|
+
props: {
|
|
148
|
+
src: url,
|
|
149
|
+
title: 'Example Site',
|
|
150
|
+
description: 'An example website',
|
|
151
|
+
image: 'https://example.com/image.jpg',
|
|
152
|
+
favicon: 'https://example.com/favicon.ico',
|
|
153
|
+
},
|
|
154
|
+
meta: {},
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Mock the getAssetForExternalContent method
|
|
158
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
|
|
159
|
+
|
|
160
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
161
|
+
|
|
162
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
163
|
+
const shape = result.value
|
|
164
|
+
expect(shape.type).toBe('bookmark')
|
|
165
|
+
expect(shape.props.url).toBe(url)
|
|
166
|
+
expect(shape.props.assetId).toBe('asset:test-asset-id')
|
|
167
|
+
expect(shape.props.w).toBe(300)
|
|
168
|
+
expect(shape.props.h).toBe(320)
|
|
169
|
+
expect(shape.x).toBe(center.x - 150) // BOOKMARK_WIDTH / 2
|
|
170
|
+
expect(shape.y).toBe(center.y - 160) // BOOKMARK_HEIGHT / 2
|
|
171
|
+
|
|
172
|
+
// Verify the shape was created in the editor
|
|
173
|
+
const createdShape = editor.getShape(result.value.id)
|
|
174
|
+
expect(createdShape).toBeDefined()
|
|
175
|
+
expect(createdShape?.type).toBe('bookmark')
|
|
176
|
+
|
|
177
|
+
// Verify the asset was created
|
|
178
|
+
const createdAsset = editor.getAsset('asset:test-asset-id' as any)
|
|
179
|
+
expect(createdAsset).toBeDefined()
|
|
180
|
+
expect(createdAsset?.type).toBe('bookmark')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('creates a bookmark shape with default center when no center provided', async () => {
|
|
184
|
+
const url = 'https://example.com'
|
|
185
|
+
const viewportCenter = { x: 500, y: 300 }
|
|
186
|
+
|
|
187
|
+
// Mock getViewportPageBounds to return a known center
|
|
188
|
+
vi.spyOn(editor, 'getViewportPageBounds').mockReturnValue({
|
|
189
|
+
x: 0,
|
|
190
|
+
y: 0,
|
|
191
|
+
w: 1000,
|
|
192
|
+
h: 600,
|
|
193
|
+
center: viewportCenter,
|
|
194
|
+
} as any)
|
|
195
|
+
|
|
196
|
+
const mockAsset = {
|
|
197
|
+
id: 'asset:test-asset-id' as any,
|
|
198
|
+
typeName: 'asset' as const,
|
|
199
|
+
type: 'bookmark' as const,
|
|
200
|
+
props: {
|
|
201
|
+
src: url,
|
|
202
|
+
title: 'Example Site',
|
|
203
|
+
description: 'An example website',
|
|
204
|
+
image: '',
|
|
205
|
+
favicon: '',
|
|
206
|
+
},
|
|
207
|
+
meta: {},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
|
|
211
|
+
|
|
212
|
+
const result = await createBookmarkFromUrl(editor, { url })
|
|
213
|
+
|
|
214
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
215
|
+
const shape = result.value
|
|
216
|
+
expect(shape.x).toBe(viewportCenter.x - 150)
|
|
217
|
+
expect(shape.y).toBe(viewportCenter.y - 160)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('handles asset creation failure gracefully', async () => {
|
|
221
|
+
const url = 'https://invalid-url.com'
|
|
222
|
+
const center = { x: 100, y: 200 }
|
|
223
|
+
|
|
224
|
+
// Mock the asset creation to fail
|
|
225
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockRejectedValue(new Error('Failed to fetch'))
|
|
226
|
+
|
|
227
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
228
|
+
|
|
229
|
+
assert(!result.ok, 'Failed to create bookmark')
|
|
230
|
+
expect(result.error).toBe('Failed to fetch')
|
|
231
|
+
|
|
232
|
+
// Verify no shape was created
|
|
233
|
+
const shapes = editor.getCurrentPageShapes()
|
|
234
|
+
expect(shapes).toHaveLength(0)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('creates bookmark shape even when asset creation returns null', async () => {
|
|
238
|
+
const url = 'https://example.com'
|
|
239
|
+
const center = { x: 100, y: 200 }
|
|
240
|
+
|
|
241
|
+
// Mock the asset creation to return null
|
|
242
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(null as any)
|
|
243
|
+
|
|
244
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
245
|
+
|
|
246
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
247
|
+
const shape = result.value
|
|
248
|
+
expect(shape.type).toBe('bookmark')
|
|
249
|
+
expect(shape.props.url).toBe(url)
|
|
250
|
+
expect(shape.props.assetId).toBe(null)
|
|
251
|
+
|
|
252
|
+
// Verify the shape was created
|
|
253
|
+
const createdShape = editor.getShape(result.value.id)
|
|
254
|
+
expect(createdShape).toBeDefined()
|
|
255
|
+
})
|
|
256
|
+
})
|
|
@@ -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
|
})
|