tldraw 3.16.0-canary.fa3749606e52 → 3.16.0-next.34fddf633325
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 +32 -34
- package/dist-cjs/index.js +7 -2
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
- package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
- package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
- package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
- package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
- package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
- package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +168 -137
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +3 -3
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +3 -2
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
- package/dist-cjs/lib/ui/components/menu-items.js +6 -0
- package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +11 -3
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +267 -0
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -149
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
- package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
- package/dist-cjs/lib/ui/context/actions.js +15 -0
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +1 -1
- package/dist-cjs/lib/ui/hooks/useTools.js +9 -76
- package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-esm/index.d.mts +32 -34
- package/dist-esm/index.mjs +11 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
- package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
- package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
- package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
- package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
- package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
- package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +168 -137
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +3 -3
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +3 -2
- package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
- package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
- package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +11 -3
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +237 -0
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -157
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
- package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
- package/dist-esm/lib/ui/context/actions.mjs +15 -0
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +1 -1
- package/dist-esm/lib/ui/hooks/useTools.mjs +10 -83
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.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 +3 -3
- package/src/index.ts +8 -2
- package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
- package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
- package/src/lib/tools/SelectTool/childStates/Translating.ts +1 -0
- package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
- package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
- package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
- package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +171 -128
- package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +3 -3
- package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
- package/src/lib/ui/components/menu-items.tsx +8 -0
- package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +19 -3
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +313 -0
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -213
- package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
- package/src/lib/ui/context/actions.tsx +15 -0
- package/src/lib/ui/context/events.tsx +1 -1
- package/src/lib/ui/hooks/useTools.tsx +10 -118
- package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
- package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +57 -1
- package/src/test/arrows-megabus.test.tsx +12 -6
- package/src/test/inner-outer-margin.test.ts +315 -0
- package/tldraw.css +59 -1
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertExists,
|
|
3
|
-
createShapeId,
|
|
4
|
-
Editor,
|
|
5
|
-
GeoShapeGeoStyle,
|
|
6
|
-
TLPointerEventInfo,
|
|
7
|
-
TLShapeId,
|
|
8
|
-
toRichText,
|
|
9
|
-
useMaybeEditor,
|
|
10
|
-
} from '@tldraw/editor'
|
|
1
|
+
import { Editor, GeoShapeGeoStyle, useMaybeEditor } from '@tldraw/editor'
|
|
11
2
|
import * as React from 'react'
|
|
12
3
|
import { EmbedDialog } from '../components/EmbedDialog'
|
|
13
4
|
import { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'
|
|
@@ -28,7 +19,6 @@ export interface TLUiToolItem<
|
|
|
28
19
|
shortcutsLabel?: TranslationKey
|
|
29
20
|
icon: IconType | TLUiIconJsx
|
|
30
21
|
onSelect(source: TLUiEventSource): void
|
|
31
|
-
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
|
|
32
22
|
/**
|
|
33
23
|
* The keyboard shortcut for this tool. This is a string that can be a single key,
|
|
34
24
|
* or a combination of keys.
|
|
@@ -136,27 +126,21 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
136
126
|
onToolSelect(source, this)
|
|
137
127
|
},
|
|
138
128
|
},
|
|
139
|
-
...[...GeoShapeGeoStyle.values].map((
|
|
140
|
-
id
|
|
141
|
-
label: `tool.${
|
|
129
|
+
...[...GeoShapeGeoStyle.values].map((id) => ({
|
|
130
|
+
id,
|
|
131
|
+
label: `tool.${id}` as TLUiTranslationKey,
|
|
142
132
|
meta: {
|
|
143
|
-
geo,
|
|
133
|
+
geo: id,
|
|
144
134
|
},
|
|
145
|
-
kbd:
|
|
146
|
-
icon: ('geo-' +
|
|
135
|
+
kbd: id === 'rectangle' ? 'r' : id === 'ellipse' ? 'o' : undefined,
|
|
136
|
+
icon: ('geo-' + id) as TLUiIconType,
|
|
147
137
|
onSelect(source: TLUiEventSource) {
|
|
148
138
|
editor.run(() => {
|
|
149
|
-
editor.setStyleForNextShapes(GeoShapeGeoStyle,
|
|
139
|
+
editor.setStyleForNextShapes(GeoShapeGeoStyle, id)
|
|
150
140
|
editor.setCurrentTool('geo')
|
|
151
|
-
onToolSelect(source, this, `geo-${
|
|
141
|
+
onToolSelect(source, this, `geo-${id}`)
|
|
152
142
|
})
|
|
153
143
|
},
|
|
154
|
-
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
155
|
-
onDragFromToolbarToCreateShape(editor, info, {
|
|
156
|
-
createShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),
|
|
157
|
-
})
|
|
158
|
-
trackEvent('drag-tool', { source, id: 'geo' })
|
|
159
|
-
},
|
|
160
144
|
})),
|
|
161
145
|
{
|
|
162
146
|
id: 'arrow',
|
|
@@ -167,17 +151,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
167
151
|
editor.setCurrentTool('arrow')
|
|
168
152
|
onToolSelect(source, this)
|
|
169
153
|
},
|
|
170
|
-
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
171
|
-
onDragFromToolbarToCreateShape(editor, info, {
|
|
172
|
-
createShape: (id) =>
|
|
173
|
-
editor.createShape({
|
|
174
|
-
id,
|
|
175
|
-
type: 'arrow',
|
|
176
|
-
props: { start: { x: 0, y: 0 }, end: { x: 200, y: 0 } },
|
|
177
|
-
}),
|
|
178
|
-
})
|
|
179
|
-
trackEvent('drag-tool', { source, id: 'arrow' })
|
|
180
|
-
},
|
|
181
154
|
},
|
|
182
155
|
{
|
|
183
156
|
id: 'line',
|
|
@@ -198,12 +171,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
198
171
|
editor.setCurrentTool('frame')
|
|
199
172
|
onToolSelect(source, this)
|
|
200
173
|
},
|
|
201
|
-
onDragStart(source, info) {
|
|
202
|
-
onDragFromToolbarToCreateShape(editor, info, {
|
|
203
|
-
createShape: (id) => editor.createShape({ id, type: 'frame' }),
|
|
204
|
-
})
|
|
205
|
-
trackEvent('drag-tool', { source, id: 'frame' })
|
|
206
|
-
},
|
|
207
174
|
},
|
|
208
175
|
{
|
|
209
176
|
id: 'text',
|
|
@@ -214,17 +181,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
214
181
|
editor.setCurrentTool('text')
|
|
215
182
|
onToolSelect(source, this)
|
|
216
183
|
},
|
|
217
|
-
onDragStart(source, info) {
|
|
218
|
-
onDragFromToolbarToCreateShape(editor, info, {
|
|
219
|
-
createShape: (id) =>
|
|
220
|
-
editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
|
|
221
|
-
onDragEnd: (id) => {
|
|
222
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
223
|
-
editor.setEditingShape(id)
|
|
224
|
-
},
|
|
225
|
-
})
|
|
226
|
-
trackEvent('drag-tool', { source, id: 'text' })
|
|
227
|
-
},
|
|
228
184
|
},
|
|
229
185
|
{
|
|
230
186
|
id: 'asset',
|
|
@@ -245,16 +201,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
245
201
|
editor.setCurrentTool('note')
|
|
246
202
|
onToolSelect(source, this)
|
|
247
203
|
},
|
|
248
|
-
onDragStart(source, info) {
|
|
249
|
-
onDragFromToolbarToCreateShape(editor, info, {
|
|
250
|
-
createShape: (id) => editor.createShape({ id, type: 'note' }),
|
|
251
|
-
onDragEnd: (id) => {
|
|
252
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
253
|
-
editor.setEditingShape(id)
|
|
254
|
-
},
|
|
255
|
-
})
|
|
256
|
-
trackEvent('drag-tool', { source, id: 'note' })
|
|
257
|
-
},
|
|
258
204
|
},
|
|
259
205
|
{
|
|
260
206
|
id: 'laser',
|
|
@@ -298,7 +244,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
298
244
|
}
|
|
299
245
|
|
|
300
246
|
return tools
|
|
301
|
-
}, [overrides, editor, helpers, onToolSelect
|
|
247
|
+
}, [overrides, editor, helpers, onToolSelect])
|
|
302
248
|
|
|
303
249
|
return <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>
|
|
304
250
|
}
|
|
@@ -313,57 +259,3 @@ export function useTools() {
|
|
|
313
259
|
|
|
314
260
|
return ctx
|
|
315
261
|
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Options for {@link onDragFromToolbarToCreateShape}.
|
|
319
|
-
* @public
|
|
320
|
-
*/
|
|
321
|
-
export interface OnDragFromToolbarToCreateShapesOpts {
|
|
322
|
-
/**
|
|
323
|
-
* Create the shape being dragged. You don't need to worry about positioning it, as it'll be
|
|
324
|
-
* immediately updated with the correct position.
|
|
325
|
-
*/
|
|
326
|
-
createShape(id: TLShapeId): void
|
|
327
|
-
/**
|
|
328
|
-
* Called once the drag interaction has finished.
|
|
329
|
-
*/
|
|
330
|
-
onDragEnd?(id: TLShapeId): void
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* A helper method to use in {@link TLUiToolItem#onDragStart} to create a shape by dragging it from
|
|
335
|
-
* the toolbar.
|
|
336
|
-
* @public
|
|
337
|
-
*/
|
|
338
|
-
export function onDragFromToolbarToCreateShape(
|
|
339
|
-
editor: Editor,
|
|
340
|
-
info: TLPointerEventInfo,
|
|
341
|
-
opts: OnDragFromToolbarToCreateShapesOpts
|
|
342
|
-
) {
|
|
343
|
-
const { x, y } = editor.inputs.currentPagePoint
|
|
344
|
-
|
|
345
|
-
const stoppingPoint = editor.markHistoryStoppingPoint('drag shape tool')
|
|
346
|
-
editor.setCurrentTool('select.translating')
|
|
347
|
-
|
|
348
|
-
const id = createShapeId()
|
|
349
|
-
opts.createShape(id)
|
|
350
|
-
const shape = assertExists(editor.getShape(id), 'Shape not found')
|
|
351
|
-
|
|
352
|
-
const { w, h } = editor.getShapePageBounds(id)!
|
|
353
|
-
editor.updateShape({ id, type: shape.type, x: x - w / 2, y: y - h / 2 })
|
|
354
|
-
editor.select(id)
|
|
355
|
-
|
|
356
|
-
editor.setCurrentTool('select.translating', {
|
|
357
|
-
...info,
|
|
358
|
-
target: 'shape',
|
|
359
|
-
shape: editor.getShape(id),
|
|
360
|
-
isCreating: true,
|
|
361
|
-
creatingMarkId: stoppingPoint,
|
|
362
|
-
onCreate() {
|
|
363
|
-
editor.setCurrentTool('select.idle')
|
|
364
|
-
editor.select(id)
|
|
365
|
-
opts.onDragEnd?.(id)
|
|
366
|
-
},
|
|
367
|
-
})
|
|
368
|
-
editor.getCurrentTool().setCurrentToolIdMask(shape.type)
|
|
369
|
-
}
|
|
@@ -93,6 +93,8 @@ export type TLUiTranslationKey =
|
|
|
93
93
|
| 'action.toggle-reduce-motion'
|
|
94
94
|
| 'action.toggle-keyboard-shortcuts.menu'
|
|
95
95
|
| 'action.toggle-keyboard-shortcuts'
|
|
96
|
+
| 'action.toggle-ui-labels.menu'
|
|
97
|
+
| 'action.toggle-ui-labels'
|
|
96
98
|
| 'action.toggle-edge-scrolling.menu'
|
|
97
99
|
| 'action.toggle-edge-scrolling'
|
|
98
100
|
| 'action.toggle-debug-mode.menu'
|
|
@@ -298,6 +300,7 @@ export type TLUiTranslationKey =
|
|
|
298
300
|
| 'a11y.open-keyboard-shortcuts'
|
|
299
301
|
| 'menu.title'
|
|
300
302
|
| 'menu.theme'
|
|
303
|
+
| 'menu.accessibility'
|
|
301
304
|
| 'menu.copy-as'
|
|
302
305
|
| 'menu.edit'
|
|
303
306
|
| 'menu.export-as'
|
|
@@ -94,6 +94,8 @@ export const DEFAULT_TRANSLATION = {
|
|
|
94
94
|
'action.toggle-reduce-motion': 'Toggle reduce motion',
|
|
95
95
|
'action.toggle-keyboard-shortcuts.menu': 'Keyboard shortcuts',
|
|
96
96
|
'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
|
|
97
|
+
'action.toggle-ui-labels.menu': 'UI labels',
|
|
98
|
+
'action.toggle-ui-labels': 'Toggle UI labels',
|
|
97
99
|
'action.toggle-edge-scrolling.menu': 'Edge scrolling',
|
|
98
100
|
'action.toggle-edge-scrolling': 'Toggle edge scrolling',
|
|
99
101
|
'action.toggle-debug-mode.menu': 'Debug mode',
|
|
@@ -299,6 +301,7 @@ export const DEFAULT_TRANSLATION = {
|
|
|
299
301
|
'a11y.open-keyboard-shortcuts': 'Open keyboard shortcuts',
|
|
300
302
|
'menu.title': 'Menu',
|
|
301
303
|
'menu.theme': 'Theme',
|
|
304
|
+
'menu.accessibility': 'Accessibility',
|
|
302
305
|
'menu.copy-as': 'Copy as',
|
|
303
306
|
'menu.edit': 'Edit',
|
|
304
307
|
'menu.export-as': 'Export as',
|
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 = '3.16.0-
|
|
4
|
+
export const version = '3.16.0-next.34fddf633325'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-08-
|
|
8
|
-
patch: '2025-08-
|
|
7
|
+
minor: '2025-08-06T14:20:08.115Z',
|
|
8
|
+
patch: '2025-08-06T14:20:08.115Z',
|
|
9
9
|
}
|
package/src/lib/ui.css
CHANGED
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
opacity: 1;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
.tlui-button[aria-expanded='true'][data-direction='left']
|
|
100
|
+
.tlui-button[aria-expanded='true'][data-direction='left'] {
|
|
101
101
|
background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--color-muted-2) 100%);
|
|
102
102
|
opacity: 1;
|
|
103
103
|
}
|
|
@@ -1000,6 +1000,12 @@
|
|
|
1000
1000
|
max-width: 148px;
|
|
1001
1001
|
}
|
|
1002
1002
|
|
|
1003
|
+
.tlui-style-panel[data-show-ui-labels='true'] .tlui-button[data-isactive='true'] {
|
|
1004
|
+
border-radius: 10px;
|
|
1005
|
+
outline: 2px solid var(--color-text);
|
|
1006
|
+
outline-offset: -5px;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1003
1009
|
.tlui-style-panel::-webkit-scrollbar {
|
|
1004
1010
|
display: none;
|
|
1005
1011
|
}
|
|
@@ -1064,6 +1070,26 @@
|
|
|
1064
1070
|
}
|
|
1065
1071
|
}
|
|
1066
1072
|
|
|
1073
|
+
/* Accessibility subheadings */
|
|
1074
|
+
|
|
1075
|
+
.tlui-style-panel__section .tlui-style-panel__subheading,
|
|
1076
|
+
.tlui-style-panel__section__common .tlui-style-panel__subheading,
|
|
1077
|
+
.tlui-style-panel__subheading + .tlui-slider__container {
|
|
1078
|
+
margin: 0;
|
|
1079
|
+
padding: var(--space-2) var(--space-3) 0px var(--space-4);
|
|
1080
|
+
font-size: 12px;
|
|
1081
|
+
font-weight: inherit;
|
|
1082
|
+
line-height: inherit;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.tlui-style-panel .tlui-style-panel__subheading:nth-of-type(1) {
|
|
1086
|
+
padding-top: var(--space-3);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.tlui-style-panel__subheading + .tlui-slider__container {
|
|
1090
|
+
padding-top: 0px;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1067
1093
|
/* --------------------- Bottom --------------------- */
|
|
1068
1094
|
|
|
1069
1095
|
.tlui-layout__bottom {
|
|
@@ -1222,6 +1248,36 @@
|
|
|
1222
1248
|
transition: transform 0.15s ease-out 0.05s;
|
|
1223
1249
|
}
|
|
1224
1250
|
|
|
1251
|
+
/* ------------------- Tooltip -------------------- */
|
|
1252
|
+
|
|
1253
|
+
.tlui-tooltip {
|
|
1254
|
+
font-size: 12px;
|
|
1255
|
+
padding: 2px 8px;
|
|
1256
|
+
border-radius: 4px;
|
|
1257
|
+
background-color: var(--color-tooltip);
|
|
1258
|
+
box-shadow: none;
|
|
1259
|
+
color: var(--color-text-shadow);
|
|
1260
|
+
max-width: 400px;
|
|
1261
|
+
width: fit-content;
|
|
1262
|
+
text-align: center;
|
|
1263
|
+
pointer-events: none;
|
|
1264
|
+
will-change: transform, opacity;
|
|
1265
|
+
z-index: 2;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.tlui-tooltip__arrow {
|
|
1269
|
+
fill: var(--color-tooltip);
|
|
1270
|
+
will-change: opacity;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
[data-radix-popper-content-wrapper]:has(.tlui-tooltip) {
|
|
1274
|
+
z-index: var(--layer-toasts) !important;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
[data-radix-popper-content-wrapper]:has(.tlui-tooltip[data-should-animate='true']) {
|
|
1278
|
+
transition: all 0.1s ease-out;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1225
1281
|
/* ------------------- Debug panel ------------------ */
|
|
1226
1282
|
|
|
1227
1283
|
.tlui-debug-panel {
|
|
@@ -288,11 +288,13 @@ describe('When shapes are overlapping', () => {
|
|
|
288
288
|
editor.pointerDown(0, 50) // over nothing
|
|
289
289
|
editor.pointerMove(125, 50) // over box1 only
|
|
290
290
|
expect(bindings().end).toMatchObject({ toId: ids.box1 })
|
|
291
|
-
editor.pointerMove(175, 50) // box2 is higher but box1 is filled
|
|
291
|
+
editor.pointerMove(175, 50) // box2 is higher but box1 is filled, but we're on the edge ofd box 2
|
|
292
|
+
expect(bindings().end).toMatchObject({ toId: ids.box2 })
|
|
293
|
+
editor.pointerMove(175, 70) // box2 is higher but box1 is filled, and we're inside of box2
|
|
292
294
|
expect(bindings().end).toMatchObject({ toId: ids.box1 })
|
|
293
|
-
editor.pointerMove(225,
|
|
295
|
+
editor.pointerMove(225, 70) // box3 is higher
|
|
294
296
|
expect(bindings().end).toMatchObject({ toId: ids.box3 })
|
|
295
|
-
editor.pointerMove(275,
|
|
297
|
+
editor.pointerMove(275, 70) // box4 is higher but box 3 is filled
|
|
296
298
|
expect(bindings().end).toMatchObject({ toId: ids.box3 })
|
|
297
299
|
})
|
|
298
300
|
|
|
@@ -304,14 +306,18 @@ describe('When shapes are overlapping', () => {
|
|
|
304
306
|
])
|
|
305
307
|
editor.setCurrentTool('arrow')
|
|
306
308
|
editor.pointerDown(0, 50)
|
|
307
|
-
editor.pointerMove(175, 50) // box1 is smaller even though it's behind box2
|
|
309
|
+
editor.pointerMove(175, 50) // box1 is smaller even though it's behind box2, but we're on the edge of box 2
|
|
310
|
+
expect(bindings().end).toMatchObject({ toId: ids.box2 })
|
|
311
|
+
editor.pointerMove(175, 70) // box1 is smaller even though it's behind box2
|
|
308
312
|
expect(bindings().end).toMatchObject({ toId: ids.box1 })
|
|
309
|
-
editor.pointerMove(150, 90) // box3 is smaller and at the front
|
|
313
|
+
editor.pointerMove(150, 90) // box3 is smaller and at the front but we're on the edge of box 2
|
|
314
|
+
expect(bindings().end).toMatchObject({ toId: ids.box2 })
|
|
315
|
+
editor.pointerMove(160, 90) // box3 is smaller and at the front and we're in box1 and box 3 and box 2
|
|
310
316
|
expect(bindings().end).toMatchObject({ toId: ids.box3 })
|
|
311
317
|
editor.sendToBack([ids.box3])
|
|
312
318
|
editor.pointerMove(149, 90) // box3 is smaller, even when at the back
|
|
313
319
|
expect(bindings().end).toMatchObject({ toId: ids.box3 })
|
|
314
|
-
editor.pointerMove(175,
|
|
320
|
+
editor.pointerMove(175, 60) // inside of box1 and box 2, but box 1 is smaller
|
|
315
321
|
expect(bindings().end).toMatchObject({ toId: ids.box1 })
|
|
316
322
|
})
|
|
317
323
|
})
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { TLArrowShape, createShapeId } from '@tldraw/editor'
|
|
2
|
+
import { getArrowBindings } from '../lib/shapes/arrow/shared'
|
|
3
|
+
import { TestEditor } from './TestEditor'
|
|
4
|
+
|
|
5
|
+
let editor: TestEditor
|
|
6
|
+
|
|
7
|
+
const ids = {
|
|
8
|
+
solidShape: createShapeId('solidShape'),
|
|
9
|
+
hollowShape: createShapeId('hollowShape'),
|
|
10
|
+
arrow: createShapeId('arrow'),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const _arrow = () => editor.getOnlySelectedShape() as TLArrowShape
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
editor = new TestEditor()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('Inner/Outer Margin Shape Detection', () => {
|
|
20
|
+
describe('getShapeAtPoint with inner/outer margins', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Create a solid filled shape
|
|
23
|
+
editor.createShape({
|
|
24
|
+
id: ids.solidShape,
|
|
25
|
+
type: 'geo',
|
|
26
|
+
x: 100,
|
|
27
|
+
y: 100,
|
|
28
|
+
props: {
|
|
29
|
+
w: 100,
|
|
30
|
+
h: 100,
|
|
31
|
+
fill: 'solid',
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Create a hollow shape on top (same position, smaller size)
|
|
36
|
+
editor.createShape({
|
|
37
|
+
id: ids.hollowShape,
|
|
38
|
+
type: 'geo',
|
|
39
|
+
x: 125,
|
|
40
|
+
y: 125,
|
|
41
|
+
props: {
|
|
42
|
+
w: 50,
|
|
43
|
+
h: 50,
|
|
44
|
+
// No fill property - defaults to 'none' (hollow)
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should support inner/outer margin options', () => {
|
|
50
|
+
// Test that the new margin options are accepted
|
|
51
|
+
const point = { x: 150, y: 150 }
|
|
52
|
+
|
|
53
|
+
// Test with array margin [innerMargin, outerMargin]
|
|
54
|
+
const arrayMarginHit = editor.getShapeAtPoint(point, {
|
|
55
|
+
hitInside: true,
|
|
56
|
+
margin: [8, 4],
|
|
57
|
+
})
|
|
58
|
+
expect(arrayMarginHit).toBeDefined()
|
|
59
|
+
|
|
60
|
+
// Test with insideMargin option
|
|
61
|
+
const insideMarginHit = editor.getShapeAtPoint(point, {
|
|
62
|
+
hitInside: true,
|
|
63
|
+
})
|
|
64
|
+
expect(insideMarginHit).toBeDefined()
|
|
65
|
+
|
|
66
|
+
// Test with single number margin (should use same for both)
|
|
67
|
+
const singleMarginHit = editor.getShapeAtPoint(point, {
|
|
68
|
+
hitInside: true,
|
|
69
|
+
margin: 8,
|
|
70
|
+
})
|
|
71
|
+
expect(singleMarginHit).toBeDefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should respect hitInside option for hollow shapes', () => {
|
|
75
|
+
const point = { x: 150, y: 150 }
|
|
76
|
+
|
|
77
|
+
// Without hitInside, should not hit hollow shape
|
|
78
|
+
const noHitInsideHit = editor.getShapeAtPoint(point, {
|
|
79
|
+
margin: [8, 0],
|
|
80
|
+
})
|
|
81
|
+
expect(noHitInsideHit?.id).toBe(ids.solidShape)
|
|
82
|
+
|
|
83
|
+
// With hitInside, should be able to hit hollow shape
|
|
84
|
+
const withHitInsideHit = editor.getShapeAtPoint(point, {
|
|
85
|
+
hitInside: true,
|
|
86
|
+
margin: [8, 0],
|
|
87
|
+
})
|
|
88
|
+
expect(withHitInsideHit).toBeDefined()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should handle edge cases correctly', () => {
|
|
92
|
+
// Test point exactly on the edge of hollow shape
|
|
93
|
+
const edgePoint = { x: 125, y: 150 }
|
|
94
|
+
|
|
95
|
+
const edgeHit = editor.getShapeAtPoint(edgePoint, {
|
|
96
|
+
hitInside: true,
|
|
97
|
+
margin: [8, 8],
|
|
98
|
+
})
|
|
99
|
+
expect(edgeHit).toBeDefined()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('Arrow binding with inner/outer margins', () => {
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
// Create a solid filled shape
|
|
106
|
+
editor.createShape({
|
|
107
|
+
id: ids.solidShape,
|
|
108
|
+
type: 'geo',
|
|
109
|
+
x: 100,
|
|
110
|
+
y: 100,
|
|
111
|
+
props: {
|
|
112
|
+
w: 100,
|
|
113
|
+
h: 100,
|
|
114
|
+
fill: 'solid',
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Create a hollow shape on top (same position, smaller size)
|
|
119
|
+
editor.createShape({
|
|
120
|
+
id: ids.hollowShape,
|
|
121
|
+
type: 'geo',
|
|
122
|
+
x: 125,
|
|
123
|
+
y: 125,
|
|
124
|
+
props: {
|
|
125
|
+
w: 50,
|
|
126
|
+
h: 50,
|
|
127
|
+
// No fill property - defaults to 'none' (hollow)
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should create arrow bindings with inner/outer margin support', () => {
|
|
133
|
+
editor.setCurrentTool('arrow')
|
|
134
|
+
|
|
135
|
+
// Start arrow outside both shapes
|
|
136
|
+
editor.pointerDown(50, 150)
|
|
137
|
+
|
|
138
|
+
// Move to center of hollow shape (which overlaps solid shape)
|
|
139
|
+
editor.pointerMove(150, 150)
|
|
140
|
+
editor.pointerUp()
|
|
141
|
+
|
|
142
|
+
const createdArrow = editor
|
|
143
|
+
.getCurrentPageShapes()
|
|
144
|
+
.find((s) => s.type === 'arrow') as TLArrowShape
|
|
145
|
+
expect(createdArrow).toBeDefined()
|
|
146
|
+
|
|
147
|
+
const arrowBindings = getArrowBindings(editor, createdArrow)
|
|
148
|
+
expect(arrowBindings.end).toBeDefined()
|
|
149
|
+
// The binding should be to one of the shapes
|
|
150
|
+
expect([ids.solidShape, ids.hollowShape]).toContain(arrowBindings.end?.toId)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should bind to solid shape when no hollow shape is present', () => {
|
|
154
|
+
// Remove the hollow shape
|
|
155
|
+
editor.deleteShape(ids.hollowShape)
|
|
156
|
+
|
|
157
|
+
editor.setCurrentTool('arrow')
|
|
158
|
+
|
|
159
|
+
// Start arrow outside shape
|
|
160
|
+
editor.pointerDown(50, 150)
|
|
161
|
+
|
|
162
|
+
// Move to center of solid shape
|
|
163
|
+
editor.pointerMove(150, 150)
|
|
164
|
+
editor.pointerUp()
|
|
165
|
+
|
|
166
|
+
const createdArrow = editor
|
|
167
|
+
.getCurrentPageShapes()
|
|
168
|
+
.find((s) => s.type === 'arrow') as TLArrowShape
|
|
169
|
+
expect(createdArrow).toBeDefined()
|
|
170
|
+
|
|
171
|
+
const arrowBindings = getArrowBindings(editor, createdArrow)
|
|
172
|
+
expect(arrowBindings.end).toBeDefined()
|
|
173
|
+
expect(arrowBindings.end?.toId).toBe(ids.solidShape)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('Complex overlapping scenarios', () => {
|
|
178
|
+
it('should handle multiple overlapping shapes correctly', () => {
|
|
179
|
+
// Create multiple shapes with different fill states
|
|
180
|
+
editor.createShape({
|
|
181
|
+
id: ids.solidShape,
|
|
182
|
+
type: 'geo',
|
|
183
|
+
x: 100,
|
|
184
|
+
y: 100,
|
|
185
|
+
props: {
|
|
186
|
+
w: 100,
|
|
187
|
+
h: 100,
|
|
188
|
+
fill: 'solid',
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
editor.createShape({
|
|
193
|
+
id: ids.hollowShape,
|
|
194
|
+
type: 'geo',
|
|
195
|
+
x: 125,
|
|
196
|
+
y: 125,
|
|
197
|
+
props: {
|
|
198
|
+
w: 50,
|
|
199
|
+
h: 50,
|
|
200
|
+
// No fill property - defaults to 'none' (hollow)
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Create another hollow shape
|
|
205
|
+
const hollowShape2 = createShapeId('hollowShape2')
|
|
206
|
+
editor.createShape({
|
|
207
|
+
id: hollowShape2,
|
|
208
|
+
type: 'geo',
|
|
209
|
+
x: 140,
|
|
210
|
+
y: 140,
|
|
211
|
+
props: {
|
|
212
|
+
w: 30,
|
|
213
|
+
h: 30,
|
|
214
|
+
// No fill property - defaults to 'none' (hollow)
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// Test point in the innermost hollow shape
|
|
219
|
+
const point = { x: 155, y: 155 }
|
|
220
|
+
|
|
221
|
+
const hit = editor.getShapeAtPoint(point, {
|
|
222
|
+
hitInside: true,
|
|
223
|
+
margin: [8, 8],
|
|
224
|
+
})
|
|
225
|
+
expect(hit).toBeDefined()
|
|
226
|
+
// Should hit one of the shapes
|
|
227
|
+
expect([ids.solidShape, ids.hollowShape, hollowShape2]).toContain(hit?.id)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should handle shapes with different geometries', () => {
|
|
231
|
+
// Create a solid rectangle
|
|
232
|
+
editor.createShape({
|
|
233
|
+
id: ids.solidShape,
|
|
234
|
+
type: 'geo',
|
|
235
|
+
x: 100,
|
|
236
|
+
y: 100,
|
|
237
|
+
props: {
|
|
238
|
+
w: 100,
|
|
239
|
+
h: 100,
|
|
240
|
+
fill: 'solid',
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Create a hollow circle on top
|
|
245
|
+
editor.createShape({
|
|
246
|
+
id: ids.hollowShape,
|
|
247
|
+
type: 'geo',
|
|
248
|
+
x: 125,
|
|
249
|
+
y: 125,
|
|
250
|
+
props: {
|
|
251
|
+
w: 50,
|
|
252
|
+
h: 50,
|
|
253
|
+
geo: 'ellipse',
|
|
254
|
+
// No fill property - defaults to 'none' (hollow)
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Test point inside the circle
|
|
259
|
+
const point = { x: 150, y: 150 }
|
|
260
|
+
|
|
261
|
+
const hit = editor.getShapeAtPoint(point, {
|
|
262
|
+
hitInside: true,
|
|
263
|
+
margin: [8, 8],
|
|
264
|
+
})
|
|
265
|
+
expect(hit).toBeDefined()
|
|
266
|
+
// Should hit one of the shapes
|
|
267
|
+
expect([ids.solidShape, ids.hollowShape]).toContain(hit?.id)
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Regression tests for the original bug', () => {
|
|
272
|
+
it('should support binding to hollow shapes when overlapping solid shapes', () => {
|
|
273
|
+
// This test verifies that the infrastructure exists for the bug fix
|
|
274
|
+
// Create a solid shape
|
|
275
|
+
editor.createShape({
|
|
276
|
+
id: ids.solidShape,
|
|
277
|
+
type: 'geo',
|
|
278
|
+
x: 100,
|
|
279
|
+
y: 100,
|
|
280
|
+
props: {
|
|
281
|
+
w: 100,
|
|
282
|
+
h: 100,
|
|
283
|
+
fill: 'solid',
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// Create a hollow shape on top
|
|
288
|
+
editor.createShape({
|
|
289
|
+
id: ids.hollowShape,
|
|
290
|
+
type: 'geo',
|
|
291
|
+
x: 125,
|
|
292
|
+
y: 125,
|
|
293
|
+
props: {
|
|
294
|
+
w: 50,
|
|
295
|
+
h: 50,
|
|
296
|
+
// No fill property - defaults to 'none' (hollow)
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Test that we can detect both shapes
|
|
301
|
+
const point = { x: 150, y: 150 }
|
|
302
|
+
|
|
303
|
+
// Should be able to hit the solid shape without hitInside
|
|
304
|
+
const solidHit = editor.getShapeAtPoint(point)
|
|
305
|
+
expect(solidHit?.id).toBe(ids.solidShape)
|
|
306
|
+
|
|
307
|
+
// Should be able to hit the hollow shape with hitInside and inner margin
|
|
308
|
+
const hollowHit = editor.getShapeAtPoint(point, {
|
|
309
|
+
hitInside: true,
|
|
310
|
+
margin: [8, 0],
|
|
311
|
+
})
|
|
312
|
+
expect(hollowHit).toBeDefined()
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
})
|