tldraw 3.16.0-canary.dfdf6b7de8c2 → 3.16.0-canary.efdec30fc411

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.
Files changed (162) hide show
  1. package/dist-cjs/index.d.ts +57 -3
  2. package/dist-cjs/index.js +10 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
  5. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  6. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
  7. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
  8. package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
  9. package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
  10. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +2 -1
  11. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  12. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js +3 -2
  13. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js.map +2 -2
  14. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
  15. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  16. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js +1 -1
  17. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js.map +2 -2
  18. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +2 -1
  19. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  20. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js +3 -2
  21. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js.map +2 -2
  22. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +2 -2
  23. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js.map +2 -2
  24. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
  25. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  26. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +171 -140
  27. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  28. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js +3 -3
  29. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +2 -2
  30. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +26 -25
  31. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +3 -3
  32. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +6 -5
  33. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +2 -2
  34. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +9 -10
  35. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
  36. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +5 -4
  37. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  38. package/dist-cjs/lib/ui/components/menu-items.js +6 -0
  39. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  40. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +4 -15
  41. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +3 -3
  42. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  43. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  44. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +3 -2
  45. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +3 -3
  46. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +18 -7
  47. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  48. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +284 -0
  49. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
  50. package/dist-cjs/lib/ui/components/primitives/layout.js +51 -0
  51. package/dist-cjs/lib/ui/components/primitives/layout.js.map +7 -0
  52. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +10 -8
  53. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  54. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
  55. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
  56. package/dist-cjs/lib/ui/context/actions.js +15 -0
  57. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  58. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  59. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  60. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
  61. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  62. package/dist-cjs/lib/ui/version.js +3 -3
  63. package/dist-cjs/lib/ui/version.js.map +1 -1
  64. package/dist-esm/index.d.mts +57 -3
  65. package/dist-esm/index.mjs +17 -1
  66. package/dist-esm/index.mjs.map +2 -2
  67. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
  68. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  69. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
  70. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
  71. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
  72. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
  73. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +2 -1
  74. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  75. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs +3 -2
  76. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs.map +2 -2
  77. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
  78. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  79. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs +1 -1
  80. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs.map +2 -2
  81. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +2 -1
  82. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  83. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs +3 -2
  84. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs.map +2 -2
  85. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs +2 -2
  86. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs.map +2 -2
  87. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
  88. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  89. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +171 -140
  90. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  91. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs +3 -3
  92. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +2 -2
  93. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +26 -25
  94. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  95. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +6 -5
  96. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
  97. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +9 -10
  98. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
  99. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +5 -4
  100. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  101. package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
  102. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  103. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +4 -5
  104. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +2 -2
  105. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +1 -1
  106. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  107. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +3 -2
  108. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  109. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +18 -7
  110. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  111. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +254 -0
  112. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
  113. package/dist-esm/lib/ui/components/primitives/layout.mjs +21 -0
  114. package/dist-esm/lib/ui/components/primitives/layout.mjs.map +7 -0
  115. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +10 -8
  116. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  117. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
  118. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
  119. package/dist-esm/lib/ui/context/actions.mjs +15 -0
  120. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  121. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  122. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
  123. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  124. package/dist-esm/lib/ui/version.mjs +3 -3
  125. package/dist-esm/lib/ui/version.mjs.map +1 -1
  126. package/package.json +3 -3
  127. package/src/index.ts +13 -0
  128. package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
  129. package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
  130. package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
  131. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +2 -1
  132. package/src/lib/ui/components/DefaultMenuPanel.tsx +4 -3
  133. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
  134. package/src/lib/ui/components/NavigationPanel/DefaultNavigationPanel.tsx +1 -1
  135. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +3 -2
  136. package/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx +4 -3
  137. package/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx +3 -3
  138. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
  139. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +146 -107
  140. package/src/lib/ui/components/StylePanel/DoubleDropdownPicker.tsx +3 -3
  141. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +7 -6
  142. package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +7 -6
  143. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +10 -11
  144. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
  145. package/src/lib/ui/components/menu-items.tsx +8 -0
  146. package/src/lib/ui/components/primitives/TldrawUiButtonPicker.tsx +38 -36
  147. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +1 -1
  148. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +4 -2
  149. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +34 -12
  150. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +332 -0
  151. package/src/lib/ui/components/primitives/layout.tsx +33 -0
  152. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +13 -9
  153. package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
  154. package/src/lib/ui/context/actions.tsx +15 -0
  155. package/src/lib/ui/context/events.tsx +1 -0
  156. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
  157. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
  158. package/src/lib/ui/version.ts +3 -3
  159. package/src/lib/ui.css +80 -69
  160. package/src/test/arrows-megabus.test.tsx +12 -6
  161. package/src/test/inner-outer-margin.test.ts +315 -0
  162. package/tldraw.css +82 -69
@@ -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',
@@ -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-canary.dfdf6b7de8c2'
4
+ export const version = '3.16.0-canary.efdec30fc411'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-04T10:21:53.495Z',
8
- patch: '2025-08-04T10:21:53.495Z',
7
+ minor: '2025-08-08T14:53:46.305Z',
8
+ patch: '2025-08-08T14:53:46.305Z',
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']::after {
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
  }
@@ -161,12 +161,6 @@
161
161
  }
162
162
  }
163
163
 
164
- /* Panel button */
165
-
166
- .tlui-button__panel {
167
- position: relative;
168
- }
169
-
170
164
  /* Menu button */
171
165
 
172
166
  .tlui-button__menu {
@@ -207,7 +201,7 @@
207
201
 
208
202
  /* Tool lock button */
209
203
 
210
- .tlui-toolbar__lock-button {
204
+ .tlui-main-toolbar__lock-button {
211
205
  position: absolute;
212
206
  top: 4px;
213
207
  right: 0px;
@@ -218,7 +212,7 @@
218
212
  border-radius: var(--radius-2);
219
213
  }
220
214
 
221
- .tlui-toolbar__lock-button::after {
215
+ .tlui-main-toolbar__lock-button::after {
222
216
  top: 4px;
223
217
  left: 8px;
224
218
  inset: 4px;
@@ -230,16 +224,6 @@
230
224
  position: relative;
231
225
  height: 48px;
232
226
  width: 48px;
233
- margin-left: -2px;
234
- margin-right: -2px;
235
- }
236
-
237
- .tlui-button__tool:nth-of-type(1) {
238
- margin-left: 0px;
239
- }
240
-
241
- .tlui-button__tool:nth-last-of-type(1) {
242
- margin-right: 0px;
243
227
  }
244
228
 
245
229
  .tlui-button__tool::after {
@@ -270,47 +254,30 @@
270
254
  width: 16px;
271
255
  }
272
256
 
273
- /* Button Row */
257
+ /* Row layout */
274
258
 
275
- .tlui-buttons__horizontal {
259
+ .tlui-row {
276
260
  display: flex;
277
261
  flex-direction: row;
262
+ padding: 0 2px;
278
263
  }
279
- .tlui-buttons__horizontal > * {
264
+ .tlui-row > * {
280
265
  margin-left: -2px;
281
266
  margin-right: -2px;
282
267
  }
283
- .tlui-buttons__horizontal > *:nth-child(1) {
284
- margin-left: 0px;
285
- }
286
- .tlui-buttons__horizontal > *:nth-last-child(1) {
287
- margin-right: 0px;
288
- }
289
268
 
290
- /* Button Grid */
269
+ /* Grid layout */
291
270
 
292
- .tlui-buttons__grid {
271
+ .tlui-grid {
293
272
  display: grid;
294
- grid-template-columns: repeat(4, auto);
273
+ grid-template-columns: repeat(4, 1fr);
295
274
  grid-auto-flow: row;
296
275
  overflow: hidden;
276
+ padding: 2px;
297
277
  }
298
- .tlui-buttons__grid > .tlui-button {
278
+ .tlui-grid > * {
299
279
  margin: -2px;
300
280
  }
301
- .tlui-buttons__grid > .tlui-button:nth-of-type(4n),
302
- .tlui-buttons__vertical-align > .tlui-button:nth-of-type(3n) {
303
- margin-right: 0px;
304
- }
305
- .tlui-buttons__grid > .tlui-button:nth-of-type(4n - 3) {
306
- margin-left: 0px;
307
- }
308
- .tlui-buttons__grid > .tlui-button:nth-of-type(-n + 4) {
309
- margin-top: 0px;
310
- }
311
- .tlui-buttons__grid > .tlui-button:nth-last-of-type(-n + 4) {
312
- margin-bottom: 0px;
313
- }
314
281
 
315
282
  /* Zoom button */
316
283
 
@@ -1000,6 +967,12 @@
1000
967
  max-width: 148px;
1001
968
  }
1002
969
 
970
+ .tlui-style-panel[data-show-ui-labels='true'] .tlui-button[data-isactive='true'] {
971
+ border-radius: 10px;
972
+ outline: 2px solid var(--color-text);
973
+ outline-offset: -5px;
974
+ }
975
+
1003
976
  .tlui-style-panel::-webkit-scrollbar {
1004
977
  display: none;
1005
978
  }
@@ -1027,12 +1000,8 @@
1027
1000
  border-bottom: 1px solid var(--color-divider);
1028
1001
  }
1029
1002
 
1030
- .tlui-style-panel__row {
1031
- display: flex;
1032
- }
1033
- /* Only really used for the alignment picker */
1034
- .tlui-style-panel__row__extra-button {
1035
- margin-left: -2px;
1003
+ .tlui-style-panel__dropdown-picker:only-child {
1004
+ width: 100%;
1036
1005
  }
1037
1006
 
1038
1007
  .tlui-style-panel__double-select-picker {
@@ -1064,6 +1033,26 @@
1064
1033
  }
1065
1034
  }
1066
1035
 
1036
+ /* Accessibility subheadings */
1037
+
1038
+ .tlui-style-panel__section .tlui-style-panel__subheading,
1039
+ .tlui-style-panel__section__common .tlui-style-panel__subheading,
1040
+ .tlui-style-panel__subheading + .tlui-slider__container {
1041
+ margin: 0;
1042
+ padding: var(--space-2) var(--space-3) 0px var(--space-4);
1043
+ font-size: 12px;
1044
+ font-weight: inherit;
1045
+ line-height: inherit;
1046
+ }
1047
+
1048
+ .tlui-style-panel .tlui-style-panel__subheading:nth-of-type(1) {
1049
+ padding-top: var(--space-3);
1050
+ }
1051
+
1052
+ .tlui-style-panel__subheading + .tlui-slider__container {
1053
+ padding-top: 0px;
1054
+ }
1055
+
1067
1056
  /* --------------------- Bottom --------------------- */
1068
1057
 
1069
1058
  .tlui-layout__bottom {
@@ -1128,7 +1117,7 @@
1128
1117
  /* --------------------- Toolbar -------------------- */
1129
1118
 
1130
1119
  /* Wide container */
1131
- .tlui-toolbar {
1120
+ .tlui-main-toolbar {
1132
1121
  grid-column: 1 / span 3;
1133
1122
  grid-row: 1;
1134
1123
  display: flex;
@@ -1139,7 +1128,7 @@
1139
1128
  }
1140
1129
 
1141
1130
  /* Centered Content */
1142
- .tlui-toolbar__inner {
1131
+ .tlui-main-toolbar__inner {
1143
1132
  position: relative;
1144
1133
  width: fit-content;
1145
1134
  display: flex;
@@ -1147,12 +1136,12 @@
1147
1136
  align-items: flex-end;
1148
1137
  }
1149
1138
 
1150
- .tlui-toolbar__left {
1139
+ .tlui-main-toolbar__left {
1151
1140
  width: fit-content;
1152
1141
  }
1153
1142
 
1154
1143
  /* Row of controls + lock button */
1155
- .tlui-toolbar__extras {
1144
+ .tlui-main-toolbar__extras {
1156
1145
  position: relative;
1157
1146
  z-index: var(--layer-above);
1158
1147
  width: 100%;
@@ -1161,11 +1150,11 @@
1161
1150
  height: 48px;
1162
1151
  }
1163
1152
 
1164
- .tlui-toolbar__extras:empty {
1153
+ .tlui-main-toolbar__extras:empty {
1165
1154
  display: none;
1166
1155
  }
1167
1156
 
1168
- .tlui-toolbar__extras__controls {
1157
+ .tlui-main-toolbar__extras__controls {
1169
1158
  display: flex;
1170
1159
  position: relative;
1171
1160
  flex-direction: row;
@@ -1180,7 +1169,7 @@
1180
1169
  width: fit-content;
1181
1170
  }
1182
1171
 
1183
- .tlui-toolbar__tools {
1172
+ .tlui-main-toolbar__tools {
1184
1173
  display: flex;
1185
1174
  flex-direction: row;
1186
1175
  align-items: center;
@@ -1191,35 +1180,57 @@
1191
1180
  background: var(--color-panel);
1192
1181
  box-shadow: var(--shadow-2);
1193
1182
  }
1194
- .tlui-toolbar__tools__list {
1195
- display: flex;
1196
- flex-direction: row;
1197
- align-items: center;
1198
- }
1199
1183
 
1200
- .tlui-toolbar__overflow {
1184
+ .tlui-main-toolbar__overflow {
1201
1185
  width: 40px;
1186
+ margin-left: 2px;
1202
1187
  }
1203
1188
 
1204
- .tlui-layout__mobile .tlui-toolbar__overflow {
1189
+ .tlui-layout__mobile .tlui-main-toolbar__overflow {
1205
1190
  width: 32px;
1206
1191
  padding: 0px;
1207
1192
  }
1208
1193
 
1209
- .tlui-toolbar *[data-state='open']::after {
1194
+ .tlui-main-toolbar *[data-state='open']::after {
1210
1195
  background: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--color-muted-2) 100%);
1211
1196
  opacity: 1;
1212
1197
  }
1213
1198
 
1214
1199
  @media (hover: hover) {
1215
- .tlui-toolbar *[data-state='open']:not(:hover)::after {
1200
+ .tlui-main-toolbar *[data-state='open']:not(:hover)::after {
1216
1201
  background: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--color-muted-2) 100%);
1217
1202
  opacity: 1;
1218
1203
  }
1219
1204
  }
1220
1205
 
1221
- .tlui-layout__mobile .tlui-toolbar {
1222
- transition: transform 0.15s ease-out 0.05s;
1206
+ /* ------------------- Tooltip -------------------- */
1207
+
1208
+ .tlui-tooltip {
1209
+ font-size: 12px;
1210
+ padding: 2px 8px;
1211
+ border-radius: 4px;
1212
+ background-color: var(--color-tooltip);
1213
+ box-shadow: none;
1214
+ color: var(--color-text-shadow);
1215
+ max-width: 400px;
1216
+ width: fit-content;
1217
+ text-align: center;
1218
+ pointer-events: none;
1219
+ will-change: transform, opacity;
1220
+ z-index: 2;
1221
+ }
1222
+
1223
+ .tlui-tooltip__arrow {
1224
+ fill: var(--color-tooltip);
1225
+ will-change: opacity;
1226
+ }
1227
+
1228
+ [data-radix-popper-content-wrapper]:has(.tlui-tooltip) {
1229
+ z-index: var(--layer-toasts) !important;
1230
+ }
1231
+
1232
+ [data-radix-popper-content-wrapper]:has(.tlui-tooltip[data-should-animate='true']) {
1233
+ transition: all 0.1s ease-out;
1223
1234
  }
1224
1235
 
1225
1236
  /* ------------------- 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, 50) // box3 is higher
295
+ editor.pointerMove(225, 70) // box3 is higher
294
296
  expect(bindings().end).toMatchObject({ toId: ids.box3 })
295
- editor.pointerMove(275, 50) // box4 is higher but box 3 is filled
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, 50)
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
+ })