@tldiagram/core-ui 1.87.0

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 (272) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/api/client.d.ts +143 -0
  3. package/dist/api/transport-vscode.d.ts +8 -0
  4. package/dist/api/transport.d.ts +1 -0
  5. package/dist/components/CodePreviewPanel-vscode.d.ts +7 -0
  6. package/dist/components/CodePreviewPanel.d.ts +9 -0
  7. package/dist/components/ConfirmDialog.d.ts +12 -0
  8. package/dist/components/ConnectorPanel.d.ts +21 -0
  9. package/dist/components/ContextBoundaryElement.d.ts +11 -0
  10. package/dist/components/ContextNeighborElement.d.ts +29 -0
  11. package/dist/components/ContextStraightConnector.d.ts +4 -0
  12. package/dist/components/CrossBranchControls.d.ts +9 -0
  13. package/dist/components/DependenciesOnboarding.d.ts +5 -0
  14. package/dist/components/DrawingCanvas.d.ts +39 -0
  15. package/dist/components/ElementLibrary-vscode.d.ts +7 -0
  16. package/dist/components/ElementLibrary.d.ts +22 -0
  17. package/dist/components/ElementNode.d.ts +36 -0
  18. package/dist/components/ElementPanel.d.ts +25 -0
  19. package/dist/components/ExploreOnboarding.d.ts +5 -0
  20. package/dist/components/ExplorePageOnboarding.d.ts +5 -0
  21. package/dist/components/ExportModal.d.ts +16 -0
  22. package/dist/components/FloatingEdge.d.ts +9 -0
  23. package/dist/components/GitSourceLinker.d.ts +8 -0
  24. package/dist/components/HeaderContext.d.ts +16 -0
  25. package/dist/components/Icons.d.ts +95 -0
  26. package/dist/components/ImportModal.d.ts +10 -0
  27. package/dist/components/InlineElementAdder.d.ts +17 -0
  28. package/dist/components/LayoutSection.d.ts +7 -0
  29. package/dist/components/LocalSourceLinker.d.ts +8 -0
  30. package/dist/components/MiniZoomOnboarding.d.ts +5 -0
  31. package/dist/components/NavBreadcrumb.d.ts +6 -0
  32. package/dist/components/NodeBody.d.ts +12 -0
  33. package/dist/components/NodeContainer.d.ts +8 -0
  34. package/dist/components/NodeHoverCard.d.ts +10 -0
  35. package/dist/components/PanelHeader.d.ts +8 -0
  36. package/dist/components/PanelUI.d.ts +3 -0
  37. package/dist/components/ProxyConnectorEdge.d.ts +4 -0
  38. package/dist/components/ProxyConnectorPanel.d.ts +9 -0
  39. package/dist/components/SafeBackground.d.ts +13 -0
  40. package/dist/components/ScrollIndicatorWrapper.d.ts +8 -0
  41. package/dist/components/SetChildModal.d.ts +10 -0
  42. package/dist/components/SetParentModal.d.ts +10 -0
  43. package/dist/components/SlidingPanel.d.ts +16 -0
  44. package/dist/components/TagUpsert.d.ts +8 -0
  45. package/dist/components/TopMenuBar.d.ts +8 -0
  46. package/dist/components/ViewBezierConnector.d.ts +4 -0
  47. package/dist/components/ViewDrawMenu.d.ts +22 -0
  48. package/dist/components/ViewEditorEdgeLabelLayout.d.ts +16 -0
  49. package/dist/components/ViewEditorOnboarding.d.ts +5 -0
  50. package/dist/components/ViewExplorer/TagManager/ColorPicker.d.ts +7 -0
  51. package/dist/components/ViewExplorer/TagManager/GroupNamingPopover.d.ts +10 -0
  52. package/dist/components/ViewExplorer/TagManager/LayerItem.d.ts +27 -0
  53. package/dist/components/ViewExplorer/TagManager/TagItem.d.ts +25 -0
  54. package/dist/components/ViewExplorer/TagManager/index.d.ts +21 -0
  55. package/dist/components/ViewExplorer/ViewNavigator.d.ts +11 -0
  56. package/dist/components/ViewExplorer/ViewSearch.d.ts +8 -0
  57. package/dist/components/ViewExplorer/ViewTree.d.ts +18 -0
  58. package/dist/components/ViewExplorer/index.d.ts +31 -0
  59. package/dist/components/ViewExplorer/types.d.ts +11 -0
  60. package/dist/components/ViewExplorer/utils.d.ts +6 -0
  61. package/dist/components/ViewExplorer-vscode.d.ts +6 -0
  62. package/dist/components/ViewFloatingMenu-vscode.d.ts +27 -0
  63. package/dist/components/ViewFloatingMenu.d.ts +39 -0
  64. package/dist/components/ViewGridNode.d.ts +29 -0
  65. package/dist/components/ViewHeaderButton.d.ts +11 -0
  66. package/dist/components/ViewPanel.d.ts +18 -0
  67. package/dist/components/ViewsGridOnboarding.d.ts +5 -0
  68. package/dist/components/ZUI/ZUICanvas.d.ts +18 -0
  69. package/dist/components/ZUI/index.d.ts +2 -0
  70. package/dist/components/ZUI/layout.d.ts +18 -0
  71. package/dist/components/ZUI/proxy.d.ts +25 -0
  72. package/dist/components/ZUI/renderer.d.ts +30 -0
  73. package/dist/components/ZUI/types.d.ts +140 -0
  74. package/dist/components/ZUI/useZUIInteraction.d.ts +21 -0
  75. package/dist/config/runtime-vscode.d.ts +22 -0
  76. package/dist/config/runtime.d.ts +5 -0
  77. package/dist/constants/colors.d.ts +27 -0
  78. package/dist/constants/diagramColors.d.ts +1 -0
  79. package/dist/context/ThemeContext.d.ts +27 -0
  80. package/dist/crossBranch/graph.d.ts +13 -0
  81. package/dist/crossBranch/resolve.d.ts +22 -0
  82. package/dist/crossBranch/settings.d.ts +6 -0
  83. package/dist/crossBranch/store.d.ts +11 -0
  84. package/dist/crossBranch/types.d.ts +96 -0
  85. package/dist/demo/DemoPage.d.ts +9 -0
  86. package/dist/demo/seed.d.ts +9 -0
  87. package/dist/demo/store.d.ts +137 -0
  88. package/dist/demo/viewEditor.d.ts +26 -0
  89. package/dist/favicon.svg +35 -0
  90. package/dist/hooks/useSafeFitView.d.ts +16 -0
  91. package/dist/index.css +1 -0
  92. package/dist/index.d.ts +115 -0
  93. package/dist/index.js +19966 -0
  94. package/dist/lib/vscodeBridge-vscode.d.ts +13 -0
  95. package/dist/lib/vscodeBridge.d.ts +5 -0
  96. package/dist/logo-120.png +0 -0
  97. package/dist/logo-bw.png +0 -0
  98. package/dist/logo-bw.svg +15 -0
  99. package/dist/logo-text.svg +51 -0
  100. package/dist/logo.svg +35 -0
  101. package/dist/pages/AppearanceSettings.d.ts +3 -0
  102. package/dist/pages/Dependencies.d.ts +1 -0
  103. package/dist/pages/InfiniteZoom.d.ts +7 -0
  104. package/dist/pages/Settings.d.ts +7 -0
  105. package/dist/pages/ViewEditor/components/EditorMenus.d.ts +24 -0
  106. package/dist/pages/ViewEditor/components/EditorOverlays.d.ts +30 -0
  107. package/dist/pages/ViewEditor/components/EmptyCanvasState.d.ts +7 -0
  108. package/dist/pages/ViewEditor/context.d.ts +13 -0
  109. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +201 -0
  110. package/dist/pages/ViewEditor/hooks/useDrawingEngine.d.ts +40 -0
  111. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +20 -0
  112. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +74 -0
  113. package/dist/pages/ViewEditor/index.d.ts +8 -0
  114. package/dist/pages/ViewEditor/utils.d.ts +14 -0
  115. package/dist/pages/Views.d.ts +6 -0
  116. package/dist/pages/ViewsGrid.d.ts +6 -0
  117. package/dist/pkg/importer/mermaid.d.ts +7 -0
  118. package/dist/pkg/importer/mermaid.test.d.ts +1 -0
  119. package/dist/platform/PlatformContext.d.ts +6 -0
  120. package/dist/platform/context.d.ts +3 -0
  121. package/dist/platform/local.d.ts +2 -0
  122. package/dist/platform/types.d.ts +17 -0
  123. package/dist/slots.d.ts +67 -0
  124. package/dist/theme.d.ts +2 -0
  125. package/dist/types/index.d.ts +193 -0
  126. package/dist/types/vscode-messages.d.ts +60 -0
  127. package/dist/utils/edgeDistribution.d.ts +34 -0
  128. package/dist/utils/githubApi.d.ts +4 -0
  129. package/dist/utils/githubCache.d.ts +17 -0
  130. package/dist/utils/ids.d.ts +2 -0
  131. package/dist/utils/technologyCatalog.d.ts +15 -0
  132. package/dist/utils/toast.d.ts +15 -0
  133. package/dist/utils/treesitter.d.ts +13 -0
  134. package/dist/utils/url.d.ts +12 -0
  135. package/package.json +159 -0
  136. package/src/App.tsx +141 -0
  137. package/src/api/client.ts +618 -0
  138. package/src/api/transport-vscode.ts +28 -0
  139. package/src/api/transport.ts +7 -0
  140. package/src/assets/logo-mark.svg +31 -0
  141. package/src/assets/logo-wordmark.svg +22 -0
  142. package/src/assets/logo.svg +35 -0
  143. package/src/components/CodePreviewPanel-vscode.tsx +85 -0
  144. package/src/components/CodePreviewPanel.tsx +384 -0
  145. package/src/components/ConfirmDialog.tsx +66 -0
  146. package/src/components/ConnectorPanel.tsx +403 -0
  147. package/src/components/ContextBoundaryElement.tsx +35 -0
  148. package/src/components/ContextNeighborElement.tsx +282 -0
  149. package/src/components/ContextStraightConnector.tsx +144 -0
  150. package/src/components/CrossBranchControls.tsx +105 -0
  151. package/src/components/DependenciesOnboarding.tsx +427 -0
  152. package/src/components/DrawingCanvas.tsx +391 -0
  153. package/src/components/ElementLibrary-vscode.tsx +9 -0
  154. package/src/components/ElementLibrary.tsx +512 -0
  155. package/src/components/ElementNode.tsx +1033 -0
  156. package/src/components/ElementPanel.tsx +928 -0
  157. package/src/components/ExploreOnboarding.tsx +347 -0
  158. package/src/components/ExplorePageOnboarding.tsx +383 -0
  159. package/src/components/ExportModal.tsx +132 -0
  160. package/src/components/FloatingEdge.tsx +115 -0
  161. package/src/components/GitSourceLinker.tsx +1053 -0
  162. package/src/components/HeaderContext.tsx +30 -0
  163. package/src/components/Icons.tsx +245 -0
  164. package/src/components/ImportModal.tsx +219 -0
  165. package/src/components/InlineElementAdder.tsx +216 -0
  166. package/src/components/LayoutSection.tsx +624 -0
  167. package/src/components/LocalSourceLinker.tsx +330 -0
  168. package/src/components/MiniZoomOnboarding.tsx +78 -0
  169. package/src/components/NavBreadcrumb.tsx +24 -0
  170. package/src/components/NodeBody.tsx +89 -0
  171. package/src/components/NodeContainer.tsx +58 -0
  172. package/src/components/NodeHoverCard.tsx +135 -0
  173. package/src/components/PanelHeader.tsx +36 -0
  174. package/src/components/PanelUI.tsx +24 -0
  175. package/src/components/ProxyConnectorEdge.tsx +169 -0
  176. package/src/components/ProxyConnectorPanel.tsx +130 -0
  177. package/src/components/SafeBackground.tsx +19 -0
  178. package/src/components/ScrollIndicatorWrapper.tsx +117 -0
  179. package/src/components/SetChildModal.tsx +191 -0
  180. package/src/components/SetParentModal.tsx +187 -0
  181. package/src/components/SlidingPanel.tsx +114 -0
  182. package/src/components/TagUpsert.tsx +142 -0
  183. package/src/components/TopMenuBar.tsx +380 -0
  184. package/src/components/ViewBezierConnector.tsx +143 -0
  185. package/src/components/ViewDrawMenu.tsx +270 -0
  186. package/src/components/ViewEditorEdgeLabelLayout.ts +189 -0
  187. package/src/components/ViewEditorOnboarding.tsx +445 -0
  188. package/src/components/ViewExplorer/TagManager/ColorPicker.tsx +49 -0
  189. package/src/components/ViewExplorer/TagManager/GroupNamingPopover.tsx +96 -0
  190. package/src/components/ViewExplorer/TagManager/LayerItem.tsx +228 -0
  191. package/src/components/ViewExplorer/TagManager/TagItem.tsx +242 -0
  192. package/src/components/ViewExplorer/TagManager/index.tsx +418 -0
  193. package/src/components/ViewExplorer/ViewNavigator.tsx +121 -0
  194. package/src/components/ViewExplorer/ViewSearch.tsx +33 -0
  195. package/src/components/ViewExplorer/ViewTree.tsx +98 -0
  196. package/src/components/ViewExplorer/index.tsx +384 -0
  197. package/src/components/ViewExplorer/types.ts +13 -0
  198. package/src/components/ViewExplorer/utils.ts +56 -0
  199. package/src/components/ViewExplorer-vscode.tsx +8 -0
  200. package/src/components/ViewFloatingMenu-vscode.tsx +248 -0
  201. package/src/components/ViewFloatingMenu.tsx +379 -0
  202. package/src/components/ViewGridNode.tsx +451 -0
  203. package/src/components/ViewHeaderButton.tsx +60 -0
  204. package/src/components/ViewPanel.tsx +162 -0
  205. package/src/components/ViewsGridOnboarding.tsx +400 -0
  206. package/src/components/ZUI/ZUICanvas.tsx +853 -0
  207. package/src/components/ZUI/index.ts +3 -0
  208. package/src/components/ZUI/layout.ts +323 -0
  209. package/src/components/ZUI/proxy.ts +278 -0
  210. package/src/components/ZUI/renderer.ts +1189 -0
  211. package/src/components/ZUI/types.ts +150 -0
  212. package/src/components/ZUI/useZUIInteraction.ts +720 -0
  213. package/src/config/runtime-vscode.ts +46 -0
  214. package/src/config/runtime.ts +30 -0
  215. package/src/constants/colors.ts +80 -0
  216. package/src/constants/diagramColors.ts +9 -0
  217. package/src/context/ThemeContext.tsx +158 -0
  218. package/src/crossBranch/graph.ts +207 -0
  219. package/src/crossBranch/resolve.ts +643 -0
  220. package/src/crossBranch/settings.ts +59 -0
  221. package/src/crossBranch/store.ts +71 -0
  222. package/src/crossBranch/types.ts +102 -0
  223. package/src/demo/DemoPage.tsx +184 -0
  224. package/src/demo/seed.ts +67 -0
  225. package/src/demo/store.ts +536 -0
  226. package/src/demo/viewEditor.ts +110 -0
  227. package/src/hooks/useSafeFitView.ts +60 -0
  228. package/src/index.css +309 -0
  229. package/src/index.ts +184 -0
  230. package/src/kafka-ss.png +0 -0
  231. package/src/lib/vscodeBridge-vscode.ts +27 -0
  232. package/src/lib/vscodeBridge.ts +7 -0
  233. package/src/main.tsx +46 -0
  234. package/src/pages/AppearanceSettings.tsx +135 -0
  235. package/src/pages/Dependencies.tsx +926 -0
  236. package/src/pages/InfiniteZoom.tsx +404 -0
  237. package/src/pages/Settings.tsx +91 -0
  238. package/src/pages/ViewEditor/EDGE_DISTRIBUTION.md +64 -0
  239. package/src/pages/ViewEditor/components/EditorMenus.tsx +112 -0
  240. package/src/pages/ViewEditor/components/EditorOverlays.tsx +172 -0
  241. package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +42 -0
  242. package/src/pages/ViewEditor/context.tsx +21 -0
  243. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +1349 -0
  244. package/src/pages/ViewEditor/hooks/useDrawingEngine.ts +127 -0
  245. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +501 -0
  246. package/src/pages/ViewEditor/hooks/useViewData.ts +491 -0
  247. package/src/pages/ViewEditor/index.tsx +1366 -0
  248. package/src/pages/ViewEditor/utils.ts +88 -0
  249. package/src/pages/Views.tsx +171 -0
  250. package/src/pages/ViewsGrid.tsx +1310 -0
  251. package/src/pkg/importer/mermaid.test.ts +141 -0
  252. package/src/pkg/importer/mermaid.ts +76 -0
  253. package/src/platform/PlatformContext.tsx +17 -0
  254. package/src/platform/context.ts +9 -0
  255. package/src/platform/local.tsx +15 -0
  256. package/src/platform/types.ts +19 -0
  257. package/src/slots.ts +92 -0
  258. package/src/styles/editor-panels.css +66 -0
  259. package/src/styles/theme.css +56 -0
  260. package/src/theme.ts +336 -0
  261. package/src/types/index.ts +234 -0
  262. package/src/types/offline-ambient.d.ts +14 -0
  263. package/src/types/vscode-messages.ts +32 -0
  264. package/src/utils/edgeDistribution.ts +103 -0
  265. package/src/utils/githubApi.ts +121 -0
  266. package/src/utils/githubCache.ts +108 -0
  267. package/src/utils/ids.ts +9 -0
  268. package/src/utils/technologyCatalog.ts +143 -0
  269. package/src/utils/toast.ts +100 -0
  270. package/src/utils/treesitter.ts +147 -0
  271. package/src/utils/url.ts +72 -0
  272. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,391 @@
1
+ import React, { useRef, useEffect, useCallback, forwardRef, useImperativeHandle, useState } from 'react'
2
+ import { useReactFlow } from 'reactflow'
3
+ import { ACCENT_DEFAULT } from '../constants/colors'
4
+
5
+ export type DrawingPoint = { x: number; y: number }
6
+
7
+ export type DrawingPath = {
8
+ id: string
9
+ points: DrawingPoint[]
10
+ color: string
11
+ width: number
12
+ text?: string
13
+ fontSize?: number
14
+ }
15
+
16
+ interface DrawingCanvasProps {
17
+ paths: DrawingPath[]
18
+ isDrawing: boolean
19
+ isVisible: boolean
20
+ strokeColor?: string
21
+ strokeWidth?: number
22
+ mode?: 'pencil' | 'eraser' | 'text' | 'select'
23
+ onPathComplete: (path: DrawingPath) => void
24
+ onPathDelete?: (pathId: string) => void
25
+ onPathUpdate?: (path: DrawingPath) => void
26
+ onTextPositionSelected?: (canvasX: number, canvasY: number, flowX: number, flowY: number) => void
27
+ }
28
+
29
+ export interface DrawingCanvasHandle {
30
+ /** Imperatively update the viewport and redraw avoids React re-renders on every pan frame. */
31
+ notifyViewportChange: (vp: { x: number; y: number; zoom: number }) => void
32
+ }
33
+
34
+ /**
35
+ * A premium free-drawing overlay with smooth ink-like strokes, hit detection for move/erase,
36
+ * and a broad eraser brush.
37
+ */
38
+ const DrawingCanvas = forwardRef<DrawingCanvasHandle, DrawingCanvasProps>(function DrawingCanvas({
39
+ paths,
40
+ isDrawing,
41
+ isVisible,
42
+ strokeColor = ACCENT_DEFAULT,
43
+ strokeWidth = 3,
44
+ mode = 'pencil',
45
+ onPathComplete,
46
+ onPathDelete,
47
+ onPathUpdate,
48
+ onTextPositionSelected,
49
+ }: DrawingCanvasProps, ref: React.ForwardedRef<DrawingCanvasHandle>) {
50
+ const { getViewport } = useReactFlow()
51
+ const canvasRef = useRef<HTMLCanvasElement>(null)
52
+
53
+ // Internal selection state (not lifted to parent to keep drag smooth)
54
+ const [selectedPathId, setSelectedPathId] = useState<string | null>(null)
55
+
56
+ // Refs for values needed inside async/event callbacks without stale closure issues
57
+ const viewportRef = useRef(getViewport())
58
+ const pathsRef = useRef(paths)
59
+ const isVisibleRef = useRef(isVisible)
60
+ const strokeColorRef = useRef(strokeColor)
61
+ const strokeWidthRef = useRef(strokeWidth)
62
+ const modeRef = useRef(mode)
63
+ const currentPathRef = useRef<DrawingPoint[]>([])
64
+ const isPointerDownRef = useRef(false)
65
+ const dragStartRef = useRef<{ x: number, y: number } | null>(null)
66
+ const pathCloneRef = useRef<DrawingPath | null>(null)
67
+
68
+ useEffect(() => { pathsRef.current = paths }, [paths])
69
+ useEffect(() => { isVisibleRef.current = isVisible }, [isVisible])
70
+ useEffect(() => { strokeColorRef.current = strokeColor }, [strokeColor])
71
+ useEffect(() => { strokeWidthRef.current = strokeWidth }, [strokeWidth])
72
+ useEffect(() => { modeRef.current = mode }, [mode])
73
+
74
+ // Helper: Draw a smooth Catmull-Rom spline on a canvas context
75
+ const drawSpline = useCallback((ctx: CanvasRenderingContext2D, points: DrawingPoint[], tension = 0.5) => {
76
+ if (points.length < 2) return
77
+
78
+ ctx.beginPath()
79
+ ctx.moveTo(points[0].x, points[0].y)
80
+
81
+ if (points.length === 2) {
82
+ ctx.lineTo(points[1].x, points[1].y)
83
+ ctx.stroke()
84
+ return
85
+ }
86
+
87
+ for (let i = 0; i < points.length - 1; i++) {
88
+ const p0 = i > 0 ? points[i - 1] : points[i]
89
+ const p1 = points[i]
90
+ const p2 = points[i + 1]
91
+ const p3 = i < points.length - 2 ? points[i + 2] : p2
92
+
93
+ const cp1x = p1.x + (p2.x - p0.x) / 6 * tension
94
+ const cp1y = p1.y + (p2.y - p0.y) / 6 * tension
95
+ const cp2x = p2.x - (p3.x - p1.x) / 6 * tension
96
+ const cp2y = p2.y - (p3.y - p1.y) / 6 * tension
97
+
98
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y)
99
+ }
100
+ ctx.stroke()
101
+ }, [])
102
+
103
+ const getPathBounds = (path: DrawingPath) => {
104
+ if (path.points.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 }
105
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
106
+ for (const p of path.points) {
107
+ if (p.x < minX) minX = p.x
108
+ if (p.y < minY) minY = p.y
109
+ if (p.x > maxX) maxX = p.x
110
+ if (p.y > maxY) maxY = p.y
111
+ }
112
+ const pad = (path.width || 10) / 2 + 2
113
+ return { minX: minX - pad, minY: minY - pad, maxX: maxX + pad, maxY: maxY + pad }
114
+ }
115
+
116
+ // ── Redraw all committed paths ────────────────────────────────────────────
117
+ const redraw = useCallback(() => {
118
+ const canvas = canvasRef.current
119
+ if (!canvas) return
120
+ const ctx = canvas.getContext('2d')
121
+ if (!ctx) return
122
+
123
+ const vp = viewportRef.current
124
+ const committed = pathsRef.current
125
+ const visible = isVisibleRef.current
126
+
127
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
128
+ if (!visible) return
129
+
130
+ ctx.save()
131
+ ctx.setTransform(vp.zoom, 0, 0, vp.zoom, vp.x, vp.y)
132
+
133
+ for (const path of committed) {
134
+ if (path.points.length === 0) continue
135
+
136
+ if (path.text) {
137
+ ctx.fillStyle = path.color
138
+ ctx.font = `${path.fontSize || 16}px Josefin Sans, sans-serif`
139
+ ctx.textBaseline = 'middle'
140
+ ctx.fillText(path.text, path.points[0].x, path.points[0].y)
141
+ } else {
142
+ ctx.strokeStyle = path.color
143
+ ctx.lineWidth = path.width
144
+ ctx.lineCap = 'round'
145
+ ctx.lineJoin = 'round'
146
+
147
+ if (path.points.length === 1) {
148
+ ctx.beginPath()
149
+ ctx.arc(path.points[0].x, path.points[0].y, path.width / 2, 0, Math.PI * 2)
150
+ ctx.fillStyle = path.color
151
+ ctx.fill()
152
+ } else {
153
+ drawSpline(ctx, path.points)
154
+ }
155
+ }
156
+
157
+ // Selection halo
158
+ if (path.id === selectedPathId) {
159
+ const bounds = getPathBounds(path)
160
+ ctx.setLineDash([5, 5])
161
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'
162
+ ctx.lineWidth = 1 / vp.zoom
163
+ ctx.strokeRect(bounds.minX, bounds.minY, bounds.maxX - bounds.minX, bounds.maxY - bounds.minY)
164
+ ctx.setLineDash([])
165
+ }
166
+ }
167
+
168
+ // Active drawing path
169
+ const activePts = currentPathRef.current
170
+ if (activePts.length > 0) {
171
+ ctx.strokeStyle = strokeColorRef.current
172
+ ctx.lineWidth = strokeWidthRef.current
173
+ ctx.lineCap = 'round'
174
+ ctx.lineJoin = 'round'
175
+ if (activePts.length === 1) {
176
+ ctx.beginPath()
177
+ ctx.arc(activePts[0].x, activePts[0].y, strokeWidthRef.current / 2, 0, Math.PI * 2)
178
+ ctx.fillStyle = strokeColorRef.current
179
+ ctx.fill()
180
+ } else {
181
+ drawSpline(ctx, activePts)
182
+ }
183
+ }
184
+
185
+ ctx.restore()
186
+ }, [selectedPathId, drawSpline])
187
+
188
+ useImperativeHandle(ref, () => ({
189
+ notifyViewportChange(vp) {
190
+ viewportRef.current = vp
191
+ redraw()
192
+ },
193
+ }), [redraw])
194
+
195
+ useEffect(() => {
196
+ redraw()
197
+ }, [paths, isVisible, redraw])
198
+
199
+ useEffect(() => {
200
+ const canvas = canvasRef.current
201
+ const parent = canvas?.parentElement
202
+ if (!canvas || !parent) return
203
+
204
+ const ro = new ResizeObserver(() => {
205
+ const rect = parent.getBoundingClientRect()
206
+ canvas.width = Math.round(rect.width)
207
+ canvas.height = Math.round(rect.height)
208
+ redraw()
209
+ })
210
+ ro.observe(parent)
211
+
212
+ const rect = parent.getBoundingClientRect()
213
+ canvas.width = Math.round(rect.width)
214
+ canvas.height = Math.round(rect.height)
215
+
216
+ return () => ro.disconnect()
217
+ }, [redraw])
218
+
219
+ const screenToFlow = useCallback((sx: number, sy: number): DrawingPoint => {
220
+ const { x, y, zoom } = viewportRef.current
221
+ return { x: (sx - x) / zoom, y: (sy - y) / zoom }
222
+ }, [])
223
+
224
+ function distToSegment(p: DrawingPoint, v: DrawingPoint, w: DrawingPoint) {
225
+ const l2 = Math.pow(v.x - w.x, 2) + Math.pow(v.y - w.y, 2)
226
+ if (l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y)
227
+ let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2
228
+ t = Math.max(0, Math.min(1, t))
229
+ return Math.hypot(p.x - (v.x + t * (w.x - v.x)), p.y - (v.y + t * (w.y - v.y)))
230
+ }
231
+
232
+ const findPathAt = useCallback((flowPt: DrawingPoint) => {
233
+ // Reverse to find top-most
234
+ return [...pathsRef.current].reverse().find((path) => {
235
+ if (path.text) {
236
+ const dist = Math.hypot(path.points[0].x - flowPt.x, path.points[0].y - flowPt.y)
237
+ return dist < (path.fontSize || 16)
238
+ }
239
+ for (let i = 0; i < path.points.length - 1; i++) {
240
+ if (distToSegment(flowPt, path.points[i], path.points[i + 1]) < path.width + 10) return true
241
+ }
242
+ return path.points.length === 1 && Math.hypot(path.points[0].x - flowPt.x, path.points[0].y - flowPt.y) < path.width + 10
243
+ })
244
+ }, [])
245
+
246
+ const onPointerDown = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
247
+ if (!isDrawing) return
248
+ e.preventDefault()
249
+ e.stopPropagation()
250
+
251
+ const canvas = canvasRef.current
252
+ if (!canvas) return
253
+ canvas.setPointerCapture(e.pointerId)
254
+
255
+ const rect = canvas.getBoundingClientRect()
256
+ const flowPt = screenToFlow(e.clientX - rect.left, e.clientY - rect.top)
257
+
258
+ if (modeRef.current === 'eraser') {
259
+ isPointerDownRef.current = true
260
+ const hit = findPathAt(flowPt)
261
+ if (hit && onPathDelete) onPathDelete(hit.id)
262
+ return
263
+ }
264
+
265
+ if (modeRef.current === 'select') {
266
+ const hit = findPathAt(flowPt)
267
+ if (hit) {
268
+ setSelectedPathId(hit.id)
269
+ isPointerDownRef.current = true
270
+ dragStartRef.current = flowPt
271
+ pathCloneRef.current = { ...hit, points: hit.points.map(p => ({ ...p })) }
272
+ } else {
273
+ setSelectedPathId(null)
274
+ }
275
+ return
276
+ }
277
+
278
+ if (modeRef.current === 'text') {
279
+ onTextPositionSelected?.(e.clientX - rect.left, e.clientY - rect.top, flowPt.x, flowPt.y)
280
+ return
281
+ }
282
+
283
+ isPointerDownRef.current = true
284
+ currentPathRef.current = [flowPt]
285
+ }, [isDrawing, screenToFlow, findPathAt, onPathDelete, onTextPositionSelected])
286
+
287
+ const onPointerMove = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
288
+ if (!isDrawing || !isPointerDownRef.current) return
289
+ e.preventDefault()
290
+
291
+ const canvas = canvasRef.current
292
+ if (!canvas) return
293
+ const ctx = canvas.getContext('2d')
294
+ if (!ctx) return
295
+
296
+ const rect = canvas.getBoundingClientRect()
297
+ const flowPt = screenToFlow(e.clientX - rect.left, e.clientY - rect.top)
298
+
299
+ if (modeRef.current === 'eraser') {
300
+ const hit = findPathAt(flowPt)
301
+ if (hit && onPathDelete) onPathDelete(hit.id)
302
+ return
303
+ }
304
+
305
+ if (modeRef.current === 'select' && selectedPathId && dragStartRef.current && pathCloneRef.current) {
306
+ const dx = flowPt.x - dragStartRef.current.x
307
+ const dy = flowPt.y - dragStartRef.current.y
308
+
309
+ const updatedPath = {
310
+ ...pathCloneRef.current,
311
+ points: pathCloneRef.current.points.map(p => ({ x: p.x + dx, y: p.y + dy }))
312
+ }
313
+
314
+ // Local update for smoothness
315
+ pathsRef.current = pathsRef.current.map(p => p.id === selectedPathId ? updatedPath : p)
316
+ redraw()
317
+ return
318
+ }
319
+
320
+ if (modeRef.current === 'pencil') {
321
+ currentPathRef.current.push(flowPt)
322
+ redraw()
323
+ }
324
+ }, [isDrawing, screenToFlow, redraw, selectedPathId, findPathAt, onPathDelete])
325
+
326
+ const onPointerUp = useCallback(() => {
327
+ if (!isPointerDownRef.current) return
328
+ isPointerDownRef.current = false
329
+
330
+ if (modeRef.current === 'select' && selectedPathId) {
331
+ const finalPath = pathsRef.current.find(p => p.id === selectedPathId)
332
+ if (finalPath && onPathUpdate) onPathUpdate(finalPath)
333
+ dragStartRef.current = null
334
+ pathCloneRef.current = null
335
+ return
336
+ }
337
+
338
+ const pts = [...currentPathRef.current]
339
+ currentPathRef.current = []
340
+
341
+ if (pts.length >= 1 && modeRef.current === 'pencil') {
342
+ onPathComplete({
343
+ id: `path-${Date.now()}-${Math.random().toString(36).slice(2)}`,
344
+ points: pts,
345
+ color: strokeColorRef.current,
346
+ width: strokeWidthRef.current,
347
+ })
348
+ }
349
+ }, [onPathComplete, onPathUpdate, selectedPathId])
350
+
351
+ // Delete key handler
352
+ useEffect(() => {
353
+ const handleKeyDown = (e: KeyboardEvent) => {
354
+ if (!isDrawing) return
355
+ if ((e.key === 'Delete' || e.key === 'Backspace') && selectedPathId) {
356
+ onPathDelete?.(selectedPathId)
357
+ setSelectedPathId(null)
358
+ }
359
+ }
360
+ window.addEventListener('keydown', handleKeyDown)
361
+ return () => window.removeEventListener('keydown', handleKeyDown)
362
+ }, [selectedPathId, isDrawing, onPathDelete])
363
+
364
+ return (
365
+ <canvas
366
+ ref={canvasRef}
367
+ style={{
368
+ position: 'absolute',
369
+ top: 0,
370
+ left: 0,
371
+ width: '100%',
372
+ height: '100%',
373
+ pointerEvents: isDrawing ? 'auto' : 'none',
374
+ cursor: isDrawing ? (
375
+ mode === 'text' ? 'text' :
376
+ mode === 'eraser' ? 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'24\' height=\'24\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'white\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'%3E%3Cpath d=\'M20 20H7L3 16C2 15 2 13 3 12L13 2L22 11L20 13L17 10L10 17L13 20\'/%3E%3C/svg%3E") 6 18, auto' :
377
+ mode === 'select' ? 'move' : 'crosshair'
378
+ ) : 'default',
379
+ opacity: isVisible ? 1 : 0,
380
+ transition: 'opacity 0.15s ease',
381
+ zIndex: 10,
382
+ }}
383
+ onPointerDown={onPointerDown}
384
+ onPointerMove={onPointerMove}
385
+ onPointerUp={onPointerUp}
386
+ onPointerCancel={onPointerUp}
387
+ />
388
+ )
389
+ })
390
+
391
+ export default DrawingCanvas
@@ -0,0 +1,9 @@
1
+ /**
2
+ * VS Code variant of ElementLibrary.
3
+ * The element library is surfaced as a native VS Code tree view instead.
4
+ * This stub prevents the web component (with its drag/touch/scroll logic) from
5
+ * being bundled into the webview build.
6
+ */
7
+ export default function ElementLibrary() {
8
+ return null
9
+ }