tldraw 4.3.0-canary.35392ae6dc0d → 4.3.0-canary.37e6bf0fa8c6

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 (124) hide show
  1. package/dist-cjs/index.d.ts +9 -0
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/canvas/TldrawSelectionForeground.js +2 -2
  4. package/dist-cjs/lib/canvas/TldrawSelectionForeground.js.map +2 -2
  5. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +9 -12
  6. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
  7. package/dist-cjs/lib/shapes/arrow/arrow-types.js.map +1 -1
  8. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +3 -3
  9. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +1 -1
  11. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +10 -6
  13. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  14. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +1 -1
  15. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +5 -5
  17. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  18. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +2 -1
  19. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js.map +2 -2
  20. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +14 -2
  21. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js.map +3 -3
  22. package/dist-cjs/lib/shapes/shared/RichTextLabel.js +11 -3
  23. package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +3 -3
  24. package/dist-cjs/lib/shapes/shared/ShapeFill.js +2 -2
  25. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  26. package/dist-cjs/lib/shapes/shared/{useForceSolid.js → useEfficientZoomThreshold.js} +10 -7
  27. package/dist-cjs/lib/shapes/shared/useEfficientZoomThreshold.js.map +7 -0
  28. package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js +1 -1
  29. package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js.map +2 -2
  30. package/dist-cjs/lib/shapes/text/TextShapeUtil.js +5 -2
  31. package/dist-cjs/lib/shapes/text/TextShapeUtil.js.map +2 -2
  32. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +1 -1
  33. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +2 -2
  34. package/dist-cjs/lib/tools/SelectTool/childStates/EditingShape.js +30 -10
  35. package/dist-cjs/lib/tools/SelectTool/childStates/EditingShape.js.map +2 -2
  36. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.js +3 -9
  37. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.js.map +2 -2
  38. package/dist-cjs/lib/ui/components/ZoomMenu/DefaultZoomMenu.js +1 -1
  39. package/dist-cjs/lib/ui/components/ZoomMenu/DefaultZoomMenu.js.map +2 -2
  40. package/dist-cjs/lib/ui/components/menu-items.js +3 -1
  41. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  42. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +1 -13
  43. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  44. package/dist-cjs/lib/ui/version.js +3 -3
  45. package/dist-cjs/lib/ui/version.js.map +1 -1
  46. package/dist-cjs/lib/utils/text/richText.js +7 -17
  47. package/dist-cjs/lib/utils/text/richText.js.map +3 -3
  48. package/dist-esm/index.d.mts +9 -0
  49. package/dist-esm/index.mjs +1 -1
  50. package/dist-esm/lib/canvas/TldrawSelectionForeground.mjs +2 -2
  51. package/dist-esm/lib/canvas/TldrawSelectionForeground.mjs.map +2 -2
  52. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +10 -14
  53. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  54. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +3 -3
  55. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  56. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +1 -1
  57. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  58. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +10 -6
  59. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  60. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +1 -1
  61. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  62. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +5 -5
  63. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  64. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs +3 -2
  65. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs.map +2 -2
  66. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs +14 -2
  67. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs.map +2 -2
  68. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +11 -3
  69. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
  70. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +2 -2
  71. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  72. package/dist-esm/lib/shapes/shared/useEfficientZoomThreshold.mjs +12 -0
  73. package/dist-esm/lib/shapes/shared/useEfficientZoomThreshold.mjs.map +7 -0
  74. package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs +1 -1
  75. package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs.map +2 -2
  76. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +5 -2
  77. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  78. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +1 -1
  79. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +2 -2
  80. package/dist-esm/lib/tools/SelectTool/childStates/EditingShape.mjs +30 -10
  81. package/dist-esm/lib/tools/SelectTool/childStates/EditingShape.mjs.map +2 -2
  82. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.mjs +2 -8
  83. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.mjs.map +2 -2
  84. package/dist-esm/lib/ui/components/ZoomMenu/DefaultZoomMenu.mjs +1 -1
  85. package/dist-esm/lib/ui/components/ZoomMenu/DefaultZoomMenu.mjs.map +2 -2
  86. package/dist-esm/lib/ui/components/menu-items.mjs +3 -1
  87. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  88. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +9 -14
  89. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  90. package/dist-esm/lib/ui/version.mjs +3 -3
  91. package/dist-esm/lib/ui/version.mjs.map +1 -1
  92. package/dist-esm/lib/utils/text/richText.mjs +3 -3
  93. package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
  94. package/package.json +3 -3
  95. package/src/lib/canvas/TldrawSelectionForeground.tsx +2 -2
  96. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +10 -12
  97. package/src/lib/shapes/arrow/arrow-types.ts +2 -0
  98. package/src/lib/shapes/draw/DrawShapeUtil.tsx +3 -3
  99. package/src/lib/shapes/frame/FrameShapeUtil.tsx +1 -1
  100. package/src/lib/shapes/geo/GeoShapeUtil.tsx +9 -4
  101. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +1 -1
  102. package/src/lib/shapes/note/NoteShapeUtil.tsx +7 -8
  103. package/src/lib/shapes/shared/HyperlinkButton.tsx +3 -2
  104. package/src/lib/shapes/shared/PlainTextLabel.tsx +10 -1
  105. package/src/lib/shapes/shared/RichTextLabel.tsx +11 -2
  106. package/src/lib/shapes/shared/ShapeFill.tsx +2 -2
  107. package/src/lib/shapes/shared/useEfficientZoomThreshold.ts +10 -0
  108. package/src/lib/shapes/shared/useImageOrVideoAsset.ts +1 -1
  109. package/src/lib/shapes/text/TextShapeUtil.tsx +5 -0
  110. package/src/lib/shapes/video/VideoShapeUtil.tsx +2 -1
  111. package/src/lib/tools/SelectTool/childStates/EditingShape.ts +44 -11
  112. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.tsx +1 -9
  113. package/src/lib/ui/components/ZoomMenu/DefaultZoomMenu.tsx +1 -1
  114. package/src/lib/ui/components/menu-items.tsx +3 -1
  115. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +10 -15
  116. package/src/lib/ui/version.ts +3 -3
  117. package/src/lib/utils/text/richText.ts +3 -3
  118. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +2 -2
  119. package/src/test/commands/cameraState.test.ts +299 -0
  120. package/tldraw.css +8 -4
  121. package/dist-cjs/lib/shapes/shared/useForceSolid.js.map +0 -7
  122. package/dist-esm/lib/shapes/shared/useForceSolid.mjs +0 -9
  123. package/dist-esm/lib/shapes/shared/useForceSolid.mjs.map +0 -7
  124. package/src/lib/shapes/shared/useForceSolid.ts +0 -6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/ui/components/primitives/TldrawUiTooltip.tsx"],
4
- "sourcesContent": ["import { assert, Atom, atom, Editor, uniqueId, useMaybeEditor, useValue } from '@tldraw/editor'\nimport { Tooltip as _Tooltip } from 'radix-ui'\nimport React, {\n\tcreateContext,\n\tforwardRef,\n\tReactNode,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { useTldrawUiOrientation } from './layout'\n\nconst DEFAULT_TOOLTIP_DELAY_MS = 700\n\n/** @public */\nexport interface TldrawUiTooltipProps {\n\tchildren: React.ReactNode\n\tcontent?: string | React.ReactNode\n\tside?: 'top' | 'right' | 'bottom' | 'left'\n\tsideOffset?: number\n\tdisabled?: boolean\n\tshowOnMobile?: boolean\n\tdelayDuration?: number\n}\n\ninterface TooltipData {\n\tid: string\n\tcontent: ReactNode\n\tside: 'top' | 'right' | 'bottom' | 'left'\n\tsideOffset: number\n\tshowOnMobile: boolean\n\ttargetElement: HTMLElement\n\tdelayDuration: number\n}\n\n// State machine states\ntype TooltipState =\n\t| { name: 'idle' }\n\t| { name: 'pointer_down' }\n\t| { name: 'showing'; tooltip: TooltipData }\n\t| { name: 'waiting_to_hide'; tooltip: TooltipData; timeoutId: number }\n\n// State machine events\ntype TooltipEvent =\n\t| { type: 'pointer_down' }\n\t| { type: 'pointer_up' }\n\t| { type: 'show'; tooltip: TooltipData }\n\t| { type: 'hide'; tooltipId: string; editor: Editor | null; instant: boolean }\n\t| { type: 'hide_all' }\n\n// Singleton tooltip manager using explicit state machine\nclass TooltipManager {\n\tprivate static instance: TooltipManager | null = null\n\tprivate state = atom<TooltipState>('tooltip state', { name: 'idle' })\n\n\tstatic getInstance(): TooltipManager {\n\t\tif (!TooltipManager.instance) {\n\t\t\tTooltipManager.instance = new TooltipManager()\n\t\t}\n\t\treturn TooltipManager.instance\n\t}\n\n\thideAllTooltips() {\n\t\tthis.handleEvent({ type: 'hide_all' })\n\t}\n\n\thandleEvent(event: TooltipEvent) {\n\t\tconst currentState = this.state.get()\n\n\t\tswitch (event.type) {\n\t\t\tcase 'pointer_down': {\n\t\t\t\t// Transition to pointer_down from any state\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\t\t\t\tthis.state.set({ name: 'pointer_down' })\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'pointer_up': {\n\t\t\t\t// Only transition from pointer_down to idle\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'show': {\n\t\t\t\t// Don't show tooltips while pointer is down\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Clear any existing timeout if transitioning from waiting_to_hide\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\n\t\t\t\t// Transition to showing state\n\t\t\t\tthis.state.set({ name: 'showing', tooltip: event.tooltip })\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'hide': {\n\t\t\t\tconst { tooltipId, editor, instant } = event\n\n\t\t\t\t// Only hide if the tooltip matches\n\t\t\t\tif (currentState.name === 'showing' && currentState.tooltip.id === tooltipId) {\n\t\t\t\t\tif (editor && !instant) {\n\t\t\t\t\t\t// Transition to waiting_to_hide state\n\t\t\t\t\t\tconst timeoutId = editor.timers.setTimeout(() => {\n\t\t\t\t\t\t\tconst state = this.state.get()\n\t\t\t\t\t\t\tif (state.name === 'waiting_to_hide' && state.tooltip.id === tooltipId) {\n\t\t\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 300)\n\t\t\t\t\t\tthis.state.set({\n\t\t\t\t\t\t\tname: 'waiting_to_hide',\n\t\t\t\t\t\t\ttooltip: currentState.tooltip,\n\t\t\t\t\t\t\ttimeoutId,\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t}\n\t\t\t\t} else if (\n\t\t\t\t\tcurrentState.name === 'waiting_to_hide' &&\n\t\t\t\t\tcurrentState.tooltip.id === tooltipId\n\t\t\t\t) {\n\t\t\t\t\t// Already waiting to hide, make it instant if requested\n\t\t\t\t\tif (instant) {\n\t\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'hide_all': {\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\t\t\t\t// Preserve pointer_down state if that's the current state\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tgetCurrentTooltipData(): TooltipData | null {\n\t\tconst currentState = this.state.get()\n\t\tlet tooltip: TooltipData | null = null\n\n\t\tif (currentState.name === 'showing') {\n\t\t\ttooltip = currentState.tooltip\n\t\t} else if (currentState.name === 'waiting_to_hide') {\n\t\t\ttooltip = currentState.tooltip\n\t\t}\n\n\t\tif (!tooltip) return null\n\t\tif (!this.supportsHover() && !tooltip.showOnMobile) return null\n\t\treturn tooltip\n\t}\n\n\tprivate supportsHoverAtom: Atom<boolean> | null = null\n\tsupportsHover() {\n\t\tif (!this.supportsHoverAtom) {\n\t\t\tconst mediaQuery = window.matchMedia('(hover: hover)')\n\t\t\tconst supportsHover = atom('has hover', mediaQuery.matches)\n\t\t\tthis.supportsHoverAtom = supportsHover\n\t\t\tmediaQuery.addEventListener('change', (e) => {\n\t\t\t\tsupportsHover.set(e.matches)\n\t\t\t})\n\t\t}\n\t\treturn this.supportsHoverAtom.get()\n\t}\n}\n\nconst tooltipManager = TooltipManager.getInstance()\n\n/** @public */\nexport function hideAllTooltips() {\n\ttooltipManager.hideAllTooltips()\n}\n\n// Context for the tooltip singleton\nconst TooltipSingletonContext = createContext<boolean>(false)\n\n/** @public */\nexport interface TldrawUiTooltipProviderProps {\n\tchildren: React.ReactNode\n}\n\n/** @public @react */\nexport function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderProps) {\n\treturn (\n\t\t<_Tooltip.Provider skipDelayDuration={700}>\n\t\t\t<TooltipSingletonContext.Provider value={true}>\n\t\t\t\t{children}\n\t\t\t\t<TooltipSingleton />\n\t\t\t</TooltipSingletonContext.Provider>\n\t\t</_Tooltip.Provider>\n\t)\n}\n\n// The singleton tooltip component that renders once\nfunction TooltipSingleton() {\n\tconst [isOpen, setIsOpen] = useState(false)\n\tconst triggerRef = useRef<HTMLDivElement>(null)\n\tconst isFirstShowRef = useRef(true)\n\tconst editor = useMaybeEditor()\n\n\tconst currentTooltip = useValue(\n\t\t'current tooltip',\n\t\t() => tooltipManager.getCurrentTooltipData(),\n\t\t[]\n\t)\n\n\tconst cameraState = useValue('camera state', () => editor?.getCameraState(), [editor])\n\n\t// Hide tooltip when camera is moving (panning/zooming)\n\tuseEffect(() => {\n\t\tif (cameraState === 'moving' && isOpen && currentTooltip) {\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: currentTooltip.id,\n\t\t\t\teditor,\n\t\t\t\tinstant: true,\n\t\t\t})\n\t\t}\n\t}, [cameraState, isOpen, currentTooltip, editor])\n\n\tuseEffect(() => {\n\t\tfunction handleKeyDown(event: KeyboardEvent) {\n\t\t\tif (event.key === 'Escape' && currentTooltip && isOpen) {\n\t\t\t\thideAllTooltips()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t}\n\n\t\tdocument.addEventListener('keydown', handleKeyDown, { capture: true })\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('keydown', handleKeyDown, { capture: true })\n\t\t}\n\t}, [currentTooltip, isOpen])\n\n\t// Hide tooltip and prevent new ones from opening while pointer is down\n\tuseEffect(() => {\n\t\tfunction handlePointerDown() {\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_down' })\n\t\t}\n\n\t\tfunction handlePointerUp() {\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_up' })\n\t\t}\n\n\t\tdocument.addEventListener('pointerdown', handlePointerDown, { capture: true })\n\t\tdocument.addEventListener('pointerup', handlePointerUp, { capture: true })\n\t\tdocument.addEventListener('pointercancel', handlePointerUp, { capture: true })\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('pointerdown', handlePointerDown, { capture: true })\n\t\t\tdocument.removeEventListener('pointerup', handlePointerUp, { capture: true })\n\t\t\tdocument.removeEventListener('pointercancel', handlePointerUp, { capture: true })\n\t\t\t// Reset pointer state on unmount to prevent stuck state\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_up' })\n\t\t}\n\t}, [])\n\n\t// Update open state and trigger position\n\tuseEffect(() => {\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null\n\t\tif (currentTooltip && triggerRef.current) {\n\t\t\t// Position the invisible trigger element over the active element\n\t\t\tconst activeRect = currentTooltip.targetElement.getBoundingClientRect()\n\t\t\tconst trigger = triggerRef.current\n\n\t\t\ttrigger.style.position = 'fixed'\n\t\t\ttrigger.style.left = `${activeRect.left}px`\n\t\t\ttrigger.style.top = `${activeRect.top}px`\n\t\t\ttrigger.style.width = `${activeRect.width}px`\n\t\t\ttrigger.style.height = `${activeRect.height}px`\n\t\t\ttrigger.style.pointerEvents = 'none'\n\t\t\ttrigger.style.zIndex = '9999'\n\n\t\t\t// Handle delay for first show\n\t\t\tif (isFirstShowRef.current) {\n\t\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tsetIsOpen(true)\n\t\t\t\t\tisFirstShowRef.current = false\n\t\t\t\t}, currentTooltip.delayDuration)\n\t\t\t} else {\n\t\t\t\t// Subsequent tooltips show immediately\n\t\t\t\tsetIsOpen(true)\n\t\t\t}\n\t\t} else {\n\t\t\t// Hide tooltip immediately\n\t\t\tsetIsOpen(false)\n\t\t\t// Reset first show state after tooltip is hidden\n\t\t\tisFirstShowRef.current = true\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer)\n\t\t\t}\n\t\t}\n\t}, [currentTooltip])\n\n\tif (!currentTooltip) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<_Tooltip.Root open={isOpen} delayDuration={0}>\n\t\t\t<_Tooltip.Trigger asChild>\n\t\t\t\t<div ref={triggerRef} />\n\t\t\t</_Tooltip.Trigger>\n\t\t\t<_Tooltip.Content\n\t\t\t\tclassName=\"tlui-tooltip\"\n\t\t\t\tside={currentTooltip.side}\n\t\t\t\tsideOffset={currentTooltip.sideOffset}\n\t\t\t\tavoidCollisions\n\t\t\t\tcollisionPadding={8}\n\t\t\t\tdir=\"ltr\"\n\t\t\t>\n\t\t\t\t{currentTooltip.content}\n\t\t\t\t<_Tooltip.Arrow className=\"tlui-tooltip__arrow\" />\n\t\t\t</_Tooltip.Content>\n\t\t</_Tooltip.Root>\n\t)\n}\n\n/** @public @react */\nexport const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(\n\t(\n\t\t{\n\t\t\tchildren,\n\t\t\tcontent,\n\t\t\tside,\n\t\t\tsideOffset = 5,\n\t\t\tdisabled = false,\n\t\t\tshowOnMobile = false,\n\t\t\tdelayDuration,\n\t\t},\n\t\tref\n\t) => {\n\t\tconst editor = useMaybeEditor()\n\t\tconst tooltipId = useRef<string>(uniqueId())\n\t\tconst hasProvider = useContext(TooltipSingletonContext)\n\t\tconst enhancedA11yMode = useValue(\n\t\t\t'enhancedA11yMode',\n\t\t\t() => editor?.user.getEnhancedA11yMode(),\n\t\t\t[editor]\n\t\t)\n\n\t\tconst orientationCtx = useTldrawUiOrientation()\n\t\tconst sideToUse = side ?? orientationCtx.tooltipSide\n\n\t\tuseEffect(() => {\n\t\t\tconst currentTooltipId = tooltipId.current\n\t\t\treturn () => {\n\t\t\t\tif (hasProvider) {\n\t\t\t\t\ttooltipManager.handleEvent({\n\t\t\t\t\t\ttype: 'hide',\n\t\t\t\t\t\ttooltipId: currentTooltipId,\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\tinstant: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}, [editor, hasProvider])\n\n\t\t// Don't show tooltip if disabled, no content, or enhanced accessibility mode is disabled\n\t\tif (disabled || !content) {\n\t\t\treturn <>{children}</>\n\t\t}\n\n\t\tlet delayDurationToUse\n\t\tif (enhancedA11yMode) {\n\t\t\tdelayDurationToUse = 0\n\t\t} else {\n\t\t\tdelayDurationToUse =\n\t\t\t\tdelayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)\n\t\t}\n\n\t\t// Fallback to old behavior if no provider\n\t\tif (!hasProvider || enhancedA11yMode) {\n\t\t\treturn (\n\t\t\t\t<_Tooltip.Root\n\t\t\t\t\tdelayDuration={delayDurationToUse}\n\t\t\t\t\tdisableHoverableContent={!enhancedA11yMode}\n\t\t\t\t>\n\t\t\t\t\t<_Tooltip.Trigger asChild ref={ref}>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</_Tooltip.Trigger>\n\t\t\t\t\t<_Tooltip.Content\n\t\t\t\t\t\tclassName=\"tlui-tooltip\"\n\t\t\t\t\t\tside={sideToUse}\n\t\t\t\t\t\tsideOffset={sideOffset}\n\t\t\t\t\t\tavoidCollisions\n\t\t\t\t\t\tcollisionPadding={8}\n\t\t\t\t\t\tdir=\"ltr\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{content}\n\t\t\t\t\t\t<_Tooltip.Arrow className=\"tlui-tooltip__arrow\" />\n\t\t\t\t\t</_Tooltip.Content>\n\t\t\t\t</_Tooltip.Root>\n\t\t\t)\n\t\t}\n\n\t\tconst child = React.Children.only(children)\n\t\tassert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')\n\n\t\tconst handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {\n\t\t\tchild.props.onMouseEnter?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'show',\n\t\t\t\ttooltip: {\n\t\t\t\t\tid: tooltipId.current,\n\t\t\t\t\tcontent,\n\t\t\t\t\ttargetElement: event.currentTarget as HTMLElement,\n\t\t\t\t\tside: sideToUse,\n\t\t\t\t\tsideOffset,\n\t\t\t\t\tshowOnMobile,\n\t\t\t\t\tdelayDuration: delayDurationToUse,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tconst handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {\n\t\t\tchild.props.onMouseLeave?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: tooltipId.current,\n\t\t\t\teditor,\n\t\t\t\tinstant: false,\n\t\t\t})\n\t\t}\n\n\t\tconst handleFocus = (event: React.FocusEvent<HTMLElement>) => {\n\t\t\tchild.props.onFocus?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'show',\n\t\t\t\ttooltip: {\n\t\t\t\t\tid: tooltipId.current,\n\t\t\t\t\tcontent,\n\t\t\t\t\ttargetElement: event.currentTarget as HTMLElement,\n\t\t\t\t\tside: sideToUse,\n\t\t\t\t\tsideOffset,\n\t\t\t\t\tshowOnMobile,\n\t\t\t\t\tdelayDuration: delayDurationToUse,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tconst handleBlur = (event: React.FocusEvent<HTMLElement>) => {\n\t\t\tchild.props.onBlur?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: tooltipId.current,\n\t\t\t\teditor,\n\t\t\t\tinstant: false,\n\t\t\t})\n\t\t}\n\n\t\tconst childrenWithHandlers = React.cloneElement(children as React.ReactElement, {\n\t\t\tonMouseEnter: handleMouseEnter,\n\t\t\tonMouseLeave: handleMouseLeave,\n\t\t\tonFocus: handleFocus,\n\t\t\tonBlur: handleBlur,\n\t\t})\n\n\t\treturn childrenWithHandlers\n\t}\n)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\tassert,\n\tatom,\n\tEditor,\n\ttlenvReactive,\n\tuniqueId,\n\tuseMaybeEditor,\n\tuseValue,\n} from '@tldraw/editor'\nimport { Tooltip as _Tooltip } from 'radix-ui'\nimport React, {\n\tcreateContext,\n\tforwardRef,\n\tReactNode,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { useTldrawUiOrientation } from './layout'\n\nconst DEFAULT_TOOLTIP_DELAY_MS = 700\n\n/** @public */\nexport interface TldrawUiTooltipProps {\n\tchildren: React.ReactNode\n\tcontent?: string | React.ReactNode\n\tside?: 'top' | 'right' | 'bottom' | 'left'\n\tsideOffset?: number\n\tdisabled?: boolean\n\tshowOnMobile?: boolean\n\tdelayDuration?: number\n}\n\ninterface TooltipData {\n\tid: string\n\tcontent: ReactNode\n\tside: 'top' | 'right' | 'bottom' | 'left'\n\tsideOffset: number\n\tshowOnMobile: boolean\n\ttargetElement: HTMLElement\n\tdelayDuration: number\n}\n\n// State machine states\ntype TooltipState =\n\t| { name: 'idle' }\n\t| { name: 'pointer_down' }\n\t| { name: 'showing'; tooltip: TooltipData }\n\t| { name: 'waiting_to_hide'; tooltip: TooltipData; timeoutId: number }\n\n// State machine events\ntype TooltipEvent =\n\t| { type: 'pointer_down' }\n\t| { type: 'pointer_up' }\n\t| { type: 'show'; tooltip: TooltipData }\n\t| { type: 'hide'; tooltipId: string; editor: Editor | null; instant: boolean }\n\t| { type: 'hide_all' }\n\n// Singleton tooltip manager using explicit state machine\nclass TooltipManager {\n\tprivate static instance: TooltipManager | null = null\n\tprivate state = atom<TooltipState>('tooltip state', { name: 'idle' })\n\n\tstatic getInstance(): TooltipManager {\n\t\tif (!TooltipManager.instance) {\n\t\t\tTooltipManager.instance = new TooltipManager()\n\t\t}\n\t\treturn TooltipManager.instance\n\t}\n\n\thideAllTooltips() {\n\t\tthis.handleEvent({ type: 'hide_all' })\n\t}\n\n\thandleEvent(event: TooltipEvent) {\n\t\tconst currentState = this.state.get()\n\n\t\tswitch (event.type) {\n\t\t\tcase 'pointer_down': {\n\t\t\t\t// Transition to pointer_down from any state\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\t\t\t\tthis.state.set({ name: 'pointer_down' })\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'pointer_up': {\n\t\t\t\t// Only transition from pointer_down to idle\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'show': {\n\t\t\t\t// Don't show tooltips while pointer is down\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Clear any existing timeout if transitioning from waiting_to_hide\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\n\t\t\t\t// Transition to showing state\n\t\t\t\tthis.state.set({ name: 'showing', tooltip: event.tooltip })\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'hide': {\n\t\t\t\tconst { tooltipId, editor, instant } = event\n\n\t\t\t\t// Only hide if the tooltip matches\n\t\t\t\tif (currentState.name === 'showing' && currentState.tooltip.id === tooltipId) {\n\t\t\t\t\tif (editor && !instant) {\n\t\t\t\t\t\t// Transition to waiting_to_hide state\n\t\t\t\t\t\tconst timeoutId = editor.timers.setTimeout(() => {\n\t\t\t\t\t\t\tconst state = this.state.get()\n\t\t\t\t\t\t\tif (state.name === 'waiting_to_hide' && state.tooltip.id === tooltipId) {\n\t\t\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 300)\n\t\t\t\t\t\tthis.state.set({\n\t\t\t\t\t\t\tname: 'waiting_to_hide',\n\t\t\t\t\t\t\ttooltip: currentState.tooltip,\n\t\t\t\t\t\t\ttimeoutId,\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t}\n\t\t\t\t} else if (\n\t\t\t\t\tcurrentState.name === 'waiting_to_hide' &&\n\t\t\t\t\tcurrentState.tooltip.id === tooltipId\n\t\t\t\t) {\n\t\t\t\t\t// Already waiting to hide, make it instant if requested\n\t\t\t\t\tif (instant) {\n\t\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'hide_all': {\n\t\t\t\tif (currentState.name === 'waiting_to_hide') {\n\t\t\t\t\tclearTimeout(currentState.timeoutId)\n\t\t\t\t}\n\t\t\t\t// Preserve pointer_down state if that's the current state\n\t\t\t\tif (currentState.name === 'pointer_down') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.state.set({ name: 'idle' })\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tgetCurrentTooltipData(): TooltipData | null {\n\t\tconst currentState = this.state.get()\n\t\tlet tooltip: TooltipData | null = null\n\n\t\tif (currentState.name === 'showing') {\n\t\t\ttooltip = currentState.tooltip\n\t\t} else if (currentState.name === 'waiting_to_hide') {\n\t\t\ttooltip = currentState.tooltip\n\t\t}\n\n\t\tif (!tooltip) return null\n\t\tif (tlenvReactive.get().isCoarsePointer && !tooltip.showOnMobile) return null\n\t\treturn tooltip\n\t}\n}\n\nconst tooltipManager = TooltipManager.getInstance()\n\n/** @public */\nexport function hideAllTooltips() {\n\ttooltipManager.hideAllTooltips()\n}\n\n// Context for the tooltip singleton\nconst TooltipSingletonContext = createContext<boolean>(false)\n\n/** @public */\nexport interface TldrawUiTooltipProviderProps {\n\tchildren: React.ReactNode\n}\n\n/** @public @react */\nexport function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderProps) {\n\treturn (\n\t\t<_Tooltip.Provider skipDelayDuration={700}>\n\t\t\t<TooltipSingletonContext.Provider value={true}>\n\t\t\t\t{children}\n\t\t\t\t<TooltipSingleton />\n\t\t\t</TooltipSingletonContext.Provider>\n\t\t</_Tooltip.Provider>\n\t)\n}\n\n// The singleton tooltip component that renders once\nfunction TooltipSingleton() {\n\tconst [isOpen, setIsOpen] = useState(false)\n\tconst triggerRef = useRef<HTMLDivElement>(null)\n\tconst isFirstShowRef = useRef(true)\n\tconst editor = useMaybeEditor()\n\n\tconst currentTooltip = useValue(\n\t\t'current tooltip',\n\t\t() => tooltipManager.getCurrentTooltipData(),\n\t\t[]\n\t)\n\n\tconst cameraState = useValue('camera state', () => editor?.getCameraState(), [editor])\n\n\t// Hide tooltip when camera is moving (panning/zooming)\n\tuseEffect(() => {\n\t\tif (cameraState === 'moving' && isOpen && currentTooltip) {\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: currentTooltip.id,\n\t\t\t\teditor,\n\t\t\t\tinstant: true,\n\t\t\t})\n\t\t}\n\t}, [cameraState, isOpen, currentTooltip, editor])\n\n\tuseEffect(() => {\n\t\tfunction handleKeyDown(event: KeyboardEvent) {\n\t\t\tif (event.key === 'Escape' && currentTooltip && isOpen) {\n\t\t\t\thideAllTooltips()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t}\n\n\t\tdocument.addEventListener('keydown', handleKeyDown, { capture: true })\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('keydown', handleKeyDown, { capture: true })\n\t\t}\n\t}, [currentTooltip, isOpen])\n\n\t// Hide tooltip and prevent new ones from opening while pointer is down\n\tuseEffect(() => {\n\t\tfunction handlePointerDown() {\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_down' })\n\t\t}\n\n\t\tfunction handlePointerUp() {\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_up' })\n\t\t}\n\n\t\tdocument.addEventListener('pointerdown', handlePointerDown, { capture: true })\n\t\tdocument.addEventListener('pointerup', handlePointerUp, { capture: true })\n\t\tdocument.addEventListener('pointercancel', handlePointerUp, { capture: true })\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('pointerdown', handlePointerDown, { capture: true })\n\t\t\tdocument.removeEventListener('pointerup', handlePointerUp, { capture: true })\n\t\t\tdocument.removeEventListener('pointercancel', handlePointerUp, { capture: true })\n\t\t\t// Reset pointer state on unmount to prevent stuck state\n\t\t\ttooltipManager.handleEvent({ type: 'pointer_up' })\n\t\t}\n\t}, [])\n\n\t// Update open state and trigger position\n\tuseEffect(() => {\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null\n\t\tif (currentTooltip && triggerRef.current) {\n\t\t\t// Position the invisible trigger element over the active element\n\t\t\tconst activeRect = currentTooltip.targetElement.getBoundingClientRect()\n\t\t\tconst trigger = triggerRef.current\n\n\t\t\ttrigger.style.position = 'fixed'\n\t\t\ttrigger.style.left = `${activeRect.left}px`\n\t\t\ttrigger.style.top = `${activeRect.top}px`\n\t\t\ttrigger.style.width = `${activeRect.width}px`\n\t\t\ttrigger.style.height = `${activeRect.height}px`\n\t\t\ttrigger.style.pointerEvents = 'none'\n\t\t\ttrigger.style.zIndex = '9999'\n\n\t\t\t// Handle delay for first show\n\t\t\tif (isFirstShowRef.current) {\n\t\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tsetIsOpen(true)\n\t\t\t\t\tisFirstShowRef.current = false\n\t\t\t\t}, currentTooltip.delayDuration)\n\t\t\t} else {\n\t\t\t\t// Subsequent tooltips show immediately\n\t\t\t\tsetIsOpen(true)\n\t\t\t}\n\t\t} else {\n\t\t\t// Hide tooltip immediately\n\t\t\tsetIsOpen(false)\n\t\t\t// Reset first show state after tooltip is hidden\n\t\t\tisFirstShowRef.current = true\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer)\n\t\t\t}\n\t\t}\n\t}, [currentTooltip])\n\n\tif (!currentTooltip) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<_Tooltip.Root open={isOpen} delayDuration={0}>\n\t\t\t<_Tooltip.Trigger asChild>\n\t\t\t\t<div ref={triggerRef} />\n\t\t\t</_Tooltip.Trigger>\n\t\t\t<_Tooltip.Content\n\t\t\t\tclassName=\"tlui-tooltip\"\n\t\t\t\tside={currentTooltip.side}\n\t\t\t\tsideOffset={currentTooltip.sideOffset}\n\t\t\t\tavoidCollisions\n\t\t\t\tcollisionPadding={8}\n\t\t\t\tdir=\"ltr\"\n\t\t\t>\n\t\t\t\t{currentTooltip.content}\n\t\t\t\t<_Tooltip.Arrow className=\"tlui-tooltip__arrow\" />\n\t\t\t</_Tooltip.Content>\n\t\t</_Tooltip.Root>\n\t)\n}\n\n/** @public @react */\nexport const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(\n\t(\n\t\t{\n\t\t\tchildren,\n\t\t\tcontent,\n\t\t\tside,\n\t\t\tsideOffset = 5,\n\t\t\tdisabled = false,\n\t\t\tshowOnMobile = false,\n\t\t\tdelayDuration,\n\t\t},\n\t\tref\n\t) => {\n\t\tconst editor = useMaybeEditor()\n\t\tconst tooltipId = useRef<string>(uniqueId())\n\t\tconst hasProvider = useContext(TooltipSingletonContext)\n\t\tconst enhancedA11yMode = useValue(\n\t\t\t'enhancedA11yMode',\n\t\t\t() => editor?.user.getEnhancedA11yMode(),\n\t\t\t[editor]\n\t\t)\n\n\t\tconst orientationCtx = useTldrawUiOrientation()\n\t\tconst sideToUse = side ?? orientationCtx.tooltipSide\n\n\t\tuseEffect(() => {\n\t\t\tconst currentTooltipId = tooltipId.current\n\t\t\treturn () => {\n\t\t\t\tif (hasProvider) {\n\t\t\t\t\ttooltipManager.handleEvent({\n\t\t\t\t\t\ttype: 'hide',\n\t\t\t\t\t\ttooltipId: currentTooltipId,\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\tinstant: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}, [editor, hasProvider])\n\n\t\t// Don't show tooltip if disabled, no content, or enhanced accessibility mode is disabled\n\t\tif (disabled || !content) {\n\t\t\treturn <>{children}</>\n\t\t}\n\n\t\tlet delayDurationToUse\n\t\tif (enhancedA11yMode) {\n\t\t\tdelayDurationToUse = 0\n\t\t} else {\n\t\t\tdelayDurationToUse =\n\t\t\t\tdelayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)\n\t\t}\n\n\t\t// Fallback to old behavior if no provider\n\t\tif (!hasProvider || enhancedA11yMode) {\n\t\t\treturn (\n\t\t\t\t<_Tooltip.Root\n\t\t\t\t\tdelayDuration={delayDurationToUse}\n\t\t\t\t\tdisableHoverableContent={!enhancedA11yMode}\n\t\t\t\t>\n\t\t\t\t\t<_Tooltip.Trigger asChild ref={ref}>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</_Tooltip.Trigger>\n\t\t\t\t\t<_Tooltip.Content\n\t\t\t\t\t\tclassName=\"tlui-tooltip\"\n\t\t\t\t\t\tside={sideToUse}\n\t\t\t\t\t\tsideOffset={sideOffset}\n\t\t\t\t\t\tavoidCollisions\n\t\t\t\t\t\tcollisionPadding={8}\n\t\t\t\t\t\tdir=\"ltr\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{content}\n\t\t\t\t\t\t<_Tooltip.Arrow className=\"tlui-tooltip__arrow\" />\n\t\t\t\t\t</_Tooltip.Content>\n\t\t\t\t</_Tooltip.Root>\n\t\t\t)\n\t\t}\n\n\t\tconst child = React.Children.only(children)\n\t\tassert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')\n\n\t\tconst handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {\n\t\t\tchild.props.onMouseEnter?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'show',\n\t\t\t\ttooltip: {\n\t\t\t\t\tid: tooltipId.current,\n\t\t\t\t\tcontent,\n\t\t\t\t\ttargetElement: event.currentTarget as HTMLElement,\n\t\t\t\t\tside: sideToUse,\n\t\t\t\t\tsideOffset,\n\t\t\t\t\tshowOnMobile,\n\t\t\t\t\tdelayDuration: delayDurationToUse,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tconst handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {\n\t\t\tchild.props.onMouseLeave?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: tooltipId.current,\n\t\t\t\teditor,\n\t\t\t\tinstant: false,\n\t\t\t})\n\t\t}\n\n\t\tconst handleFocus = (event: React.FocusEvent<HTMLElement>) => {\n\t\t\tchild.props.onFocus?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'show',\n\t\t\t\ttooltip: {\n\t\t\t\t\tid: tooltipId.current,\n\t\t\t\t\tcontent,\n\t\t\t\t\ttargetElement: event.currentTarget as HTMLElement,\n\t\t\t\t\tside: sideToUse,\n\t\t\t\t\tsideOffset,\n\t\t\t\t\tshowOnMobile,\n\t\t\t\t\tdelayDuration: delayDurationToUse,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tconst handleBlur = (event: React.FocusEvent<HTMLElement>) => {\n\t\t\tchild.props.onBlur?.(event)\n\t\t\ttooltipManager.handleEvent({\n\t\t\t\ttype: 'hide',\n\t\t\t\ttooltipId: tooltipId.current,\n\t\t\t\teditor,\n\t\t\t\tinstant: false,\n\t\t\t})\n\t\t}\n\n\t\tconst childrenWithHandlers = React.cloneElement(children as React.ReactElement, {\n\t\t\tonMouseEnter: handleMouseEnter,\n\t\t\tonMouseLeave: handleMouseLeave,\n\t\t\tonFocus: handleFocus,\n\t\t\tonBlur: handleBlur,\n\t\t})\n\n\t\treturn childrenWithHandlers\n\t}\n)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "4.3.0-canary.35392ae6dc0d";
1
+ const version = "4.3.0-canary.37e6bf0fa8c6";
2
2
  const publishDates = {
3
3
  major: "2025-09-18T14:39:22.803Z",
4
- minor: "2025-12-05T09:46:38.266Z",
5
- patch: "2025-12-05T09:46:38.266Z"
4
+ minor: "2025-12-09T10:38:11.018Z",
5
+ patch: "2025-12-09T10:38:11.018Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/ui/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.3.0-canary.35392ae6dc0d'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2025-12-05T09:46:38.266Z',\n\tpatch: '2025-12-05T09:46:38.266Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.3.0-canary.37e6bf0fa8c6'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2025-12-09T10:38:11.018Z',\n\tpatch: '2025-12-09T10:38:11.018Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -4,9 +4,9 @@ import {
4
4
  generateJSON,
5
5
  generateText
6
6
  } from "@tiptap/core";
7
- import Code from "@tiptap/extension-code";
8
- import Highlight from "@tiptap/extension-highlight";
9
- import StarterKit from "@tiptap/starter-kit";
7
+ import { Code } from "@tiptap/extension-code";
8
+ import { Highlight } from "@tiptap/extension-highlight";
9
+ import { StarterKit } from "@tiptap/starter-kit";
10
10
  import {
11
11
  getOwnProperty,
12
12
  WeakCache
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/text/richText.ts"],
4
- "sourcesContent": ["import {\n\tExtension,\n\tExtensions,\n\tgenerateHTML,\n\tgenerateJSON,\n\tgenerateText,\n\tJSONContent,\n} from '@tiptap/core'\nimport Code from '@tiptap/extension-code'\nimport Highlight from '@tiptap/extension-highlight'\nimport { Node } from '@tiptap/pm/model'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n\tEditor,\n\tgetOwnProperty,\n\tRichTextFontVisitorState,\n\tTLFontFace,\n\tTLRichText,\n\tWeakCache,\n} from '@tldraw/editor'\nimport { DefaultFontFaces } from '../../shapes/shared/defaultFonts'\nimport { TextDirection } from './textDirection'\n\n/** @public */\nexport const KeyboardShiftEnterTweakExtension = Extension.create({\n\tname: 'keyboardShiftEnterHandler',\n\taddKeyboardShortcuts() {\n\t\treturn {\n\t\t\t// We don't support soft breaks, so we just use the default enter command.\n\t\t\t'Shift-Enter': ({ editor }) => editor.commands.enter(),\n\t\t}\n\t},\n})\n\n// We change the default Code to override what's in the StarterKit.\n// It allows for other attributes/extensions.\n// @ts-ignore this is fine.\nCode.config.excludes = undefined\n\n// We want the highlighting to take precedence over bolding/italics/links\n// as far as rendering is concerned. Otherwise, the highlighting\n// looks broken up.\nHighlight.config.priority = 1100\n\n/**\n * Default extensions for the TipTap editor.\n *\n * @public\n */\nexport const tipTapDefaultExtensions: Extensions = [\n\tStarterKit.configure({\n\t\tblockquote: false,\n\t\tcodeBlock: false,\n\t\thorizontalRule: false,\n\t\tlink: {\n\t\t\topenOnClick: false,\n\t\t\tautolink: true,\n\t\t},\n\t}),\n\tHighlight,\n\tKeyboardShiftEnterTweakExtension,\n\tTextDirection,\n]\n\n// todo: bust this if the editor changes, too\nconst htmlCache = new WeakCache<TLRichText, string>()\n\n/**\n * Renders HTML from a rich text string.\n *\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n * @public\n */\nexport function renderHtmlFromRichText(editor: Editor, richText: TLRichText) {\n\treturn htmlCache.get(richText, () => {\n\t\tconst tipTapExtensions =\n\t\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\t\tconst html = generateHTML(richText as JSONContent, tipTapExtensions)\n\t\t// We replace empty paragraphs with a single line break to prevent the browser from collapsing them.\n\t\treturn html.replaceAll('<p dir=\"auto\"></p>', '<p><br /></p>') ?? ''\n\t})\n}\n\n/**\n * Renders HTML from a rich text string for measurement.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderHtmlFromRichTextForMeasurement(editor: Editor, richText: TLRichText) {\n\tconst html = renderHtmlFromRichText(editor, richText)\n\treturn `<div class=\"tl-rich-text\">${html}</div>`\n}\n\n// A weak cache used to store plaintext that's been extracted from rich text.\nconst plainTextFromRichTextCache = new WeakCache<TLRichText, string>()\n\nexport function isEmptyRichText(richText: TLRichText) {\n\tif (richText.content.length === 1) {\n\t\tif (!(richText.content[0] as any).content) return true\n\t}\n\treturn false\n}\n\n/**\n * Renders plaintext from a rich text string.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderPlaintextFromRichText(editor: Editor, richText: TLRichText) {\n\tif (isEmptyRichText(richText)) return ''\n\n\treturn plainTextFromRichTextCache.get(richText, () => {\n\t\tconst tipTapExtensions =\n\t\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\t\treturn generateText(richText as JSONContent, tipTapExtensions, {\n\t\t\tblockSeparator: '\\n',\n\t\t})\n\t})\n}\n\n/**\n * Renders JSONContent from html.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderRichTextFromHTML(editor: Editor, html: string): TLRichText {\n\tconst tipTapExtensions =\n\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\treturn generateJSON(html, tipTapExtensions) as TLRichText\n}\n\n/** @public */\nexport function defaultAddFontsFromNode(\n\tnode: Node,\n\tstate: RichTextFontVisitorState,\n\taddFont: (font: TLFontFace) => void\n) {\n\tfor (const mark of node.marks) {\n\t\tif (mark.type.name === 'bold' && state.weight !== 'bold') {\n\t\t\tstate = { ...state, weight: 'bold' }\n\t\t}\n\t\tif (mark.type.name === 'italic' && state.style !== 'italic') {\n\t\t\tstate = { ...state, style: 'italic' }\n\t\t}\n\t\tif (mark.type.name === 'code' && state.family !== 'tldraw_mono') {\n\t\t\tstate = { ...state, family: 'tldraw_mono' }\n\t\t}\n\t}\n\n\tconst fontsForFamily = getOwnProperty(DefaultFontFaces, state.family)\n\tif (!fontsForFamily) return state\n\n\tconst fontsForStyle = getOwnProperty(fontsForFamily, state.style)\n\tif (!fontsForStyle) return state\n\n\tconst fontsForWeight = getOwnProperty(fontsForStyle, state.weight)\n\tif (!fontsForWeight) return state\n\n\taddFont(fontsForWeight)\n\n\treturn state\n}\n"],
5
- "mappings": "AAAA;AAAA,EACC;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,OAAO,UAAU;AACjB,OAAO,eAAe;AAEtB,OAAO,gBAAgB;AACvB;AAAA,EAEC;AAAA,EAIA;AAAA,OACM;AACP,SAAS,wBAAwB;AACjC,SAAS,qBAAqB;AAGvB,MAAM,mCAAmC,UAAU,OAAO;AAAA,EAChE,MAAM;AAAA,EACN,uBAAuB;AACtB,WAAO;AAAA;AAAA,MAEN,eAAe,CAAC,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM;AAAA,IACtD;AAAA,EACD;AACD,CAAC;AAKD,KAAK,OAAO,WAAW;AAKvB,UAAU,OAAO,WAAW;AAOrB,MAAM,0BAAsC;AAAA,EAClD,WAAW,UAAU;AAAA,IACpB,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,MAAM;AAAA,MACL,aAAa;AAAA,MACb,UAAU;AAAA,IACX;AAAA,EACD,CAAC;AAAA,EACD;AAAA,EACA;AAAA,EACA;AACD;AAGA,MAAM,YAAY,IAAI,UAA8B;AAU7C,SAAS,uBAAuB,QAAgB,UAAsB;AAC5E,SAAO,UAAU,IAAI,UAAU,MAAM;AACpC,UAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,UAAM,OAAO,aAAa,UAAyB,gBAAgB;AAEnE,WAAO,KAAK,WAAW,sBAAsB,eAAe,KAAK;AAAA,EAClE,CAAC;AACF;AAUO,SAAS,qCAAqC,QAAgB,UAAsB;AAC1F,QAAM,OAAO,uBAAuB,QAAQ,QAAQ;AACpD,SAAO,6BAA6B,IAAI;AACzC;AAGA,MAAM,6BAA6B,IAAI,UAA8B;AAE9D,SAAS,gBAAgB,UAAsB;AACrD,MAAI,SAAS,QAAQ,WAAW,GAAG;AAClC,QAAI,CAAE,SAAS,QAAQ,CAAC,EAAU,QAAS,QAAO;AAAA,EACnD;AACA,SAAO;AACR;AAUO,SAAS,4BAA4B,QAAgB,UAAsB;AACjF,MAAI,gBAAgB,QAAQ,EAAG,QAAO;AAEtC,SAAO,2BAA2B,IAAI,UAAU,MAAM;AACrD,UAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,WAAO,aAAa,UAAyB,kBAAkB;AAAA,MAC9D,gBAAgB;AAAA,IACjB,CAAC;AAAA,EACF,CAAC;AACF;AAUO,SAAS,uBAAuB,QAAgB,MAA0B;AAChF,QAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,SAAO,aAAa,MAAM,gBAAgB;AAC3C;AAGO,SAAS,wBACf,MACA,OACA,SACC;AACD,aAAW,QAAQ,KAAK,OAAO;AAC9B,QAAI,KAAK,KAAK,SAAS,UAAU,MAAM,WAAW,QAAQ;AACzD,cAAQ,EAAE,GAAG,OAAO,QAAQ,OAAO;AAAA,IACpC;AACA,QAAI,KAAK,KAAK,SAAS,YAAY,MAAM,UAAU,UAAU;AAC5D,cAAQ,EAAE,GAAG,OAAO,OAAO,SAAS;AAAA,IACrC;AACA,QAAI,KAAK,KAAK,SAAS,UAAU,MAAM,WAAW,eAAe;AAChE,cAAQ,EAAE,GAAG,OAAO,QAAQ,cAAc;AAAA,IAC3C;AAAA,EACD;AAEA,QAAM,iBAAiB,eAAe,kBAAkB,MAAM,MAAM;AACpE,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,gBAAgB,eAAe,gBAAgB,MAAM,KAAK;AAChE,MAAI,CAAC,cAAe,QAAO;AAE3B,QAAM,iBAAiB,eAAe,eAAe,MAAM,MAAM;AACjE,MAAI,CAAC,eAAgB,QAAO;AAE5B,UAAQ,cAAc;AAEtB,SAAO;AACR;",
4
+ "sourcesContent": ["import {\n\tExtension,\n\tExtensions,\n\tgenerateHTML,\n\tgenerateJSON,\n\tgenerateText,\n\tJSONContent,\n} from '@tiptap/core'\nimport { Code } from '@tiptap/extension-code'\nimport { Highlight } from '@tiptap/extension-highlight'\nimport { Node } from '@tiptap/pm/model'\nimport { StarterKit } from '@tiptap/starter-kit'\nimport {\n\tEditor,\n\tgetOwnProperty,\n\tRichTextFontVisitorState,\n\tTLFontFace,\n\tTLRichText,\n\tWeakCache,\n} from '@tldraw/editor'\nimport { DefaultFontFaces } from '../../shapes/shared/defaultFonts'\nimport { TextDirection } from './textDirection'\n\n/** @public */\nexport const KeyboardShiftEnterTweakExtension = Extension.create({\n\tname: 'keyboardShiftEnterHandler',\n\taddKeyboardShortcuts() {\n\t\treturn {\n\t\t\t// We don't support soft breaks, so we just use the default enter command.\n\t\t\t'Shift-Enter': ({ editor }) => editor.commands.enter(),\n\t\t}\n\t},\n})\n\n// We change the default Code to override what's in the StarterKit.\n// It allows for other attributes/extensions.\n// @ts-ignore this is fine.\nCode.config.excludes = undefined\n\n// We want the highlighting to take precedence over bolding/italics/links\n// as far as rendering is concerned. Otherwise, the highlighting\n// looks broken up.\nHighlight.config.priority = 1100\n\n/**\n * Default extensions for the TipTap editor.\n *\n * @public\n */\nexport const tipTapDefaultExtensions: Extensions = [\n\tStarterKit.configure({\n\t\tblockquote: false,\n\t\tcodeBlock: false,\n\t\thorizontalRule: false,\n\t\tlink: {\n\t\t\topenOnClick: false,\n\t\t\tautolink: true,\n\t\t},\n\t}),\n\tHighlight,\n\tKeyboardShiftEnterTweakExtension,\n\tTextDirection,\n]\n\n// todo: bust this if the editor changes, too\nconst htmlCache = new WeakCache<TLRichText, string>()\n\n/**\n * Renders HTML from a rich text string.\n *\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n * @public\n */\nexport function renderHtmlFromRichText(editor: Editor, richText: TLRichText) {\n\treturn htmlCache.get(richText, () => {\n\t\tconst tipTapExtensions =\n\t\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\t\tconst html = generateHTML(richText as JSONContent, tipTapExtensions)\n\t\t// We replace empty paragraphs with a single line break to prevent the browser from collapsing them.\n\t\treturn html.replaceAll('<p dir=\"auto\"></p>', '<p><br /></p>') ?? ''\n\t})\n}\n\n/**\n * Renders HTML from a rich text string for measurement.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderHtmlFromRichTextForMeasurement(editor: Editor, richText: TLRichText) {\n\tconst html = renderHtmlFromRichText(editor, richText)\n\treturn `<div class=\"tl-rich-text\">${html}</div>`\n}\n\n// A weak cache used to store plaintext that's been extracted from rich text.\nconst plainTextFromRichTextCache = new WeakCache<TLRichText, string>()\n\nexport function isEmptyRichText(richText: TLRichText) {\n\tif (richText.content.length === 1) {\n\t\tif (!(richText.content[0] as any).content) return true\n\t}\n\treturn false\n}\n\n/**\n * Renders plaintext from a rich text string.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderPlaintextFromRichText(editor: Editor, richText: TLRichText) {\n\tif (isEmptyRichText(richText)) return ''\n\n\treturn plainTextFromRichTextCache.get(richText, () => {\n\t\tconst tipTapExtensions =\n\t\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\t\treturn generateText(richText as JSONContent, tipTapExtensions, {\n\t\t\tblockSeparator: '\\n',\n\t\t})\n\t})\n}\n\n/**\n * Renders JSONContent from html.\n * @param editor - The editor instance.\n * @param richText - The rich text content.\n *\n *\n * @public\n */\nexport function renderRichTextFromHTML(editor: Editor, html: string): TLRichText {\n\tconst tipTapExtensions =\n\t\teditor.getTextOptions().tipTapConfig?.extensions ?? tipTapDefaultExtensions\n\treturn generateJSON(html, tipTapExtensions) as TLRichText\n}\n\n/** @public */\nexport function defaultAddFontsFromNode(\n\tnode: Node,\n\tstate: RichTextFontVisitorState,\n\taddFont: (font: TLFontFace) => void\n) {\n\tfor (const mark of node.marks) {\n\t\tif (mark.type.name === 'bold' && state.weight !== 'bold') {\n\t\t\tstate = { ...state, weight: 'bold' }\n\t\t}\n\t\tif (mark.type.name === 'italic' && state.style !== 'italic') {\n\t\t\tstate = { ...state, style: 'italic' }\n\t\t}\n\t\tif (mark.type.name === 'code' && state.family !== 'tldraw_mono') {\n\t\t\tstate = { ...state, family: 'tldraw_mono' }\n\t\t}\n\t}\n\n\tconst fontsForFamily = getOwnProperty(DefaultFontFaces, state.family)\n\tif (!fontsForFamily) return state\n\n\tconst fontsForStyle = getOwnProperty(fontsForFamily, state.style)\n\tif (!fontsForStyle) return state\n\n\tconst fontsForWeight = getOwnProperty(fontsForStyle, state.weight)\n\tif (!fontsForWeight) return state\n\n\taddFont(fontsForWeight)\n\n\treturn state\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACC;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,SAAS,kBAAkB;AAC3B;AAAA,EAEC;AAAA,EAIA;AAAA,OACM;AACP,SAAS,wBAAwB;AACjC,SAAS,qBAAqB;AAGvB,MAAM,mCAAmC,UAAU,OAAO;AAAA,EAChE,MAAM;AAAA,EACN,uBAAuB;AACtB,WAAO;AAAA;AAAA,MAEN,eAAe,CAAC,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM;AAAA,IACtD;AAAA,EACD;AACD,CAAC;AAKD,KAAK,OAAO,WAAW;AAKvB,UAAU,OAAO,WAAW;AAOrB,MAAM,0BAAsC;AAAA,EAClD,WAAW,UAAU;AAAA,IACpB,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,MAAM;AAAA,MACL,aAAa;AAAA,MACb,UAAU;AAAA,IACX;AAAA,EACD,CAAC;AAAA,EACD;AAAA,EACA;AAAA,EACA;AACD;AAGA,MAAM,YAAY,IAAI,UAA8B;AAU7C,SAAS,uBAAuB,QAAgB,UAAsB;AAC5E,SAAO,UAAU,IAAI,UAAU,MAAM;AACpC,UAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,UAAM,OAAO,aAAa,UAAyB,gBAAgB;AAEnE,WAAO,KAAK,WAAW,sBAAsB,eAAe,KAAK;AAAA,EAClE,CAAC;AACF;AAUO,SAAS,qCAAqC,QAAgB,UAAsB;AAC1F,QAAM,OAAO,uBAAuB,QAAQ,QAAQ;AACpD,SAAO,6BAA6B,IAAI;AACzC;AAGA,MAAM,6BAA6B,IAAI,UAA8B;AAE9D,SAAS,gBAAgB,UAAsB;AACrD,MAAI,SAAS,QAAQ,WAAW,GAAG;AAClC,QAAI,CAAE,SAAS,QAAQ,CAAC,EAAU,QAAS,QAAO;AAAA,EACnD;AACA,SAAO;AACR;AAUO,SAAS,4BAA4B,QAAgB,UAAsB;AACjF,MAAI,gBAAgB,QAAQ,EAAG,QAAO;AAEtC,SAAO,2BAA2B,IAAI,UAAU,MAAM;AACrD,UAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,WAAO,aAAa,UAAyB,kBAAkB;AAAA,MAC9D,gBAAgB;AAAA,IACjB,CAAC;AAAA,EACF,CAAC;AACF;AAUO,SAAS,uBAAuB,QAAgB,MAA0B;AAChF,QAAM,mBACL,OAAO,eAAe,EAAE,cAAc,cAAc;AACrD,SAAO,aAAa,MAAM,gBAAgB;AAC3C;AAGO,SAAS,wBACf,MACA,OACA,SACC;AACD,aAAW,QAAQ,KAAK,OAAO;AAC9B,QAAI,KAAK,KAAK,SAAS,UAAU,MAAM,WAAW,QAAQ;AACzD,cAAQ,EAAE,GAAG,OAAO,QAAQ,OAAO;AAAA,IACpC;AACA,QAAI,KAAK,KAAK,SAAS,YAAY,MAAM,UAAU,UAAU;AAC5D,cAAQ,EAAE,GAAG,OAAO,OAAO,SAAS;AAAA,IACrC;AACA,QAAI,KAAK,KAAK,SAAS,UAAU,MAAM,WAAW,eAAe;AAChE,cAAQ,EAAE,GAAG,OAAO,QAAQ,cAAc;AAAA,IAC3C;AAAA,EACD;AAEA,QAAM,iBAAiB,eAAe,kBAAkB,MAAM,MAAM;AACpE,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,gBAAgB,eAAe,gBAAgB,MAAM,KAAK;AAChE,MAAI,CAAC,cAAe,QAAO;AAE3B,QAAM,iBAAiB,eAAe,eAAe,MAAM,MAAM;AACjE,MAAI,CAAC,eAAgB,QAAO;AAE5B,UAAQ,cAAc;AAEtB,SAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tldraw",
3
3
  "description": "A tiny little drawing editor.",
4
- "version": "4.3.0-canary.35392ae6dc0d",
4
+ "version": "4.3.0-canary.37e6bf0fa8c6",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -62,8 +62,8 @@
62
62
  "@tiptap/pm": "^3.6.2",
63
63
  "@tiptap/react": "^3.6.2",
64
64
  "@tiptap/starter-kit": "^3.6.2",
65
- "@tldraw/editor": "4.3.0-canary.35392ae6dc0d",
66
- "@tldraw/store": "4.3.0-canary.35392ae6dc0d",
65
+ "@tldraw/editor": "4.3.0-canary.37e6bf0fa8c6",
66
+ "@tldraw/store": "4.3.0-canary.37e6bf0fa8c6",
67
67
  "classnames": "^2.5.1",
68
68
  "hotkeys-js": "^3.13.9",
69
69
  "idb": "^7.1.1",
@@ -63,7 +63,7 @@ export const TldrawSelectionForeground = track(function TldrawSelectionForegroun
63
63
 
64
64
  if (onlyShape && editor.isShapeHidden(onlyShape)) return null
65
65
 
66
- const zoom = editor.getZoomLevel()
66
+ const zoom = editor.getEfficientZoomLevel()
67
67
  const isChangingStyle = editor.getInstanceState().isChangingStyle
68
68
 
69
69
  const width = expandedBounds.width
@@ -532,7 +532,7 @@ export const MobileRotateHandle = function RotateHandle({
532
532
  const events = useSelectionEvents('mobile_rotate')
533
533
 
534
534
  const editor = useEditor()
535
- const zoom = useValue('zoom level', () => editor.getZoomLevel(), [editor])
535
+ const zoom = useValue('zoom level', () => editor.getEfficientZoomLevel(), [editor])
536
536
  const bgRadius = Math.max(14 * (1 / zoom), 20 / Math.max(1, zoom))
537
537
  const msg = useTranslation()
538
538
  return (
@@ -45,7 +45,6 @@ import {
45
45
  useEditor,
46
46
  useIsEditing,
47
47
  useSharedSafeId,
48
- useValue,
49
48
  } from '@tldraw/editor'
50
49
  import React, { useMemo } from 'react'
51
50
  import { updateArrowTerminal } from '../../bindings/arrow/ArrowBindingUtil'
@@ -56,6 +55,7 @@ import { ShapeFill } from '../shared/ShapeFill'
56
55
  import { ARROW_LABEL_PADDING, STROKE_SIZES, TEXT_PROPS } from '../shared/default-shape-constants'
57
56
  import { getFillDefForCanvas, getFillDefForExport } from '../shared/defaultStyleDefs'
58
57
  import { useDefaultColorTheme } from '../shared/useDefaultColorTheme'
58
+ import { useEfficientZoomThreshold } from '../shared/useEfficientZoomThreshold'
59
59
  import { getArrowBodyPath, getArrowHandlePath } from './ArrowPath'
60
60
  import { ArrowShapeOptions } from './arrow-types'
61
61
  import {
@@ -120,6 +120,8 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
120
120
 
121
121
  shouldBeExact: (editor: Editor) => editor.inputs.altKey,
122
122
  shouldIgnoreTargets: (editor: Editor) => editor.inputs.ctrlKey,
123
+
124
+ showTextOutline: true,
123
125
  }
124
126
 
125
127
  override canEdit() {
@@ -269,7 +271,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
269
271
 
270
272
  const segmentStart = shapePageTransform.applyToPoint(info.route.midpointHandle.segmentStart)
271
273
  const segmentEnd = shapePageTransform.applyToPoint(info.route.midpointHandle.segmentEnd)
272
- const segmentLength = Vec.Dist(segmentStart, segmentEnd) * this.editor.getZoomLevel()
274
+ const segmentLength = Vec.Dist(segmentStart, segmentEnd) * this.editor.getEfficientZoomLevel()
273
275
 
274
276
  if (segmentLength > this.options.elbowMinSegmentLengthToShowMidpointHandle) {
275
277
  handles.push({
@@ -369,7 +371,8 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
369
371
 
370
372
  // we want to snap to certain points. the maximum distance at which a snap will occur is
371
373
  // relative to the zoom level:
372
- const maxSnapDistance = this.options.elbowMidpointSnapDistance / this.editor.getZoomLevel()
374
+ const maxSnapDistance =
375
+ this.options.elbowMidpointSnapDistance / this.editor.getEfficientZoomLevel()
373
376
 
374
377
  // we snap to the midpoint of the range by default
375
378
  const midPoint = perpDistanceToLineAngle(
@@ -794,6 +797,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
794
797
  textWidth={labelPosition.box.w - ARROW_LABEL_PADDING * 2 * shape.props.scale}
795
798
  isSelected={isSelected}
796
799
  padding={0}
800
+ showTextOutline={this.options.showTextOutline}
797
801
  style={{
798
802
  transform: `translate(${labelPosition.box.center.x}px, ${labelPosition.box.center.y}px)`,
799
803
  }}
@@ -944,7 +948,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
944
948
  .box.clone()
945
949
  .expandBy(-ARROW_LABEL_PADDING * shape.props.scale)}
946
950
  padding={0}
947
- showTextOutline={true}
951
+ showTextOutline={this.options.showTextOutline}
948
952
  />
949
953
  </g>
950
954
  )
@@ -1005,13 +1009,7 @@ const ArrowSvg = track(function ArrowSvg({
1005
1009
  const editor = useEditor()
1006
1010
  const theme = useDefaultColorTheme()
1007
1011
  const info = getArrowInfo(editor, shape)
1008
- const isForceSolid = useValue(
1009
- 'force solid',
1010
- () => {
1011
- return editor.getZoomLevel() < 0.2
1012
- },
1013
- [editor]
1014
- )
1012
+ const isForceSolid = useEfficientZoomThreshold(shape.props.scale * 0.25)
1015
1013
  const clipPathId = useSharedSafeId(shape.id + '_clip')
1016
1014
  const arrowheadDotId = useSharedSafeId('arrowhead-dot')
1017
1015
  const arrowheadCrossId = useSharedSafeId('arrowhead-cross')
@@ -1037,7 +1035,7 @@ const ArrowSvg = track(function ArrowSvg({
1037
1035
  start: 'skip',
1038
1036
  end: 'skip',
1039
1037
  lengthRatio: 2.5,
1040
- strokeWidth: 2 / editor.getZoomLevel(),
1038
+ strokeWidth: 2 / editor.getEfficientZoomLevel(),
1041
1039
  props: {
1042
1040
  className: 'tl-arrow-hint',
1043
1041
  markerStart: bindings.start
@@ -95,6 +95,8 @@ export interface ArrowShapeOptions {
95
95
  * When creating an arrow, should it bind to the target shape.
96
96
  */
97
97
  shouldIgnoreTargets(editor: Editor): boolean
98
+ /** Whether to show the outline of the arrow shape's label (using the same color as the canvas). This helps with overlapping shapes. It does not show up on Safari, where text outline is a performance issues. */
99
+ readonly showTextOutline: boolean
98
100
  }
99
101
 
100
102
  /** @public */
@@ -139,7 +139,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
139
139
  const forceSolid = useValue(
140
140
  'force solid',
141
141
  () => {
142
- const zoomLevel = this.editor.getZoomLevel()
142
+ const zoomLevel = this.editor.getEfficientZoomLevel()
143
143
  return zoomLevel < 0.5 && zoomLevel < 1.5 / sw
144
144
  },
145
145
  [this.editor, sw]
@@ -244,7 +244,7 @@ function DrawShapeSvg({ shape, zoomOverride }: { shape: TLDrawShape; zoomOverrid
244
244
  const forceSolid = useValue(
245
245
  'force solid',
246
246
  () => {
247
- const zoomLevel = zoomOverride ?? editor.getZoomLevel()
247
+ const zoomLevel = zoomOverride ?? editor.getEfficientZoomLevel()
248
248
  return zoomLevel < 0.5 && zoomLevel < 1.5 / sw
249
249
  },
250
250
  [editor, sw, zoomOverride]
@@ -253,7 +253,7 @@ function DrawShapeSvg({ shape, zoomOverride }: { shape: TLDrawShape; zoomOverrid
253
253
  const dotAdjustment = useValue(
254
254
  'dot adjustment',
255
255
  () => {
256
- const zoomLevel = zoomOverride ?? editor.getZoomLevel()
256
+ const zoomLevel = zoomOverride ?? editor.getEfficientZoomLevel()
257
257
  // If we're zoomed way out (10%), then we need to make the dotted line go to 9 instead 0.1
258
258
  // Chrome doesn't render anything otherwise.
259
259
  return zoomLevel < 0.2 ? 0 : 0.1
@@ -115,7 +115,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
115
115
  override getGeometry(shape: TLFrameShape): Geometry2d {
116
116
  const { editor } = this
117
117
 
118
- const z = editor.getZoomLevel()
118
+ const z = editor.getEfficientZoomLevel()
119
119
 
120
120
  // Which dimension measures the top edge after rotation?
121
121
  const labelSide = getFrameHeadingSide(editor, shape)
@@ -43,6 +43,7 @@ import {
43
43
  import { getFillDefForCanvas, getFillDefForExport } from '../shared/defaultStyleDefs'
44
44
  import { useDefaultColorTheme } from '../shared/useDefaultColorTheme'
45
45
  import { useIsReadyForEditing } from '../shared/useEditablePlainText'
46
+ import { useEfficientZoomThreshold } from '../shared/useEfficientZoomThreshold'
46
47
  import { GeoShapeBody } from './components/GeoShapeBody'
47
48
  import { getGeoShapePath } from './getGeoShapePath'
48
49
 
@@ -54,6 +55,10 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
54
55
  static override props = geoShapeProps
55
56
  static override migrations = geoShapeMigrations
56
57
 
58
+ override options = {
59
+ showTextOutline: true,
60
+ }
61
+
57
62
  override canEdit() {
58
63
  return true
59
64
  }
@@ -195,7 +200,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
195
200
  const isReadyForEditing = useIsReadyForEditing(editor, shape.id)
196
201
  const isEmpty = isEmptyRichText(shape.props.richText)
197
202
  const showHtmlContainer = isReadyForEditing || !isEmpty
198
- const isForceSolid = useValue('force solid', () => editor.getZoomLevel() < 0.2, [editor])
203
+ const isForceSolid = useEfficientZoomThreshold(shape.props.scale * 0.25)
199
204
 
200
205
  return (
201
206
  <>
@@ -224,6 +229,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
224
229
  isSelected={isOnlySelected}
225
230
  labelColor={getColorValue(theme, props.labelColor, 'solid')}
226
231
  wrap
232
+ showTextOutline={this.options.showTextOutline}
227
233
  />
228
234
  </HTMLContainer>
229
235
  )}
@@ -233,9 +239,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
233
239
  }
234
240
 
235
241
  indicator(shape: TLGeoShape) {
236
- const isZoomedOut = useValue('isZoomedOut', () => this.editor.getZoomLevel() < 0.25, [
237
- this.editor,
238
- ])
242
+ const isZoomedOut = useEfficientZoomThreshold(shape.props.scale * 0.25)
239
243
 
240
244
  const { size, dash, scale } = shape.props
241
245
  const strokeWidth = STROKE_SIZES[size]
@@ -283,6 +287,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
283
287
  labelColor={getColorValue(theme, props.labelColor, 'solid')}
284
288
  bounds={bounds}
285
289
  padding={LABEL_PADDING}
290
+ showTextOutline={this.options.showTextOutline}
286
291
  />
287
292
  )
288
293
  }
@@ -315,7 +315,7 @@ function useHighlightForceSolid(editor: Editor, shape: TLHighlightShape) {
315
315
  'forceSolid',
316
316
  () => {
317
317
  const sw = getStrokeWidth(shape)
318
- const zoomLevel = editor.getZoomLevel()
318
+ const zoomLevel = editor.getEfficientZoomLevel()
319
319
  if (sw / zoomLevel < 1.5) {
320
320
  return true
321
321
  }
@@ -50,6 +50,7 @@ import {
50
50
  } from '../shared/default-shape-constants'
51
51
  import { useDefaultColorTheme } from '../shared/useDefaultColorTheme'
52
52
  import { useIsReadyForEditing } from '../shared/useEditablePlainText'
53
+ import { useEfficientZoomThreshold } from '../shared/useEfficientZoomThreshold'
53
54
  import {
54
55
  CLONE_HANDLE_MARGIN,
55
56
  NOTE_CENTER_OFFSET,
@@ -158,7 +159,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
158
159
  const isCoarsePointer = this.editor.getInstanceState().isCoarsePointer
159
160
  if (isCoarsePointer) return []
160
161
 
161
- const zoom = this.editor.getZoomLevel()
162
+ const zoom = this.editor.getEfficientZoomLevel()
162
163
  if (zoom * scale < 0.25) return []
163
164
 
164
165
  const nh = getNoteHeight(shape)
@@ -268,15 +269,12 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
268
269
  [this.editor]
269
270
  )
270
271
 
271
- // todo: consider hiding shadows on dark mode if they're invisible anyway
272
-
273
- const hideShadows = useValue('zoom', () => this.editor.getZoomLevel() < 0.35 / scale, [
274
- scale,
275
- this.editor,
276
- ])
277
-
278
272
  const isDarkMode = useValue('dark mode', () => this.editor.user.getIsDarkMode(), [this.editor])
279
273
 
274
+ // Shadows are hidden when zoomed out far enough or in dark mode
275
+ let hideShadows = useEfficientZoomThreshold(scale * 0.25)
276
+ if (isDarkMode) hideShadows = true
277
+
280
278
  const isSelected = shape.id === this.editor.getOnlySelectedShapeId()
281
279
 
282
280
  const isReadyForEditing = useIsReadyForEditing(this.editor, shape.id)
@@ -318,6 +316,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
318
316
  wrap
319
317
  padding={LABEL_PADDING * scale}
320
318
  hasCustomTabBehavior
319
+ showTextOutline={false}
321
320
  onKeyDown={handleKeyDown}
322
321
  />
323
322
  )}
@@ -1,13 +1,14 @@
1
- import { useEditor, useValue } from '@tldraw/editor'
1
+ import { useEditor } from '@tldraw/editor'
2
2
  import classNames from 'classnames'
3
3
  import { PointerEventHandler, useCallback } from 'react'
4
+ import { useEfficientZoomThreshold } from './useEfficientZoomThreshold'
4
5
 
5
6
  const LINK_ICON =
6
7
  "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none'%3E%3Cpath stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13 5H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6M19 5h6m0 0v6m0-6L13 17'/%3E%3C/svg%3E"
7
8
 
8
9
  export function HyperlinkButton({ url }: { url: string }) {
9
10
  const editor = useEditor()
10
- const hideButton = useValue('zoomLevel', () => editor.getZoomLevel() < 0.32, [editor])
11
+ const hideButton = useEfficientZoomThreshold()
11
12
  const markAsHandledOnShiftKey = useCallback<PointerEventHandler>(
12
13
  (e) => {
13
14
  if (!editor.inputs.shiftKey) editor.markEventAsHandled(e)
@@ -7,6 +7,7 @@ import {
7
7
  TLDefaultVerticalAlignStyle,
8
8
  TLShapeId,
9
9
  } from '@tldraw/editor'
10
+ import classNames from 'classnames'
10
11
  import React from 'react'
11
12
  import { PlainTextArea } from '../text/PlainTextArea'
12
13
  import { TextHelpers } from './TextHelpers'
@@ -34,6 +35,7 @@ export interface PlainTextLabelProps {
34
35
  textWidth?: number
35
36
  textHeight?: number
36
37
  padding?: number
38
+ showTextOutline?: boolean
37
39
  }
38
40
 
39
41
  /**
@@ -61,6 +63,7 @@ export const PlainTextLabel = React.memo(function PlainTextLabel({
61
63
  style,
62
64
  textWidth,
63
65
  textHeight,
66
+ showTextOutline = true,
64
67
  }: PlainTextLabelProps) {
65
68
  const { rInput, isEmpty, isEditing, isReadyForEditing, ...editableTextRest } =
66
69
  useEditablePlainText(shapeId, type, plaintext)
@@ -109,7 +112,13 @@ export const PlainTextLabel = React.memo(function PlainTextLabel({
109
112
  height: textHeight ? Math.ceil(textHeight) : undefined,
110
113
  }}
111
114
  >
112
- <div className={`${cssPrefix} tl-text tl-text-content`} dir="auto">
115
+ <div
116
+ className={classNames(
117
+ `${cssPrefix} tl-text tl-text-content`,
118
+ showTextOutline ? 'tl-text__outline' : 'tl-text__no-outline'
119
+ )}
120
+ dir="auto"
121
+ >
113
122
  {finalPlainText.split('\n').map((lineOfText, index) => (
114
123
  <div key={index} dir="auto">
115
124
  {lineOfText}
@@ -15,6 +15,7 @@ import {
15
15
  useReactor,
16
16
  useValue,
17
17
  } from '@tldraw/editor'
18
+ import classNames from 'classnames'
18
19
  import React, { useMemo } from 'react'
19
20
  import { renderHtmlFromRichText } from '../../utils/text/richText'
20
21
  import { RichTextArea } from '../text/RichTextArea'
@@ -44,6 +45,7 @@ export interface RichTextLabelProps {
44
45
  textHeight?: number
45
46
  padding?: number
46
47
  hasCustomTabBehavior?: boolean
48
+ showTextOutline?: boolean
47
49
  }
48
50
 
49
51
  /**
@@ -72,6 +74,7 @@ export const RichTextLabel = React.memo(function RichTextLabel({
72
74
  textWidth,
73
75
  textHeight,
74
76
  hasCustomTabBehavior,
77
+ showTextOutline = true,
75
78
  }: RichTextLabelProps) {
76
79
  const editor = useEditor()
77
80
  const isDragging = React.useRef(false)
@@ -129,7 +132,10 @@ export const RichTextLabel = React.memo(function RichTextLabel({
129
132
  const cssPrefix = classNamePrefix || 'tl-text'
130
133
  return (
131
134
  <div
132
- className={`${cssPrefix}-label tl-text-wrapper tl-rich-text-wrapper`}
135
+ className={classNames(
136
+ `${cssPrefix}-label tl-text-wrapper tl-rich-text-wrapper`,
137
+ showTextOutline ? 'tl-text__outline' : 'tl-text__no-outline'
138
+ )}
133
139
  aria-hidden={!isEditing}
134
140
  data-font={font}
135
141
  data-align={align}
@@ -259,7 +265,10 @@ export function RichTextSVG({
259
265
  y={bounds.minY}
260
266
  width={bounds.w}
261
267
  height={bounds.h}
262
- className="tl-export-embed-styles tl-rich-text tl-rich-text-svg"
268
+ className={classNames(
269
+ 'tl-export-embed-styles tl-rich-text tl-rich-text-svg',
270
+ showTextOutline ? 'tl-text__outline' : 'tl-text__no-outline'
271
+ )}
263
272
  >
264
273
  <div style={wrapperStyle}>
265
274
  <div dangerouslySetInnerHTML={{ __html: html }} style={style} />
@@ -50,10 +50,10 @@ export const ShapeFill = React.memo(function ShapeFill({
50
50
  export function PatternFill({ d, color, theme }: ShapeFillProps) {
51
51
  const editor = useEditor()
52
52
  const svgExport = useSvgExportContext()
53
- const zoomLevel = useValue('zoomLevel', () => editor.getZoomLevel(), [editor])
53
+ const zoomLevel = useValue('zoomLevel', () => editor.getEfficientZoomLevel(), [editor])
54
54
  const getHashPatternZoomName = useGetHashPatternZoomName()
55
55
 
56
- const teenyTiny = editor.getZoomLevel() <= 0.18
56
+ const teenyTiny = zoomLevel <= 0.18
57
57
 
58
58
  return (
59
59
  <>
@@ -0,0 +1,10 @@
1
+ import { useEditor, useValue } from '@tldraw/editor'
2
+
3
+ /** Returns true when zoomed out far enough that shapes should render in a simplified "solid" style. */
4
+ export function useEfficientZoomThreshold(threshold = 0.25) {
5
+ const editor = useEditor()
6
+ return useValue('efficient zoom threshold', () => editor.getEfficientZoomLevel() < threshold, [
7
+ editor,
8
+ threshold,
9
+ ])
10
+ }
@@ -96,7 +96,7 @@ export function useImageOrVideoAsset({ shapeId, assetId, width }: UseImageOrVide
96
96
 
97
97
  const screenScale = exportInfo
98
98
  ? exportInfo.scale * (width / asset.props.w)
99
- : editor.getZoomLevel() * (width / asset.props.w)
99
+ : editor.getEfficientZoomLevel() * (width / asset.props.w)
100
100
 
101
101
  function resolve(asset: TLImageAsset | TLVideoAsset, url: string | null) {
102
102
  if (isCancelled) return // don't update if the hook has remounted