@zendir/ui 0.5.0 → 0.6.1

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 (391) hide show
  1. package/CHANGELOG.md +419 -386
  2. package/README.md +640 -631
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/index.js +17 -221
  5. package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
  6. package/dist/react/3d/CesiumCaptureSource.js +364 -283
  7. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  8. package/dist/react/3d/ZenSpace3D.d.ts +2 -3
  9. package/dist/react/3d/ZenSpace3D.js +1681 -1237
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  11. package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
  12. package/dist/react/3d/ZenSpace3DCesium.js +513 -560
  13. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  14. package/dist/react/3d/ZenSpace3DShaders.js +61 -66
  15. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  16. package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
  17. package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
  18. package/dist/react/3d/ZenSpace3DUtils.js +37 -24
  19. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  20. package/dist/react/3d/capturePngAnalysis.js +42 -36
  21. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  22. package/dist/react/3d/index.d.ts +3 -3
  23. package/dist/react/3d/index.js +5 -53
  24. package/dist/react/3d/threeLoader.d.ts +1 -1
  25. package/dist/react/3d/threeLoader.js +23 -18
  26. package/dist/react/3d/threeLoader.js.map +1 -1
  27. package/dist/react/astro/ClassificationBanner.d.ts +2 -3
  28. package/dist/react/astro/ClassificationBanner.js +95 -79
  29. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  30. package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
  31. package/dist/react/astro/GlobalStatusBar.js +157 -163
  32. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  33. package/dist/react/astro/MissionClock.d.ts +7 -8
  34. package/dist/react/astro/MissionClock.js +377 -400
  35. package/dist/react/astro/MissionClock.js.map +1 -1
  36. package/dist/react/astro/MonitoringIcon.d.ts +1 -2
  37. package/dist/react/astro/MonitoringIcon.js +422 -358
  38. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  39. package/dist/react/astro/Notification.d.ts +1 -2
  40. package/dist/react/astro/Notification.js +229 -126
  41. package/dist/react/astro/Notification.js.map +1 -1
  42. package/dist/react/astro/Progress.d.ts +0 -1
  43. package/dist/react/astro/Progress.js +199 -141
  44. package/dist/react/astro/Progress.js.map +1 -1
  45. package/dist/react/astro/SimulationControls.d.ts +3 -4
  46. package/dist/react/astro/SimulationControls.js +596 -650
  47. package/dist/react/astro/SimulationControls.js.map +1 -1
  48. package/dist/react/astro/StatusIndicator.d.ts +0 -1
  49. package/dist/react/astro/StatusIndicator.js +236 -170
  50. package/dist/react/astro/StatusIndicator.js.map +1 -1
  51. package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
  52. package/dist/react/astro/UnifiedTimeline.js +2345 -2392
  53. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  54. package/dist/react/astro/index.d.ts +5 -5
  55. package/dist/react/astro/index.js +1 -25
  56. package/dist/react/cards/AccessCard.d.ts +0 -1
  57. package/dist/react/cards/AccessCard.js +471 -405
  58. package/dist/react/cards/AccessCard.js.map +1 -1
  59. package/dist/react/cards/OrbitCard.d.ts +0 -1
  60. package/dist/react/cards/OrbitCard.js +487 -364
  61. package/dist/react/cards/OrbitCard.js.map +1 -1
  62. package/dist/react/cards/SpacecraftCard.d.ts +0 -1
  63. package/dist/react/cards/SpacecraftCard.js +813 -925
  64. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  65. package/dist/react/cards/TelemetryCard.d.ts +0 -1
  66. package/dist/react/cards/TelemetryCard.js +1055 -732
  67. package/dist/react/cards/TelemetryCard.js.map +1 -1
  68. package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
  69. package/dist/react/cards/TelemetryStreamCard.js +323 -297
  70. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  71. package/dist/react/cards/index.d.ts +1 -1
  72. package/dist/react/cards/index.js +1 -8
  73. package/dist/react/charts/GroundTrackMap.d.ts +6 -7
  74. package/dist/react/charts/GroundTrackMap.js +1377 -1256
  75. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  76. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
  77. package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
  78. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  79. package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
  80. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  81. package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
  82. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  83. package/dist/react/charts/index.d.ts +1 -1
  84. package/dist/react/charts/index.js +7 -100
  85. package/dist/react/charts/unified/AstroChart.d.ts +1 -2
  86. package/dist/react/charts/unified/AstroChart.js +1331 -1411
  87. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  88. package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
  89. package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
  90. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  91. package/dist/react/charts/unified/domain.d.ts +12 -13
  92. package/dist/react/charts/unified/domain.js +3999 -3145
  93. package/dist/react/charts/unified/domain.js.map +1 -1
  94. package/dist/react/charts/unified/generators.d.ts +3 -4
  95. package/dist/react/charts/unified/generators.js +561 -498
  96. package/dist/react/charts/unified/generators.js.map +1 -1
  97. package/dist/react/charts/unified/index.d.ts +1 -1
  98. package/dist/react/charts/unified/presets.d.ts +14 -15
  99. package/dist/react/charts/unified/presets.js +857 -994
  100. package/dist/react/charts/unified/presets.js.map +1 -1
  101. package/dist/react/charts/unified/sync.d.ts +2 -3
  102. package/dist/react/charts/unified/sync.js +229 -195
  103. package/dist/react/charts/unified/sync.js.map +1 -1
  104. package/dist/react/charts/unified/theme.d.ts +11 -12
  105. package/dist/react/charts/unified/theme.js +606 -642
  106. package/dist/react/charts/unified/theme.js.map +1 -1
  107. package/dist/react/charts/unified/types.d.ts +32 -33
  108. package/dist/react/charts/unified/useChartStream.d.ts +1 -2
  109. package/dist/react/charts/unified/useChartStream.js +203 -216
  110. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  111. package/dist/react/chatgpt/AppCard.d.ts +1 -2
  112. package/dist/react/chatgpt/index.d.ts +7 -7
  113. package/dist/react/chatgpt/index.js +160 -139
  114. package/dist/react/chatgpt/index.js.map +1 -1
  115. package/dist/react/context/CategoryContext.d.ts +0 -1
  116. package/dist/react/context/CategoryContext.js +78 -29
  117. package/dist/react/context/CategoryContext.js.map +1 -1
  118. package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
  119. package/dist/react/context/DisplaySettingsContext.js +205 -156
  120. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  121. package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
  122. package/dist/react/context/SpatialSelectionContext.js +36 -7
  123. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  124. package/dist/react/core/data/DataTable.d.ts +3 -4
  125. package/dist/react/core/data/DataTable.js +473 -440
  126. package/dist/react/core/data/DataTable.js.map +1 -1
  127. package/dist/react/core/data/DataValue.d.ts +4 -5
  128. package/dist/react/core/data/DataValue.js +605 -571
  129. package/dist/react/core/data/DataValue.js.map +1 -1
  130. package/dist/react/core/data/propertyConfig.d.ts +1 -2
  131. package/dist/react/core/data/propertyConfig.js +446 -382
  132. package/dist/react/core/data/propertyConfig.js.map +1 -1
  133. package/dist/react/core/display/AstroIcon.d.ts +4 -5
  134. package/dist/react/core/display/AstroIcon.js +339 -429
  135. package/dist/react/core/display/AstroIcon.js.map +1 -1
  136. package/dist/react/core/display/Badge.d.ts +2 -3
  137. package/dist/react/core/display/Badge.js +131 -131
  138. package/dist/react/core/display/Badge.js.map +1 -1
  139. package/dist/react/core/display/CardHeader.d.ts +0 -1
  140. package/dist/react/core/display/CardHeader.js +398 -311
  141. package/dist/react/core/display/CardHeader.js.map +1 -1
  142. package/dist/react/core/display/Container.d.ts +2 -3
  143. package/dist/react/core/display/Container.js +640 -668
  144. package/dist/react/core/display/Container.js.map +1 -1
  145. package/dist/react/core/display/CopyButton.d.ts +1 -2
  146. package/dist/react/core/display/CopyButton.js +158 -101
  147. package/dist/react/core/display/CopyButton.js.map +1 -1
  148. package/dist/react/core/display/GlassCard.d.ts +3 -4
  149. package/dist/react/core/display/GlassCard.js +416 -369
  150. package/dist/react/core/display/GlassCard.js.map +1 -1
  151. package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
  152. package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
  153. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  154. package/dist/react/core/display/Icon.d.ts +2 -3
  155. package/dist/react/core/display/Icon.js +172 -143
  156. package/dist/react/core/display/Icon.js.map +1 -1
  157. package/dist/react/core/display/Typography.d.ts +24 -25
  158. package/dist/react/core/display/Typography.js +360 -178
  159. package/dist/react/core/display/Typography.js.map +1 -1
  160. package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
  161. package/dist/react/core/feedback/ConfirmDialog.js +343 -304
  162. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  163. package/dist/react/core/feedback/Dialog.d.ts +2 -3
  164. package/dist/react/core/feedback/Dialog.js +189 -197
  165. package/dist/react/core/feedback/Dialog.js.map +1 -1
  166. package/dist/react/core/feedback/Toast.d.ts +2 -3
  167. package/dist/react/core/feedback/Toast.js +346 -207
  168. package/dist/react/core/feedback/Toast.js.map +1 -1
  169. package/dist/react/core/index.d.ts +26 -26
  170. package/dist/react/core/index.js +7 -119
  171. package/dist/react/core/inputs/Button.d.ts +2 -3
  172. package/dist/react/core/inputs/Button.js +281 -304
  173. package/dist/react/core/inputs/Button.js.map +1 -1
  174. package/dist/react/core/inputs/Checkbox.d.ts +2 -3
  175. package/dist/react/core/inputs/Checkbox.js +139 -127
  176. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  177. package/dist/react/core/inputs/Input.d.ts +3 -4
  178. package/dist/react/core/inputs/Input.js +277 -285
  179. package/dist/react/core/inputs/Input.js.map +1 -1
  180. package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
  181. package/dist/react/core/inputs/LimitsBar.js +271 -196
  182. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  183. package/dist/react/core/inputs/NumberInput.d.ts +2 -3
  184. package/dist/react/core/inputs/NumberInput.js +531 -503
  185. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  186. package/dist/react/core/inputs/PinInput.d.ts +2 -3
  187. package/dist/react/core/inputs/PinInput.js +224 -207
  188. package/dist/react/core/inputs/PinInput.js.map +1 -1
  189. package/dist/react/core/inputs/Select.d.ts +1 -2
  190. package/dist/react/core/inputs/Select.js +298 -299
  191. package/dist/react/core/inputs/Select.js.map +1 -1
  192. package/dist/react/core/inputs/Toggle.d.ts +2 -3
  193. package/dist/react/core/inputs/Toggle.js +142 -148
  194. package/dist/react/core/inputs/Toggle.js.map +1 -1
  195. package/dist/react/core/layout/Box.d.ts +3 -4
  196. package/dist/react/core/layout/Box.js +118 -122
  197. package/dist/react/core/layout/Box.js.map +1 -1
  198. package/dist/react/core/layout/Center.d.ts +0 -1
  199. package/dist/react/core/layout/Center.js +45 -32
  200. package/dist/react/core/layout/Center.js.map +1 -1
  201. package/dist/react/core/layout/Divider.d.ts +1 -2
  202. package/dist/react/core/layout/Divider.js +112 -106
  203. package/dist/react/core/layout/Divider.js.map +1 -1
  204. package/dist/react/core/layout/Flex.d.ts +4 -5
  205. package/dist/react/core/layout/Flex.js +120 -124
  206. package/dist/react/core/layout/Flex.js.map +1 -1
  207. package/dist/react/core/layout/Grid.d.ts +5 -6
  208. package/dist/react/core/layout/Grid.js +131 -138
  209. package/dist/react/core/layout/Grid.js.map +1 -1
  210. package/dist/react/core/layout/Spacer.d.ts +0 -1
  211. package/dist/react/core/layout/Spacer.js +36 -29
  212. package/dist/react/core/layout/Spacer.js.map +1 -1
  213. package/dist/react/core/layout/Stack.d.ts +4 -5
  214. package/dist/react/core/layout/Stack.js +127 -70
  215. package/dist/react/core/layout/Stack.js.map +1 -1
  216. package/dist/react/core/layout/responsive.d.ts +1 -2
  217. package/dist/react/core/layout/responsive.js +25 -19
  218. package/dist/react/core/layout/responsive.js.map +1 -1
  219. package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
  220. package/dist/react/core/layout/useBreakpoint.js +119 -66
  221. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  222. package/dist/react/core/navigation/AppBar.d.ts +1 -2
  223. package/dist/react/core/navigation/AppBar.js +827 -805
  224. package/dist/react/core/navigation/AppBar.js.map +1 -1
  225. package/dist/react/core/navigation/Pagination.d.ts +2 -3
  226. package/dist/react/core/navigation/Pagination.js +207 -187
  227. package/dist/react/core/navigation/Pagination.js.map +1 -1
  228. package/dist/react/core/navigation/SideNav.d.ts +5 -6
  229. package/dist/react/core/navigation/SideNav.js +855 -629
  230. package/dist/react/core/navigation/SideNav.js.map +1 -1
  231. package/dist/react/core/navigation/Tabs.d.ts +0 -1
  232. package/dist/react/core/navigation/Tabs.js +113 -119
  233. package/dist/react/core/navigation/Tabs.js.map +1 -1
  234. package/dist/react/core/overlays/Popover.d.ts +2 -3
  235. package/dist/react/core/overlays/Popover.js +303 -282
  236. package/dist/react/core/overlays/Popover.js.map +1 -1
  237. package/dist/react/core/overlays/SidePanel.d.ts +1 -2
  238. package/dist/react/core/overlays/SidePanel.js +217 -212
  239. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  240. package/dist/react/core/overlays/Tooltip.d.ts +1 -2
  241. package/dist/react/core/overlays/Tooltip.js +170 -176
  242. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  243. package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
  244. package/dist/react/core/widgets/ActivityPlanner.js +855 -517
  245. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  246. package/dist/react/core/widgets/Capture.d.ts +2 -3
  247. package/dist/react/core/widgets/Capture.js +965 -779
  248. package/dist/react/core/widgets/Capture.js.map +1 -1
  249. package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
  250. package/dist/react/core/widgets/ChatPanel.js +1734 -1034
  251. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  252. package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
  253. package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
  254. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  255. package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
  256. package/dist/react/core/widgets/CommandBuilder.js +624 -516
  257. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  258. package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
  259. package/dist/react/core/widgets/ConnectionForm.js +528 -492
  260. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  261. package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
  262. package/dist/react/core/widgets/FileExplorer.js +617 -505
  263. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  264. package/dist/react/core/widgets/HexViewer.d.ts +2 -3
  265. package/dist/react/core/widgets/HexViewer.js +1353 -1091
  266. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  267. package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
  268. package/dist/react/core/widgets/ImageFrame.js +230 -193
  269. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  270. package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
  271. package/dist/react/core/widgets/ImageGallery.js +380 -314
  272. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  273. package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
  274. package/dist/react/core/widgets/ImagePanel.js +110 -101
  275. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  276. package/dist/react/core/widgets/LogViewer.d.ts +2 -3
  277. package/dist/react/core/widgets/LogViewer.js +711 -607
  278. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  279. package/dist/react/core/widgets/MessageStream.d.ts +3 -4
  280. package/dist/react/core/widgets/MessageStream.js +514 -444
  281. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  282. package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
  283. package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
  284. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  285. package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
  286. package/dist/react/core/widgets/PacketViewer.js +550 -421
  287. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  288. package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
  289. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
  290. package/dist/react/hooks/index.d.ts +5 -5
  291. package/dist/react/hooks/useAccessWindows.d.ts +0 -1
  292. package/dist/react/hooks/useCompactMode.d.ts +1 -1
  293. package/dist/react/hooks/useCompactMode.js +104 -58
  294. package/dist/react/hooks/useCompactMode.js.map +1 -1
  295. package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
  296. package/dist/react/hooks/useSimulationScene.d.ts +1 -2
  297. package/dist/react/hooks/useSimulationScene.js +481 -374
  298. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  299. package/dist/react/index.d.ts +3 -3
  300. package/dist/react/panels/LayerControlPanel.d.ts +1 -2
  301. package/dist/react/panels/LayerControlPanel.js +223 -180
  302. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  303. package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
  304. package/dist/react/panels/ObjectInventoryPanel.js +315 -250
  305. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  306. package/dist/react/panels/index.d.ts +1 -1
  307. package/dist/react/panels/index.js +1 -6
  308. package/dist/react/shared/ErrorBoundary.d.ts +1 -2
  309. package/dist/react/shared/ErrorBoundary.js +170 -139
  310. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  311. package/dist/react/shared/Skeleton.d.ts +1 -2
  312. package/dist/react/shared/Skeleton.js +300 -301
  313. package/dist/react/shared/Skeleton.js.map +1 -1
  314. package/dist/react/theme/ThemeProvider.d.ts +2 -321
  315. package/dist/react/theme/ThemeProvider.js +313 -939
  316. package/dist/react/theme/ThemeProvider.js.map +1 -1
  317. package/dist/react/theme/cardAccent.d.ts +1 -2
  318. package/dist/react/theme/cardAccent.js +196 -126
  319. package/dist/react/theme/cardAccent.js.map +1 -1
  320. package/dist/react/theme/config.d.ts +0 -1
  321. package/dist/react/theme/config.js +47 -25
  322. package/dist/react/theme/config.js.map +1 -1
  323. package/dist/react/theme/index.d.ts +2 -2
  324. package/dist/react/theme/index.js +1 -16
  325. package/dist/react/theme/types.d.ts +332 -0
  326. package/dist/react/theme/types.js +39 -0
  327. package/dist/react/theme/types.js.map +1 -0
  328. package/dist/react/types.d.ts +8 -8
  329. package/dist/react/types.js +147 -47
  330. package/dist/react/types.js.map +1 -1
  331. package/dist/react/utils/categoryPalette.d.ts +0 -1
  332. package/dist/react/utils/categoryPalette.js +91 -102
  333. package/dist/react/utils/categoryPalette.js.map +1 -1
  334. package/dist/react/utils/index.d.ts +5 -5
  335. package/dist/react/utils/index.js +457 -371
  336. package/dist/react/utils/index.js.map +1 -1
  337. package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
  338. package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
  339. package/dist/react/visualizations/NavBallCard.d.ts +0 -1
  340. package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
  341. package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
  342. package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
  343. package/dist/react/visualizations/index.d.ts +3 -3
  344. package/dist/react.d.ts +2 -1
  345. package/dist/react.js +17 -221
  346. package/dist/shaders/atmosphere.frag.js +6 -5
  347. package/dist/shaders/atmosphere.frag.js.map +1 -1
  348. package/dist/shaders/atmosphere.vert.js +6 -5
  349. package/dist/shaders/atmosphere.vert.js.map +1 -1
  350. package/dist/shaders/stars.frag.js +6 -5
  351. package/dist/shaders/stars.frag.js.map +1 -1
  352. package/dist/shaders/stars.vert.js +6 -5
  353. package/dist/shaders/stars.vert.js.map +1 -1
  354. package/dist/style.css +26 -3
  355. package/dist/tokens/core.d.ts +159 -0
  356. package/dist/tokens/core.js +144 -0
  357. package/dist/tokens/core.js.map +1 -0
  358. package/dist/tokens/css-vars.d.ts +213 -2
  359. package/dist/tokens/css-vars.js +590 -227
  360. package/dist/tokens/css-vars.js.map +1 -1
  361. package/dist/tokens/index.d.ts +31 -228
  362. package/dist/tokens/index.js +281 -366
  363. package/dist/tokens/index.js.map +1 -1
  364. package/dist/tokens/palettes/astro.d.ts +129 -0
  365. package/dist/tokens/palettes/astro.js +112 -0
  366. package/dist/tokens/palettes/astro.js.map +1 -0
  367. package/dist/tokens/palettes/index.d.ts +288 -0
  368. package/dist/tokens/palettes/index.js +35 -0
  369. package/dist/tokens/palettes/index.js.map +1 -0
  370. package/dist/tokens/palettes/tailwind.d.ts +324 -0
  371. package/dist/tokens/palettes/tailwind.js +201 -0
  372. package/dist/tokens/palettes/tailwind.js.map +1 -0
  373. package/dist/tokens/palettes/zendir.d.ts +126 -0
  374. package/dist/tokens/palettes/zendir.js +92 -0
  375. package/dist/tokens/palettes/zendir.js.map +1 -0
  376. package/dist/tokens/themes.d.ts +30 -0
  377. package/dist/tokens/themes.js +704 -0
  378. package/dist/tokens/themes.js.map +1 -0
  379. package/dist/tokens/tokens.css +533 -155
  380. package/dist/types/index.d.ts +2 -2
  381. package/package.json +310 -313
  382. package/dist/index.js.map +0 -1
  383. package/dist/react/3d/index.js.map +0 -1
  384. package/dist/react/astro/index.js.map +0 -1
  385. package/dist/react/cards/index.js.map +0 -1
  386. package/dist/react/charts/index.js.map +0 -1
  387. package/dist/react/core/index.js.map +0 -1
  388. package/dist/react/panels/index.js.map +0 -1
  389. package/dist/react/theme/astro-tokens.d.ts +0 -153
  390. package/dist/react/theme/index.js.map +0 -1
  391. package/dist/react.js.map +0 -1
@@ -1,1196 +1,1186 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { memo, useState, useCallback, useMemo, useRef, useEffect } from "react";
3
1
  import { useTheme } from "../theme/ThemeProvider.js";
4
- import { classNames, safeAccentText } from "../utils/index.js";
5
- import { useCategoryPalette } from "../context/CategoryContext.js";
6
2
  import { Icon } from "../core/display/Icon.js";
7
- import { Badge } from "../core/display/Badge.js";
8
- import { Tooltip } from "../core/overlays/Tooltip.js";
3
+ import { classNames, safeAccentText } from "../utils/index.js";
9
4
  import { Button } from "../core/inputs/Button.js";
10
5
  import { useBreakpoint } from "../core/layout/useBreakpoint.js";
11
- const Z = {
12
- /** Base layer for track events / scatter dots */
13
- trackEvent: 1,
14
- /** Hovered track event raised above siblings */
15
- trackEventHovered: 20,
16
- /** Sticky column elements (playhead, track labels) */
17
- sticky: 15,
18
- /** Floating day-marker lines above track content */
19
- dayMarker: 5,
20
- /** Current-time playhead line above sticky headers and events */
21
- playhead: 25,
22
- /** Loading overlay covering chart content */
23
- loadingOverlay: 100,
24
- /** Floating tooltips above all chart chrome */
25
- tooltip: 1e3
6
+ import { Tooltip } from "../core/overlays/Tooltip.js";
7
+ import { Badge } from "../core/display/Badge.js";
8
+ import { useCategoryPalette } from "../context/CategoryContext.js";
9
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/react/astro/UnifiedTimeline.tsx
12
+ /**
13
+ * @zendir/ui - UnifiedTimeline Component
14
+ *
15
+ * Enterprise-grade unified timeline with seamless view switching between
16
+ * Gantt chart view and Event list view.
17
+ *
18
+ * Design Philosophy (Apple/Google inspired):
19
+ * - Single component, multiple views
20
+ * - Smooth transitions between views
21
+ * - Consistent data model
22
+ * - Flexible for teams and single user
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <UnifiedTimeline
27
+ * title="Mission Timeline"
28
+ * events={events}
29
+ * tracks={tracks}
30
+ * defaultView="list"
31
+ * />
32
+ * ```
33
+ */
34
+ var Z = {
35
+ /** Base layer for track events / scatter dots */
36
+ trackEvent: 1,
37
+ /** Hovered track event raised above siblings */
38
+ trackEventHovered: 20,
39
+ /** Sticky column elements (playhead, track labels) */
40
+ sticky: 15,
41
+ /** Floating day-marker lines above track content */
42
+ dayMarker: 5,
43
+ /** Current-time playhead line — above sticky headers and events */
44
+ playhead: 25,
45
+ /** Loading overlay covering chart content */
46
+ loadingOverlay: 100,
47
+ /** Floating tooltips — above all chart chrome */
48
+ tooltip: 1e3
26
49
  };
27
- const TIMELINE_FILTER_TEAM_NONE = "__zendir_team_none__";
28
- const TIMELINE_FILTER_TEAM_LEGACY = "__zendir_team_legacy__";
50
+ /** Filter key for events with no team accent (use with `TimelineFilter.teams`). */
51
+ var TIMELINE_FILTER_TEAM_NONE = "__zendir_team_none__";
52
+ /** Filter key for legacy accent color only (no structured `team.id`). */
53
+ var TIMELINE_FILTER_TEAM_LEGACY = "__zendir_team_legacy__";
54
+ /** Resolved team accent for rendering (from `event.team` or legacy `event.color`). */
29
55
  function getTimelineTeamAccent(event) {
30
- const t = event.team;
31
- if (t == null ? void 0 : t.color) {
32
- return { color: t.color, id: t.id, label: t.label };
33
- }
34
- if (event.color) {
35
- return { color: event.color };
36
- }
37
- return null;
56
+ const t = event.team;
57
+ if (t?.color) return {
58
+ color: t.color,
59
+ id: t.id,
60
+ label: t.label
61
+ };
62
+ if (event.color) return { color: event.color };
63
+ return null;
38
64
  }
65
+ /** Label for team row in tooltips (id / label / arguments.Team / badge). */
39
66
  function getTimelineTeamDisplayLabel(event, accent) {
40
- if (accent.label) return accent.label;
41
- if (accent.id !== void 0 && accent.id !== "") return String(accent.id);
42
- const args = event.arguments;
43
- const argsTeam = args == null ? void 0 : args.Team;
44
- if (argsTeam !== void 0 && argsTeam !== "") return String(argsTeam);
45
- return event.badge || "Team";
67
+ if (accent.label) return accent.label;
68
+ if (accent.id !== void 0 && accent.id !== "") return String(accent.id);
69
+ const argsTeam = event.arguments?.Team;
70
+ if (argsTeam !== void 0 && argsTeam !== "") return String(argsTeam);
71
+ return event.badge || "Team";
46
72
  }
73
+ /** Stable key for team filter pills (`TimelineFilter.teams`). */
47
74
  function getTimelineTeamFilterKey(event) {
48
- const accent = getTimelineTeamAccent(event);
49
- if (!accent) return TIMELINE_FILTER_TEAM_NONE;
50
- if (accent.id === void 0 || accent.id === "") return TIMELINE_FILTER_TEAM_LEGACY;
51
- return String(accent.id);
75
+ const accent = getTimelineTeamAccent(event);
76
+ if (!accent) return TIMELINE_FILTER_TEAM_NONE;
77
+ if (accent.id === void 0 || accent.id === "") return TIMELINE_FILTER_TEAM_LEGACY;
78
+ return String(accent.id);
52
79
  }
53
- const ALL_TIMELINE_STATUSES = [
54
- "off",
55
- "standby",
56
- "normal",
57
- "caution",
58
- "serious",
59
- "critical"
80
+ var ALL_TIMELINE_STATUSES = [
81
+ "off",
82
+ "standby",
83
+ "normal",
84
+ "caution",
85
+ "serious",
86
+ "critical"
60
87
  ];
88
+ /** Display status used for filtering and markers (badgeVariant overrides raw status when set). */
61
89
  function getTimelineEventDisplayStatus(event) {
62
- if (event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary") {
63
- return event.badgeVariant;
64
- }
65
- return event.status ?? "normal";
90
+ if (event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary") return event.badgeVariant;
91
+ return event.status ?? "normal";
66
92
  }
67
93
  function isTimelineFilterActive(filter) {
68
- return Boolean(
69
- filter.search && filter.search.trim() || filter.tracks && filter.tracks.length > 0 || filter.status && filter.status.length > 0 || filter.eventShape && filter.eventShape !== "all" || filter.teams && filter.teams.length > 0 || filter.teamColoredOnly
70
- );
94
+ return Boolean(filter.search && filter.search.trim() || filter.tracks && filter.tracks.length > 0 || filter.status && filter.status.length > 0 || filter.eventShape && filter.eventShape !== "all" || filter.teams && filter.teams.length > 0 || filter.teamColoredOnly);
71
95
  }
72
96
  function normalizeTimelineFilter(f) {
73
- var _a, _b, _c, _d;
74
- const o = {};
75
- const s = (_a = f.search) == null ? void 0 : _a.trim();
76
- if (s) o.search = s;
77
- if ((_b = f.tracks) == null ? void 0 : _b.length) o.tracks = [...f.tracks];
78
- if ((_c = f.status) == null ? void 0 : _c.length) o.status = [...f.status];
79
- if (f.eventShape && f.eventShape !== "all") o.eventShape = f.eventShape;
80
- if ((_d = f.teams) == null ? void 0 : _d.length) o.teams = [...f.teams];
81
- if (f.teamColoredOnly) o.teamColoredOnly = true;
82
- return o;
97
+ const o = {};
98
+ const s = f.search?.trim();
99
+ if (s) o.search = s;
100
+ if (f.tracks?.length) o.tracks = [...f.tracks];
101
+ if (f.status?.length) o.status = [...f.status];
102
+ if (f.eventShape && f.eventShape !== "all") o.eventShape = f.eventShape;
103
+ if (f.teams?.length) o.teams = [...f.teams];
104
+ if (f.teamColoredOnly) o.teamColoredOnly = true;
105
+ return o;
83
106
  }
84
107
  function matchesTimelineFilter(event, filter) {
85
- var _a, _b, _c, _d, _e, _f;
86
- if (!isTimelineFilterActive(filter)) return true;
87
- if ((_a = filter.tracks) == null ? void 0 : _a.length) {
88
- const tid = event.track ?? "default";
89
- if (!filter.tracks.includes(tid)) return false;
90
- }
91
- if ((_b = filter.status) == null ? void 0 : _b.length) {
92
- const ds = getTimelineEventDisplayStatus(event);
93
- if (!filter.status.includes(ds)) return false;
94
- }
95
- const q = (_c = filter.search) == null ? void 0 : _c.trim().toLowerCase();
96
- if (q) {
97
- const argStr = event.arguments ? Object.values(event.arguments).map((v) => String(v)).join(" ") : "";
98
- const accent = getTimelineTeamAccent(event);
99
- const teamHay = accent ? [accent.label, accent.id !== void 0 ? String(accent.id) : ""].filter(Boolean).join(" ") : "";
100
- const hay = [
101
- event.title,
102
- event.subtitle,
103
- event.badge,
104
- event.track,
105
- argStr,
106
- teamHay
107
- ].filter(Boolean).join(" ").toLowerCase();
108
- if (!hay.includes(q)) return false;
109
- }
110
- if (filter.eventShape === "point") {
111
- const endT = (_d = event.end) == null ? void 0 : _d.getTime();
112
- const st = event.start.getTime();
113
- if (endT != null && endT > st) return false;
114
- }
115
- if (filter.eventShape === "range") {
116
- const endT = (_e = event.end) == null ? void 0 : _e.getTime();
117
- const st = event.start.getTime();
118
- if (endT == null || endT <= st) return false;
119
- }
120
- if ((_f = filter.teams) == null ? void 0 : _f.length) {
121
- const teamKey = getTimelineTeamFilterKey(event);
122
- if (!filter.teams.includes(teamKey)) return false;
123
- }
124
- if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0) && !getTimelineTeamAccent(event)) {
125
- return false;
126
- }
127
- return true;
108
+ if (!isTimelineFilterActive(filter)) return true;
109
+ if (filter.tracks?.length) {
110
+ const tid = event.track ?? "default";
111
+ if (!filter.tracks.includes(tid)) return false;
112
+ }
113
+ if (filter.status?.length) {
114
+ const ds = getTimelineEventDisplayStatus(event);
115
+ if (!filter.status.includes(ds)) return false;
116
+ }
117
+ const q = filter.search?.trim().toLowerCase();
118
+ if (q) {
119
+ const argStr = event.arguments ? Object.values(event.arguments).map((v) => String(v)).join(" ") : "";
120
+ const accent = getTimelineTeamAccent(event);
121
+ const teamHay = accent ? [accent.label, accent.id !== void 0 ? String(accent.id) : ""].filter(Boolean).join(" ") : "";
122
+ if (![
123
+ event.title,
124
+ event.subtitle,
125
+ event.badge,
126
+ event.track,
127
+ argStr,
128
+ teamHay
129
+ ].filter(Boolean).join(" ").toLowerCase().includes(q)) return false;
130
+ }
131
+ if (filter.eventShape === "point") {
132
+ const endT = event.end?.getTime();
133
+ const st = event.start.getTime();
134
+ if (endT != null && endT > st) return false;
135
+ }
136
+ if (filter.eventShape === "range") {
137
+ const endT = event.end?.getTime();
138
+ const st = event.start.getTime();
139
+ if (endT == null || endT <= st) return false;
140
+ }
141
+ if (filter.teams?.length) {
142
+ const teamKey = getTimelineTeamFilterKey(event);
143
+ if (!filter.teams.includes(teamKey)) return false;
144
+ }
145
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0) && !getTimelineTeamAccent(event)) return false;
146
+ return true;
128
147
  }
129
148
  function countTimelineFilterChips(filter) {
130
- var _a, _b, _c;
131
- let n = 0;
132
- if ((_a = filter.search) == null ? void 0 : _a.trim()) n++;
133
- if ((_b = filter.tracks) == null ? void 0 : _b.length) n++;
134
- if ((_c = filter.status) == null ? void 0 : _c.length) n++;
135
- if (filter.eventShape && filter.eventShape !== "all") n++;
136
- if (filter.teams && filter.teams.length > 0) n++;
137
- if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0)) n++;
138
- return n;
149
+ let n = 0;
150
+ if (filter.search?.trim()) n++;
151
+ if (filter.tracks?.length) n++;
152
+ if (filter.status?.length) n++;
153
+ if (filter.eventShape && filter.eventShape !== "all") n++;
154
+ if (filter.teams && filter.teams.length > 0) n++;
155
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0)) n++;
156
+ return n;
139
157
  }
140
- function TimelineStatusMarker({
141
- status,
142
- fillColor,
143
- size = 12
144
- }) {
145
- const effectiveStatus = status ?? "normal";
146
- const glow = `${fillColor}60`;
147
- switch (effectiveStatus) {
148
- case "off":
149
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "3", fill: fillColor }) });
150
- case "standby":
151
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "3.5", fill: "none", stroke: fillColor, strokeWidth: "2" }) });
152
- case "normal":
153
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: fillColor }) });
154
- case "caution":
155
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "10", height: "10", fill: fillColor }) });
156
- case "serious":
157
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("polygon", { points: "6,1 11,6 6,11 1,6", fill: fillColor }) });
158
- case "critical":
159
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("polygon", { points: "6,11 1,2 11,2", fill: fillColor }) });
160
- default:
161
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { flexShrink: 0, filter: `drop-shadow(0 0 4px ${glow})` }, children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: fillColor }) });
162
- }
158
+ /** Astro UX status shapes per official spec: filled circle (normal), ring (standby), small circle (off), square (caution), diamond (serious), triangle (critical) */
159
+ function TimelineStatusMarker({ status, fillColor, size = 12 }) {
160
+ const effectiveStatus = status ?? "normal";
161
+ const glow = `${fillColor}60`;
162
+ switch (effectiveStatus) {
163
+ case "off": return /* @__PURE__ */ jsx("svg", {
164
+ viewBox: "0 0 12 12",
165
+ width: size,
166
+ height: size,
167
+ style: {
168
+ flexShrink: 0,
169
+ filter: `drop-shadow(0 0 4px ${glow})`
170
+ },
171
+ children: /* @__PURE__ */ jsx("circle", {
172
+ cx: "6",
173
+ cy: "6",
174
+ r: "3",
175
+ fill: fillColor
176
+ })
177
+ });
178
+ case "standby": return /* @__PURE__ */ jsx("svg", {
179
+ viewBox: "0 0 12 12",
180
+ width: size,
181
+ height: size,
182
+ style: {
183
+ flexShrink: 0,
184
+ filter: `drop-shadow(0 0 4px ${glow})`
185
+ },
186
+ children: /* @__PURE__ */ jsx("circle", {
187
+ cx: "6",
188
+ cy: "6",
189
+ r: "3.5",
190
+ fill: "none",
191
+ stroke: fillColor,
192
+ strokeWidth: "2"
193
+ })
194
+ });
195
+ case "normal": return /* @__PURE__ */ jsx("svg", {
196
+ viewBox: "0 0 12 12",
197
+ width: size,
198
+ height: size,
199
+ style: {
200
+ flexShrink: 0,
201
+ filter: `drop-shadow(0 0 4px ${glow})`
202
+ },
203
+ children: /* @__PURE__ */ jsx("circle", {
204
+ cx: "6",
205
+ cy: "6",
206
+ r: "5",
207
+ fill: fillColor
208
+ })
209
+ });
210
+ case "caution": return /* @__PURE__ */ jsx("svg", {
211
+ viewBox: "0 0 12 12",
212
+ width: size,
213
+ height: size,
214
+ style: {
215
+ flexShrink: 0,
216
+ filter: `drop-shadow(0 0 4px ${glow})`
217
+ },
218
+ children: /* @__PURE__ */ jsx("rect", {
219
+ x: "1",
220
+ y: "1",
221
+ width: "10",
222
+ height: "10",
223
+ fill: fillColor
224
+ })
225
+ });
226
+ case "serious": return /* @__PURE__ */ jsx("svg", {
227
+ viewBox: "0 0 12 12",
228
+ width: size,
229
+ height: size,
230
+ style: {
231
+ flexShrink: 0,
232
+ filter: `drop-shadow(0 0 4px ${glow})`
233
+ },
234
+ children: /* @__PURE__ */ jsx("polygon", {
235
+ points: "6,1 11,6 6,11 1,6",
236
+ fill: fillColor
237
+ })
238
+ });
239
+ case "critical": return /* @__PURE__ */ jsx("svg", {
240
+ viewBox: "0 0 12 12",
241
+ width: size,
242
+ height: size,
243
+ style: {
244
+ flexShrink: 0,
245
+ filter: `drop-shadow(0 0 4px ${glow})`
246
+ },
247
+ children: /* @__PURE__ */ jsx("polygon", {
248
+ points: "6,11 1,2 11,2",
249
+ fill: fillColor
250
+ })
251
+ });
252
+ default: return /* @__PURE__ */ jsx("svg", {
253
+ viewBox: "0 0 12 12",
254
+ width: size,
255
+ height: size,
256
+ style: {
257
+ flexShrink: 0,
258
+ filter: `drop-shadow(0 0 4px ${glow})`
259
+ },
260
+ children: /* @__PURE__ */ jsx("circle", {
261
+ cx: "6",
262
+ cy: "6",
263
+ r: "5",
264
+ fill: fillColor
265
+ })
266
+ });
267
+ }
163
268
  }
164
- const EventListItem = memo(function EventListItem2({
165
- event,
166
- isLast,
167
- onEventClick
168
- }) {
169
- const { tokens } = useTheme();
170
- const [expanded, setExpanded] = useState(false);
171
- const [hovered, setHovered] = useState(false);
172
- const hasArguments = event.arguments && Object.keys(event.arguments).length > 0;
173
- const argumentCount = hasArguments ? Object.keys(event.arguments).length : 0;
174
- const timestamps = event.timestamps || [
175
- { label: "Time", value: event.start.toISOString().replace("T", " ").substring(0, 19) + " UTC", status: event.status }
176
- ];
177
- const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
178
- const markerColor = tokens.colors.status[displayStatus];
179
- const teamAccent = getTimelineTeamAccent(event);
180
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "16px" }, children: [
181
- /* @__PURE__ */ jsxs(
182
- "div",
183
- {
184
- style: {
185
- display: "flex",
186
- flexDirection: "column",
187
- alignItems: "center",
188
- width: "20px",
189
- flexShrink: 0
190
- },
191
- children: [
192
- /* @__PURE__ */ jsx("div", { style: { marginTop: "8px" }, children: /* @__PURE__ */ jsx(TimelineStatusMarker, { status: displayStatus, fillColor: markerColor, size: 12 }) }),
193
- !isLast && /* @__PURE__ */ jsx(
194
- "div",
195
- {
196
- style: {
197
- width: "2px",
198
- flex: 1,
199
- backgroundColor: `${tokens.colors.accent.primary}30`,
200
- marginTop: "4px"
201
- }
202
- }
203
- )
204
- ]
205
- }
206
- ),
207
- /* @__PURE__ */ jsxs(
208
- "div",
209
- {
210
- onMouseEnter: () => setHovered(true),
211
- onMouseLeave: () => setHovered(false),
212
- onClick: () => onEventClick == null ? void 0 : onEventClick(event),
213
- style: {
214
- position: "relative",
215
- flex: 1,
216
- marginBottom: "16px",
217
- padding: teamAccent ? "19px 20px 16px 20px" : "16px 20px",
218
- backgroundColor: tokens.colors.background.surface,
219
- border: `1px solid ${hovered ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
220
- borderRadius: tokens.borderRadius.lg,
221
- cursor: onEventClick ? "pointer" : "default",
222
- transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
223
- overflow: "hidden",
224
- boxShadow: hovered ? `0 4px 20px ${tokens.colors.accent.primary}15, 0 0 0 1px ${tokens.colors.accent.primary}20` : "none"
225
- },
226
- children: [
227
- teamAccent && /* @__PURE__ */ jsx(
228
- "div",
229
- {
230
- style: {
231
- position: "absolute",
232
- top: 0,
233
- left: 0,
234
- right: 0,
235
- height: 3,
236
- backgroundColor: teamAccent.color
237
- }
238
- }
239
- ),
240
- /* @__PURE__ */ jsxs(
241
- "div",
242
- {
243
- style: {
244
- display: "flex",
245
- alignItems: "center",
246
- justifyContent: "space-between",
247
- marginBottom: timestamps.length ? "12px" : 0
248
- },
249
- children: [
250
- /* @__PURE__ */ jsx(
251
- "h4",
252
- {
253
- style: {
254
- margin: 0,
255
- fontSize: "0.875rem",
256
- fontWeight: 500,
257
- // AstroUXDS medium (was 600)
258
- color: tokens.colors.text.primary,
259
- letterSpacing: "0.02em",
260
- textTransform: "uppercase"
261
- },
262
- children: event.title
263
- }
264
- ),
265
- event.badge && /* @__PURE__ */ jsx(
266
- Badge,
267
- {
268
- variant: displayStatus === "caution" || displayStatus === "serious" ? "filled" : "outline",
269
- size: "small",
270
- status: displayStatus === "critical" ? "critical" : displayStatus === "serious" ? "serious" : displayStatus === "caution" ? "caution" : displayStatus === "normal" ? "normal" : void 0,
271
- children: event.badge
272
- }
273
- )
274
- ]
275
- }
276
- ),
277
- event.subtitle && /* @__PURE__ */ jsx(
278
- "p",
279
- {
280
- style: {
281
- margin: "0 0 12px 0",
282
- fontSize: "0.8125rem",
283
- color: tokens.colors.text.secondary
284
- },
285
- children: event.subtitle
286
- }
287
- ),
288
- timestamps.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: timestamps.map((ts, index) => /* @__PURE__ */ jsxs(
289
- "div",
290
- {
291
- style: {
292
- display: "flex",
293
- alignItems: "center",
294
- justifyContent: "space-between",
295
- fontSize: "0.8125rem"
296
- },
297
- children: [
298
- /* @__PURE__ */ jsxs(
299
- "span",
300
- {
301
- style: {
302
- display: "flex",
303
- alignItems: "center",
304
- gap: "8px",
305
- color: tokens.colors.text.secondary
306
- },
307
- children: [
308
- /* @__PURE__ */ jsx(
309
- TimelineStatusMarker,
310
- {
311
- status: ts.status ?? "normal",
312
- fillColor: ts.status ? tokens.colors.status[ts.status] : tokens.colors.accent.primary,
313
- size: 6
314
- }
315
- ),
316
- ts.label
317
- ]
318
- }
319
- ),
320
- /* @__PURE__ */ jsx(
321
- "span",
322
- {
323
- style: {
324
- fontFamily: tokens.typography.fontFamily.mono,
325
- color: tokens.colors.text.primary,
326
- fontWeight: 500
327
- },
328
- children: ts.value
329
- }
330
- )
331
- ]
332
- },
333
- index
334
- )) }),
335
- hasArguments && /* @__PURE__ */ jsxs(Fragment, { children: [
336
- /* @__PURE__ */ jsxs(
337
- "button",
338
- {
339
- type: "button",
340
- onClick: (e) => {
341
- e.stopPropagation();
342
- setExpanded(!expanded);
343
- },
344
- style: {
345
- display: "flex",
346
- alignItems: "center",
347
- gap: "6px",
348
- marginTop: "12px",
349
- padding: 0,
350
- background: "none",
351
- border: "none",
352
- color: tokens.colors.text.secondary,
353
- fontSize: "0.75rem",
354
- cursor: "pointer",
355
- transition: "color 150ms ease"
356
- },
357
- onMouseEnter: (e) => e.currentTarget.style.color = tokens.colors.accent.primary,
358
- onMouseLeave: (e) => e.currentTarget.style.color = tokens.colors.text.secondary,
359
- children: [
360
- /* @__PURE__ */ jsx(
361
- "svg",
362
- {
363
- width: "10",
364
- height: "10",
365
- viewBox: "0 0 24 24",
366
- fill: "currentColor",
367
- style: {
368
- transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
369
- transition: "transform 150ms ease"
370
- },
371
- children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
372
- }
373
- ),
374
- expanded ? "Hide" : "Show",
375
- " Arguments (",
376
- argumentCount,
377
- ")"
378
- ]
379
- }
380
- ),
381
- expanded && /* @__PURE__ */ jsxs(
382
- "div",
383
- {
384
- style: {
385
- marginTop: "12px",
386
- borderTop: `1px solid ${tokens.colors.border.muted}`,
387
- paddingTop: "12px",
388
- animation: "zendir-expand 200ms ease-out"
389
- },
390
- children: [
391
- /* @__PURE__ */ jsx("style", { children: `@keyframes zendir-expand { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }` }),
392
- Object.entries(event.arguments).map(([key, value]) => /* @__PURE__ */ jsxs(
393
- "div",
394
- {
395
- style: {
396
- display: "flex",
397
- alignItems: "center",
398
- justifyContent: "space-between",
399
- padding: "8px 12px",
400
- marginBottom: "4px",
401
- backgroundColor: tokens.colors.background.elevated,
402
- borderRadius: tokens.borderRadius.md,
403
- fontSize: "0.8125rem"
404
- },
405
- children: [
406
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.secondary }, children: key }),
407
- /* @__PURE__ */ jsx(
408
- "span",
409
- {
410
- style: {
411
- fontFamily: tokens.typography.fontFamily.mono,
412
- color: typeof value === "boolean" ? value ? tokens.colors.status.normal : tokens.colors.text.tertiary : tokens.colors.text.primary,
413
- fontWeight: 500
414
- },
415
- children: String(value)
416
- }
417
- )
418
- ]
419
- },
420
- key
421
- ))
422
- ]
423
- }
424
- )
425
- ] })
426
- ]
427
- }
428
- )
429
- ] });
269
+ var EventListItem = memo(function EventListItem({ event, isLast, onEventClick }) {
270
+ const { tokens } = useTheme();
271
+ const [expanded, setExpanded] = useState(false);
272
+ const [hovered, setHovered] = useState(false);
273
+ const hasArguments = event.arguments && Object.keys(event.arguments).length > 0;
274
+ const argumentCount = hasArguments ? Object.keys(event.arguments).length : 0;
275
+ const timestamps = event.timestamps || [{
276
+ label: "Time",
277
+ value: event.start.toISOString().replace("T", " ").substring(0, 19) + " UTC",
278
+ status: event.status
279
+ }];
280
+ const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
281
+ const markerColor = tokens.colors.status[displayStatus];
282
+ const teamAccent = getTimelineTeamAccent(event);
283
+ return /* @__PURE__ */ jsxs("div", {
284
+ style: {
285
+ display: "flex",
286
+ gap: "16px"
287
+ },
288
+ children: [/* @__PURE__ */ jsxs("div", {
289
+ style: {
290
+ display: "flex",
291
+ flexDirection: "column",
292
+ alignItems: "center",
293
+ width: "20px",
294
+ flexShrink: 0
295
+ },
296
+ children: [/* @__PURE__ */ jsx("div", {
297
+ style: { marginTop: "8px" },
298
+ children: /* @__PURE__ */ jsx(TimelineStatusMarker, {
299
+ status: displayStatus,
300
+ fillColor: markerColor,
301
+ size: 12
302
+ })
303
+ }), !isLast && /* @__PURE__ */ jsx("div", { style: {
304
+ width: "2px",
305
+ flex: 1,
306
+ backgroundColor: `${tokens.colors.accent.primary}30`,
307
+ marginTop: "4px"
308
+ } })]
309
+ }), /* @__PURE__ */ jsxs("div", {
310
+ onMouseEnter: () => setHovered(true),
311
+ onMouseLeave: () => setHovered(false),
312
+ onClick: () => onEventClick?.(event),
313
+ style: {
314
+ position: "relative",
315
+ flex: 1,
316
+ marginBottom: "16px",
317
+ padding: teamAccent ? "19px 20px 16px 20px" : "16px 20px",
318
+ backgroundColor: tokens.colors.background.surface,
319
+ border: `1px solid ${hovered ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
320
+ borderRadius: tokens.borderRadius.lg,
321
+ cursor: onEventClick ? "pointer" : "default",
322
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
323
+ overflow: "hidden",
324
+ boxShadow: hovered ? `0 4px 20px ${tokens.colors.accent.primary}15, 0 0 0 1px ${tokens.colors.accent.primary}20` : "none"
325
+ },
326
+ children: [
327
+ teamAccent && /* @__PURE__ */ jsx("div", { style: {
328
+ position: "absolute",
329
+ top: 0,
330
+ left: 0,
331
+ right: 0,
332
+ height: 3,
333
+ backgroundColor: teamAccent.color
334
+ } }),
335
+ /* @__PURE__ */ jsxs("div", {
336
+ style: {
337
+ display: "flex",
338
+ alignItems: "center",
339
+ justifyContent: "space-between",
340
+ marginBottom: timestamps.length ? "12px" : 0
341
+ },
342
+ children: [/* @__PURE__ */ jsx("h4", {
343
+ style: {
344
+ margin: 0,
345
+ fontSize: "0.875rem",
346
+ fontWeight: 500,
347
+ color: tokens.colors.text.primary,
348
+ letterSpacing: "0.02em",
349
+ textTransform: "uppercase"
350
+ },
351
+ children: event.title
352
+ }), event.badge && /* @__PURE__ */ jsx(Badge, {
353
+ variant: displayStatus === "caution" || displayStatus === "serious" ? "filled" : "outline",
354
+ size: "small",
355
+ status: displayStatus === "critical" ? "critical" : displayStatus === "serious" ? "serious" : displayStatus === "caution" ? "caution" : displayStatus === "normal" ? "normal" : void 0,
356
+ children: event.badge
357
+ })]
358
+ }),
359
+ event.subtitle && /* @__PURE__ */ jsx("p", {
360
+ style: {
361
+ margin: "0 0 12px 0",
362
+ fontSize: "0.8125rem",
363
+ color: tokens.colors.text.secondary
364
+ },
365
+ children: event.subtitle
366
+ }),
367
+ timestamps.length > 0 && /* @__PURE__ */ jsx("div", {
368
+ style: {
369
+ display: "flex",
370
+ flexDirection: "column",
371
+ gap: "6px"
372
+ },
373
+ children: timestamps.map((ts, index) => /* @__PURE__ */ jsxs("div", {
374
+ style: {
375
+ display: "flex",
376
+ alignItems: "center",
377
+ justifyContent: "space-between",
378
+ fontSize: "0.8125rem"
379
+ },
380
+ children: [/* @__PURE__ */ jsxs("span", {
381
+ style: {
382
+ display: "flex",
383
+ alignItems: "center",
384
+ gap: "8px",
385
+ color: tokens.colors.text.secondary
386
+ },
387
+ children: [/* @__PURE__ */ jsx(TimelineStatusMarker, {
388
+ status: ts.status ?? "normal",
389
+ fillColor: ts.status ? tokens.colors.status[ts.status] : tokens.colors.accent.primary,
390
+ size: 6
391
+ }), ts.label]
392
+ }), /* @__PURE__ */ jsx("span", {
393
+ style: {
394
+ fontFamily: tokens.typography.fontFamily.mono,
395
+ color: tokens.colors.text.primary,
396
+ fontWeight: 500
397
+ },
398
+ children: ts.value
399
+ })]
400
+ }, index))
401
+ }),
402
+ hasArguments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
403
+ type: "button",
404
+ onClick: (e) => {
405
+ e.stopPropagation();
406
+ setExpanded(!expanded);
407
+ },
408
+ style: {
409
+ display: "flex",
410
+ alignItems: "center",
411
+ gap: "6px",
412
+ marginTop: "12px",
413
+ padding: 0,
414
+ background: "none",
415
+ border: "none",
416
+ color: tokens.colors.text.secondary,
417
+ fontSize: "0.75rem",
418
+ cursor: "pointer",
419
+ transition: "color 150ms ease"
420
+ },
421
+ onMouseEnter: (e) => e.currentTarget.style.color = tokens.colors.accent.primary,
422
+ onMouseLeave: (e) => e.currentTarget.style.color = tokens.colors.text.secondary,
423
+ children: [
424
+ /* @__PURE__ */ jsx("svg", {
425
+ width: "10",
426
+ height: "10",
427
+ viewBox: "0 0 24 24",
428
+ fill: "currentColor",
429
+ style: {
430
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
431
+ transition: "transform 150ms ease"
432
+ },
433
+ children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
434
+ }),
435
+ expanded ? "Hide" : "Show",
436
+ " Arguments (",
437
+ argumentCount,
438
+ ")"
439
+ ]
440
+ }), expanded && /* @__PURE__ */ jsxs("div", {
441
+ style: {
442
+ marginTop: "12px",
443
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
444
+ paddingTop: "12px",
445
+ animation: "zendir-expand 200ms ease-out"
446
+ },
447
+ children: [/* @__PURE__ */ jsx("style", { children: `@keyframes zendir-expand { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }` }), Object.entries(event.arguments).map(([key, value]) => /* @__PURE__ */ jsxs("div", {
448
+ style: {
449
+ display: "flex",
450
+ alignItems: "center",
451
+ justifyContent: "space-between",
452
+ padding: "8px 12px",
453
+ marginBottom: "4px",
454
+ backgroundColor: tokens.colors.background.elevated,
455
+ borderRadius: tokens.borderRadius.md,
456
+ fontSize: "0.8125rem"
457
+ },
458
+ children: [/* @__PURE__ */ jsx("span", {
459
+ style: { color: tokens.colors.text.secondary },
460
+ children: key
461
+ }), /* @__PURE__ */ jsx("span", {
462
+ style: {
463
+ fontFamily: tokens.typography.fontFamily.mono,
464
+ color: typeof value === "boolean" ? value ? tokens.colors.status.normal : tokens.colors.text.tertiary : tokens.colors.text.primary,
465
+ fontWeight: 500
466
+ },
467
+ children: String(value)
468
+ })]
469
+ }, key))]
470
+ })] })
471
+ ]
472
+ })]
473
+ });
430
474
  });
431
- const ChartView = memo(function ChartView2({
432
- events,
433
- tracks,
434
- start,
435
- end,
436
- trackHeight,
437
- onEventClick,
438
- zoom,
439
- showPlayhead = true,
440
- playheadTime,
441
- onPlayheadChange: _onPlayheadChange,
442
- showDayMarkers = true,
443
- timeFormat = "absolute"
444
- }) {
445
- const { tokens, theme } = useTheme();
446
- const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
447
- const [hoveredEvent, setHoveredEvent] = useState(null);
448
- const [tooltipPos, setTooltipPos] = useState(null);
449
- const scrollContainerRef = useRef(null);
450
- const [scrollX, setScrollX] = useState(0);
451
- const totalDuration = end.getTime() - start.getTime();
452
- const trackLabelWidth = 130;
453
- const timeLabels = useMemo(() => {
454
- const labels = [];
455
- const baseLabelCount = 6;
456
- const labelCount = Math.round(baseLabelCount * zoom);
457
- for (let i = 0; i <= labelCount; i++) {
458
- const percent = i / labelCount * 100;
459
- const time = new Date(start.getTime() + totalDuration * percent / 100);
460
- labels.push({ time, percent });
461
- }
462
- return labels;
463
- }, [start, totalDuration, zoom]);
464
- const eventsByTrack = useMemo(() => {
465
- const grouped = {};
466
- tracks.forEach((track) => {
467
- grouped[track.id] = events.filter((e) => e.track === track.id);
468
- });
469
- const unassigned = events.filter((e) => !e.track);
470
- if (unassigned.length > 0 && tracks.length > 0) {
471
- grouped[tracks[0].id] = [...grouped[tracks[0].id] || [], ...unassigned];
472
- }
473
- return grouped;
474
- }, [events, tracks]);
475
- const eventOverlaps = useMemo(() => {
476
- const overlaps = {};
477
- Object.entries(eventsByTrack).forEach(([_trackId, trackEvents]) => {
478
- const sorted = [...trackEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
479
- sorted.forEach((event) => {
480
- const eventStart = event.start.getTime();
481
- const eventEnd = (event.end || new Date(eventStart + 3e5)).getTime();
482
- const overlapping = sorted.filter((other) => {
483
- if (other.id === event.id) return false;
484
- const otherStart = other.start.getTime();
485
- const otherEnd = (other.end || new Date(otherStart + 3e5)).getTime();
486
- return eventStart < otherEnd && eventEnd > otherStart;
487
- });
488
- let stackIndex = 0;
489
- if (overlapping.length > 0) {
490
- const usedIndices = overlapping.filter((o) => overlaps[o.id] !== void 0).map((o) => overlaps[o.id].stackIndex);
491
- while (usedIndices.includes(stackIndex)) {
492
- stackIndex++;
493
- }
494
- }
495
- overlaps[event.id] = {
496
- stackIndex,
497
- overlapCount: overlapping.length,
498
- overlappingIds: overlapping.map((o) => o.id)
499
- };
500
- });
501
- });
502
- return overlaps;
503
- }, [eventsByTrack]);
504
- const dayMarkers = useMemo(() => {
505
- const markers = [];
506
- const current = new Date(start);
507
- current.setUTCHours(0, 0, 0, 0);
508
- if (current.getTime() < start.getTime()) {
509
- current.setUTCDate(current.getUTCDate() + 1);
510
- }
511
- while (current.getTime() <= end.getTime()) {
512
- const percent = (current.getTime() - start.getTime()) / totalDuration * 100;
513
- if (percent >= 0 && percent <= 100) {
514
- markers.push({ time: new Date(current), percent });
515
- }
516
- current.setUTCDate(current.getUTCDate() + 1);
517
- }
518
- return markers;
519
- }, [start, end, totalDuration]);
520
- const formatTime = (date, showSeconds = false) => {
521
- if (timeFormat === "utc") {
522
- const h = String(date.getUTCHours()).padStart(2, "0");
523
- const m = String(date.getUTCMinutes()).padStart(2, "0");
524
- if (showSeconds) {
525
- const s = String(date.getUTCSeconds()).padStart(2, "0");
526
- return `${h}:${m}:${s} UTC`;
527
- }
528
- return `${h}:${m} UTC`;
529
- }
530
- const options = {
531
- hour: "2-digit",
532
- minute: "2-digit",
533
- ...showSeconds && { second: "2-digit" }
534
- };
535
- return date.toLocaleTimeString([], options);
536
- };
537
- const formatDuration = (ms) => {
538
- const totalSeconds = Math.floor(ms / 1e3);
539
- const hours = Math.floor(totalSeconds / 3600);
540
- const minutes = Math.floor(totalSeconds % 3600 / 60);
541
- const seconds = totalSeconds % 60;
542
- if (hours > 0) return `${hours}h ${minutes}m`;
543
- if (minutes > 0) return `${minutes}m ${seconds}s`;
544
- return `${seconds}s`;
545
- };
546
- const formatUTC = (date) => {
547
- return date.toISOString().replace("T", " ").substring(0, 19) + " UTC";
548
- };
549
- const getEventPosition = (event) => {
550
- const eventStart = event.start.getTime();
551
- const eventEnd = (event.end || new Date(eventStart + 3e5)).getTime();
552
- const leftPercent = (eventStart - start.getTime()) / totalDuration * 100;
553
- const widthPercent = (eventEnd - eventStart) / totalDuration * 100;
554
- return {
555
- left: `${Math.max(0, Math.min(100, leftPercent))}%`,
556
- width: `${Math.max(2, Math.min(100 - leftPercent, widthPercent))}%`
557
- };
558
- };
559
- const getStatusColor = (status) => {
560
- if (!status) return tokens.colors.accent.primary;
561
- return tokens.colors.status[status] || tokens.colors.accent.primary;
562
- };
563
- const isEventVisible = useCallback(
564
- (event) => {
565
- const container = scrollContainerRef.current;
566
- if (!container) return true;
567
- const viewportWidth = container.clientWidth - trackLabelWidth;
568
- if (viewportWidth <= 0) return true;
569
- const totalWidth = container.scrollWidth - trackLabelWidth;
570
- const eventStartMs = event.start.getTime();
571
- const eventEndMs = (event.end ?? new Date(eventStartMs + 3e5)).getTime();
572
- const leftPx = (eventStartMs - start.getTime()) / totalDuration * totalWidth;
573
- const rightPx = (eventEndMs - start.getTime()) / totalDuration * totalWidth;
574
- const overscan = 50;
575
- return rightPx >= scrollX - overscan && leftPx <= scrollX + viewportWidth + overscan;
576
- },
577
- [start, totalDuration, scrollX, trackLabelWidth]
578
- );
579
- const getStatusLabel = (status) => {
580
- const labels = {
581
- normal: "Normal",
582
- standby: "Standby",
583
- caution: "Caution",
584
- serious: "Serious",
585
- critical: "Critical",
586
- off: "Off"
587
- };
588
- return labels[status || ""] || "Unknown";
589
- };
590
- const currentPlayheadTime = playheadTime || /* @__PURE__ */ new Date();
591
- const now = currentPlayheadTime.getTime();
592
- const playheadPercent = showPlayhead && now >= start.getTime() && now <= end.getTime() ? (now - start.getTime()) / totalDuration * 100 : null;
593
- const lastAutoScrollRef = useRef(0);
594
- useEffect(() => {
595
- if (playheadPercent === null) return;
596
- const container = scrollContainerRef.current;
597
- if (!container) return;
598
- const contentWidth = container.scrollWidth;
599
- const viewportWidth = container.clientWidth;
600
- if (contentWidth <= viewportWidth) return;
601
- const timelineContentWidth = contentWidth - trackLabelWidth;
602
- const playheadPx = trackLabelWidth + playheadPercent / 100 * timelineContentWidth;
603
- const scrollLeft = container.scrollLeft;
604
- const visibleLeft = scrollLeft + trackLabelWidth;
605
- const visibleRight = scrollLeft + viewportWidth;
606
- const margin = viewportWidth * 0.15;
607
- if (playheadPx > visibleRight - margin || playheadPx < visibleLeft + margin) {
608
- const nowMs = Date.now();
609
- if (nowMs - lastAutoScrollRef.current < 300) return;
610
- lastAutoScrollRef.current = nowMs;
611
- const targetScroll = playheadPx - viewportWidth * 0.5;
612
- container.scrollTo({ left: Math.max(0, targetScroll), behavior: "smooth" });
613
- }
614
- }, [playheadPercent, trackLabelWidth]);
615
- const hoveredEventData = hoveredEvent ? events.find((e) => e.id === hoveredEvent) : null;
616
- return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
617
- /* @__PURE__ */ jsx(
618
- "div",
619
- {
620
- ref: scrollContainerRef,
621
- className: "zendir-timeline-scroll",
622
- onScroll: (e) => setScrollX(e.currentTarget.scrollLeft),
623
- style: {
624
- overflowX: "auto",
625
- overflowY: "hidden",
626
- position: "relative"
627
- },
628
- children: /* @__PURE__ */ jsxs(
629
- "div",
630
- {
631
- style: {
632
- width: `${zoom * 100}%`,
633
- minWidth: "100%",
634
- position: "relative"
635
- },
636
- children: [
637
- /* @__PURE__ */ jsxs(
638
- "div",
639
- {
640
- style: {
641
- display: "flex",
642
- height: 32,
643
- borderBottom: `1px solid ${tokens.colors.accent.primary}20`,
644
- position: "sticky",
645
- top: 0,
646
- zIndex: Z.trackEventHovered,
647
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
648
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" }
649
- },
650
- children: [
651
- /* @__PURE__ */ jsx(
652
- "div",
653
- {
654
- style: {
655
- width: trackLabelWidth,
656
- flexShrink: 0,
657
- position: "sticky",
658
- left: 0,
659
- zIndex: Z.trackEventHovered + 5,
660
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
661
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
662
- borderRight: `1px solid ${tokens.colors.border.muted}30`,
663
- display: "flex",
664
- alignItems: "center",
665
- paddingLeft: 12,
666
- fontSize: "0.625rem",
667
- fontWeight: 500,
668
- // AstroUXDS medium (was 600)
669
- color: tokens.colors.text.tertiary,
670
- textTransform: "uppercase",
671
- letterSpacing: "0.08em"
672
- },
673
- children: "Tracks"
674
- }
675
- ),
676
- /* @__PURE__ */ jsxs("div", { style: { flex: 1, position: "relative" }, children: [
677
- showDayMarkers && dayMarkers.map((marker, index) => /* @__PURE__ */ jsx(
678
- "div",
679
- {
680
- style: {
681
- position: "absolute",
682
- left: `${marker.percent}%`,
683
- top: 0,
684
- height: "100%",
685
- display: "flex",
686
- flexDirection: "column",
687
- alignItems: "center",
688
- zIndex: Z.dayMarker
689
- },
690
- children: /* @__PURE__ */ jsx(
691
- "span",
692
- {
693
- style: {
694
- fontSize: "0.5rem",
695
- color: tokens.colors.status.caution,
696
- fontFamily: tokens.typography.fontFamily.mono,
697
- whiteSpace: "nowrap",
698
- backgroundColor: `${tokens.colors.status.caution}20`,
699
- padding: "1px 4px",
700
- borderRadius: 2,
701
- marginTop: 2
702
- },
703
- children: marker.time.toLocaleDateString([], { month: "short", day: "numeric" })
704
- }
705
- )
706
- },
707
- `day-${index}`
708
- )),
709
- timeLabels.map((label, index) => /* @__PURE__ */ jsx(
710
- "span",
711
- {
712
- style: {
713
- position: "absolute",
714
- left: `${label.percent}%`,
715
- transform: "translateX(-50%)",
716
- top: 8,
717
- fontSize: "0.6875rem",
718
- color: tokens.colors.text.tertiary,
719
- fontFamily: tokens.typography.fontFamily.mono,
720
- whiteSpace: "nowrap"
721
- },
722
- children: formatTime(label.time)
723
- },
724
- index
725
- ))
726
- ] })
727
- ]
728
- }
729
- ),
730
- tracks.map((track) => /* @__PURE__ */ jsxs(
731
- "div",
732
- {
733
- "aria-label": track.label,
734
- style: {
735
- display: "flex",
736
- height: track.height || trackHeight,
737
- borderBottom: `1px solid ${tokens.colors.accent.primary}10`
738
- },
739
- children: [
740
- /* @__PURE__ */ jsx(
741
- "div",
742
- {
743
- style: {
744
- width: trackLabelWidth,
745
- flexShrink: 0,
746
- position: "sticky",
747
- left: 0,
748
- zIndex: Z.sticky,
749
- display: "flex",
750
- alignItems: "center",
751
- paddingLeft: 12,
752
- fontSize: "0.6875rem",
753
- fontWeight: 500,
754
- // AstroUXDS medium (was 600)
755
- color: tokens.colors.text.secondary,
756
- textTransform: "uppercase",
757
- letterSpacing: "0.05em",
758
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
759
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
760
- borderRight: `1px solid ${tokens.colors.border.muted}30`,
761
- boxShadow: "2px 0 8px rgba(0,0,0,0.15)"
762
- },
763
- children: track.label
764
- }
765
- ),
766
- /* @__PURE__ */ jsxs(
767
- "div",
768
- {
769
- style: {
770
- flex: 1,
771
- position: "relative",
772
- height: "100%"
773
- },
774
- children: [
775
- showDayMarkers && dayMarkers.map((marker, index) => /* @__PURE__ */ jsx(
776
- "div",
777
- {
778
- style: {
779
- position: "absolute",
780
- left: `${marker.percent}%`,
781
- top: 0,
782
- bottom: 0,
783
- width: 1,
784
- backgroundColor: `${tokens.colors.status.caution}25`,
785
- zIndex: Z.trackEvent,
786
- pointerEvents: "none"
787
- }
788
- },
789
- `day-line-${index}`
790
- )),
791
- (eventsByTrack[track.id] || []).filter(isEventVisible).map((event) => {
792
- const { left, width } = getEventPosition(event);
793
- const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
794
- const color = getStatusColor(displayStatus);
795
- const isHovered = hoveredEvent === event.id;
796
- const overlap = eventOverlaps[event.id];
797
- const hasOverlap = overlap && overlap.overlapCount > 0;
798
- const teamAccent = getTimelineTeamAccent(event);
799
- const cardHeight = hasOverlap ? 30 : 38;
800
- const stackOffset = overlap ? overlap.stackIndex * 30 : 0;
801
- const baseTop = ((track.height || trackHeight) - cardHeight) / 2;
802
- return /* @__PURE__ */ jsxs(
803
- "button",
804
- {
805
- type: "button",
806
- "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
807
- onClick: () => onEventClick == null ? void 0 : onEventClick(event),
808
- onMouseEnter: (e) => {
809
- setHoveredEvent(event.id);
810
- const rect = e.currentTarget.getBoundingClientRect();
811
- setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
812
- },
813
- onMouseLeave: () => {
814
- setHoveredEvent(null);
815
- setTooltipPos(null);
816
- },
817
- onFocus: (e) => {
818
- setHoveredEvent(event.id);
819
- const rect = e.currentTarget.getBoundingClientRect();
820
- setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
821
- },
822
- onBlur: () => {
823
- setHoveredEvent(null);
824
- setTooltipPos(null);
825
- },
826
- className: "zendir-timeline-region",
827
- style: {
828
- position: "absolute",
829
- left,
830
- width,
831
- minWidth: 50,
832
- height: cardHeight,
833
- top: Math.min(baseTop + stackOffset, (track.height || trackHeight) - cardHeight - 4),
834
- backgroundColor: "rgba(11, 26, 40, 0.95)",
835
- border: `1px solid ${isHovered ? color : `${tokens.colors.accent.primary}30`}`,
836
- borderRadius: "4px",
837
- cursor: onEventClick ? "pointer" : "default",
838
- display: "flex",
839
- alignItems: "center",
840
- overflow: "hidden",
841
- zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
842
- boxShadow: isHovered ? `0 8px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px ${color}60` : "0 2px 6px rgba(0, 0, 0, 0.25)",
843
- transition: "all 150ms cubic-bezier(0.4, 0, 0.2, 1)",
844
- transform: isHovered ? "translateY(-3px) scale(1.02)" : "none"
845
- },
846
- children: [
847
- teamAccent ? /* @__PURE__ */ jsx(
848
- "div",
849
- {
850
- style: {
851
- position: "absolute",
852
- top: 0,
853
- left: 0,
854
- right: 0,
855
- height: 2,
856
- backgroundColor: teamAccent.color,
857
- borderRadius: "4px 4px 0 0",
858
- zIndex: 1,
859
- pointerEvents: "none"
860
- },
861
- "aria-hidden": true
862
- }
863
- ) : null,
864
- /* @__PURE__ */ jsxs(
865
- "div",
866
- {
867
- style: {
868
- flex: 1,
869
- padding: `${teamAccent ? 6 : 4}px 8px 4px 8px`,
870
- overflow: "hidden",
871
- minWidth: 0,
872
- display: "flex",
873
- flexDirection: "column",
874
- justifyContent: "center"
875
- },
876
- children: [
877
- /* @__PURE__ */ jsxs(
878
- "div",
879
- {
880
- style: {
881
- display: "flex",
882
- alignItems: "center",
883
- gap: 6,
884
- minWidth: 0
885
- },
886
- children: [
887
- /* @__PURE__ */ jsx(
888
- TimelineStatusMarker,
889
- {
890
- status: displayStatus,
891
- fillColor: color,
892
- size: hasOverlap ? 8 : 10
893
- }
894
- ),
895
- /* @__PURE__ */ jsx(
896
- "span",
897
- {
898
- style: {
899
- fontSize: "0.6875rem",
900
- fontWeight: 500,
901
- // AstroUXDS medium (was 600)
902
- color: "#fff",
903
- whiteSpace: "nowrap",
904
- overflow: "hidden",
905
- textOverflow: "ellipsis",
906
- minWidth: 0
907
- },
908
- children: event.title
909
- }
910
- )
911
- ]
912
- }
913
- ),
914
- event.subtitle && !hasOverlap && /* @__PURE__ */ jsx(
915
- "div",
916
- {
917
- style: {
918
- fontSize: "0.5625rem",
919
- color: "rgba(255, 255, 255, 0.5)",
920
- whiteSpace: "nowrap",
921
- overflow: "hidden",
922
- textOverflow: "ellipsis",
923
- marginTop: 1,
924
- paddingLeft: 16
925
- },
926
- children: event.subtitle
927
- }
928
- )
929
- ]
930
- }
931
- ),
932
- hasOverlap && overlap.stackIndex === 0 && /* @__PURE__ */ jsxs(
933
- "div",
934
- {
935
- style: {
936
- position: "absolute",
937
- top: -6,
938
- right: -6,
939
- minWidth: 18,
940
- height: 18,
941
- backgroundColor: tokens.colors.status.caution,
942
- borderRadius: 9,
943
- display: "flex",
944
- alignItems: "center",
945
- justifyContent: "center",
946
- fontSize: "0.625rem",
947
- fontWeight: 700,
948
- color: "#000",
949
- boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
950
- zIndex: Z.sticky - 5
951
- },
952
- title: `${overlap.overlapCount + 1} overlapping events`,
953
- children: [
954
- "+",
955
- overlap.overlapCount
956
- ]
957
- }
958
- )
959
- ]
960
- },
961
- event.id
962
- );
963
- })
964
- ]
965
- }
966
- )
967
- ]
968
- },
969
- track.id
970
- )),
971
- playheadPercent !== null && /* @__PURE__ */ jsxs(
972
- "div",
973
- {
974
- style: {
975
- position: "absolute",
976
- top: 0,
977
- bottom: 0,
978
- left: `calc(${trackLabelWidth}px + (100% - ${trackLabelWidth}px) * ${playheadPercent} / 100)`,
979
- width: 2,
980
- backgroundColor: tokens.colors.accent.primary,
981
- boxShadow: `0 0 12px ${tokens.colors.accent.primary}`,
982
- zIndex: Z.playhead,
983
- pointerEvents: "none",
984
- transition: "left 0.4s linear"
985
- },
986
- children: [
987
- /* @__PURE__ */ jsx(
988
- "div",
989
- {
990
- style: {
991
- position: "absolute",
992
- top: 22,
993
- left: -5,
994
- width: 12,
995
- height: 12,
996
- backgroundColor: tokens.colors.accent.primary,
997
- borderRadius: 2,
998
- transform: "rotate(45deg)",
999
- boxShadow: `0 0 8px ${tokens.colors.accent.primary}`
1000
- }
1001
- }
1002
- ),
1003
- /* @__PURE__ */ jsx(
1004
- "div",
1005
- {
1006
- style: {
1007
- position: "absolute",
1008
- top: 4,
1009
- left: "50%",
1010
- transform: "translateX(-50%)",
1011
- backgroundColor: tokens.colors.accent.primary,
1012
- color: tokens.colors.background.base,
1013
- fontSize: "0.5625rem",
1014
- fontWeight: 500,
1015
- // AstroUXDS medium (was 600)
1016
- fontFamily: tokens.typography.fontFamily.mono,
1017
- padding: "2px 6px",
1018
- borderRadius: 3,
1019
- whiteSpace: "nowrap",
1020
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)"
1021
- },
1022
- children: formatTime(currentPlayheadTime, true)
1023
- }
1024
- )
1025
- ]
1026
- }
1027
- )
1028
- ]
1029
- }
1030
- )
1031
- }
1032
- ),
1033
- hoveredEventData && tooltipPos && (() => {
1034
- const tooltipColor = getStatusColor(hoveredEventData.status);
1035
- const teamTip = getTimelineTeamAccent(hoveredEventData);
1036
- return /* @__PURE__ */ jsxs(
1037
- "div",
1038
- {
1039
- style: {
1040
- position: "fixed",
1041
- left: tooltipPos.x,
1042
- top: tooltipPos.y,
1043
- transform: "translate(-50%, -100%)",
1044
- zIndex: Z.tooltip,
1045
- pointerEvents: "none",
1046
- animation: "zendir-tooltip-appear 150ms ease"
1047
- },
1048
- children: [
1049
- /* @__PURE__ */ jsxs(
1050
- "div",
1051
- {
1052
- style: {
1053
- backgroundColor: "rgba(15, 23, 42, 0.98)",
1054
- border: `1px solid ${tooltipColor}50`,
1055
- borderRadius: tokens.borderRadius.md,
1056
- padding: "12px 16px",
1057
- minWidth: 220,
1058
- maxWidth: 320,
1059
- boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${tooltipColor}30`,
1060
- backdropFilter: "blur(12px)"
1061
- },
1062
- children: [
1063
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }, children: [
1064
- /* @__PURE__ */ jsx(
1065
- TimelineStatusMarker,
1066
- {
1067
- status: hoveredEventData.status,
1068
- fillColor: tooltipColor,
1069
- size: 10
1070
- }
1071
- ),
1072
- /* @__PURE__ */ jsx(
1073
- "span",
1074
- {
1075
- style: {
1076
- fontSize: "0.75rem",
1077
- fontWeight: 500,
1078
- // AstroUXDS medium (was 600)
1079
- color: tooltipColor,
1080
- textTransform: "uppercase",
1081
- letterSpacing: "0.05em"
1082
- },
1083
- children: getStatusLabel(hoveredEventData.status)
1084
- }
1085
- ),
1086
- hoveredEventData.badge && /* @__PURE__ */ jsx(
1087
- "span",
1088
- {
1089
- style: {
1090
- marginLeft: "auto",
1091
- fontSize: "0.625rem",
1092
- fontWeight: 500,
1093
- // AstroUXDS medium (was 600)
1094
- padding: "2px 6px",
1095
- borderRadius: tokens.borderRadius.sm,
1096
- backgroundColor: `${tooltipColor}20`,
1097
- color: tooltipColor
1098
- },
1099
- children: hoveredEventData.badge
1100
- }
1101
- )
1102
- ] }),
1103
- teamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1104
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: teamTip.color, flexShrink: 0 } }),
1105
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: teamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, teamTip) })
1106
- ] }),
1107
- /* @__PURE__ */ jsx(
1108
- "div",
1109
- {
1110
- style: {
1111
- fontSize: "0.875rem",
1112
- fontWeight: 500,
1113
- // AstroUXDS medium (was 600)
1114
- color: "#fff",
1115
- marginBottom: 4
1116
- },
1117
- children: hoveredEventData.title
1118
- }
1119
- ),
1120
- hoveredEventData.subtitle && /* @__PURE__ */ jsx(
1121
- "div",
1122
- {
1123
- style: {
1124
- fontSize: "0.75rem",
1125
- color: "rgba(255, 255, 255, 0.6)",
1126
- marginBottom: 12
1127
- },
1128
- children: hoveredEventData.subtitle
1129
- }
1130
- ),
1131
- /* @__PURE__ */ jsxs(
1132
- "div",
1133
- {
1134
- style: {
1135
- display: "grid",
1136
- gridTemplateColumns: "auto 1fr",
1137
- gap: "6px 12px",
1138
- fontSize: "0.6875rem",
1139
- borderTop: `1px solid ${tokens.colors.border.muted}30`,
1140
- paddingTop: 10
1141
- },
1142
- children: [
1143
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "Start:" }),
1144
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.primary, fontFamily: tokens.typography.fontFamily.mono }, children: formatTime(hoveredEventData.start, true) }),
1145
- hoveredEventData.end && /* @__PURE__ */ jsxs(Fragment, { children: [
1146
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "End:" }),
1147
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.primary, fontFamily: tokens.typography.fontFamily.mono }, children: formatTime(hoveredEventData.end, true) }),
1148
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "Duration:" }),
1149
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.accent.secondary, fontWeight: 500 }, children: formatDuration(hoveredEventData.end.getTime() - hoveredEventData.start.getTime()) })
1150
- ] }),
1151
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "UTC:" }),
1152
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.secondary, fontFamily: tokens.typography.fontFamily.mono, fontSize: "0.625rem" }, children: formatUTC(hoveredEventData.start) })
1153
- ]
1154
- }
1155
- ),
1156
- onEventClick && /* @__PURE__ */ jsx(
1157
- "div",
1158
- {
1159
- style: {
1160
- marginTop: 10,
1161
- paddingTop: 8,
1162
- borderTop: `1px solid ${tokens.colors.border.muted}20`,
1163
- fontSize: "0.625rem",
1164
- color: tokens.colors.text.tertiary,
1165
- textAlign: "center"
1166
- },
1167
- children: "Click for details"
1168
- }
1169
- )
1170
- ]
1171
- }
1172
- ),
1173
- /* @__PURE__ */ jsx(
1174
- "div",
1175
- {
1176
- style: {
1177
- position: "absolute",
1178
- bottom: -6,
1179
- left: "50%",
1180
- transform: "translateX(-50%) rotate(45deg)",
1181
- width: 12,
1182
- height: 12,
1183
- backgroundColor: "rgba(15, 23, 42, 0.98)",
1184
- borderRight: `1px solid ${tooltipColor}50`,
1185
- borderBottom: `1px solid ${tooltipColor}50`
1186
- }
1187
- }
1188
- )
1189
- ]
1190
- }
1191
- );
1192
- })(),
1193
- /* @__PURE__ */ jsx("style", { children: `
475
+ var ChartView = memo(function ChartView({ events, tracks, start, end, trackHeight, onEventClick, zoom, showPlayhead = true, playheadTime, onPlayheadChange: _onPlayheadChange, showDayMarkers = true, timeFormat = "absolute" }) {
476
+ const { tokens, theme } = useTheme();
477
+ const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
478
+ const [hoveredEvent, setHoveredEvent] = useState(null);
479
+ const [tooltipPos, setTooltipPos] = useState(null);
480
+ const scrollContainerRef = useRef(null);
481
+ const [scrollX, setScrollX] = useState(0);
482
+ const totalDuration = end.getTime() - start.getTime();
483
+ const trackLabelWidth = 130;
484
+ const timeLabels = useMemo(() => {
485
+ const labels = [];
486
+ const labelCount = Math.round(6 * zoom);
487
+ for (let i = 0; i <= labelCount; i++) {
488
+ const percent = i / labelCount * 100;
489
+ const time = new Date(start.getTime() + totalDuration * percent / 100);
490
+ labels.push({
491
+ time,
492
+ percent
493
+ });
494
+ }
495
+ return labels;
496
+ }, [
497
+ start,
498
+ totalDuration,
499
+ zoom
500
+ ]);
501
+ const eventsByTrack = useMemo(() => {
502
+ const grouped = {};
503
+ tracks.forEach((track) => {
504
+ grouped[track.id] = events.filter((e) => e.track === track.id);
505
+ });
506
+ const unassigned = events.filter((e) => !e.track);
507
+ if (unassigned.length > 0 && tracks.length > 0) grouped[tracks[0].id] = [...grouped[tracks[0].id] || [], ...unassigned];
508
+ return grouped;
509
+ }, [events, tracks]);
510
+ const eventOverlaps = useMemo(() => {
511
+ const overlaps = {};
512
+ Object.entries(eventsByTrack).forEach(([_trackId, trackEvents]) => {
513
+ const sorted = [...trackEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
514
+ sorted.forEach((event) => {
515
+ const eventStart = event.start.getTime();
516
+ const eventEnd = (event.end || new Date(eventStart + 3e5)).getTime();
517
+ const overlapping = sorted.filter((other) => {
518
+ if (other.id === event.id) return false;
519
+ const otherStart = other.start.getTime();
520
+ const otherEnd = (other.end || new Date(otherStart + 3e5)).getTime();
521
+ return eventStart < otherEnd && eventEnd > otherStart;
522
+ });
523
+ let stackIndex = 0;
524
+ if (overlapping.length > 0) {
525
+ const usedIndices = overlapping.filter((o) => overlaps[o.id] !== void 0).map((o) => overlaps[o.id].stackIndex);
526
+ while (usedIndices.includes(stackIndex)) stackIndex++;
527
+ }
528
+ overlaps[event.id] = {
529
+ stackIndex,
530
+ overlapCount: overlapping.length,
531
+ overlappingIds: overlapping.map((o) => o.id)
532
+ };
533
+ });
534
+ });
535
+ return overlaps;
536
+ }, [eventsByTrack]);
537
+ const dayMarkers = useMemo(() => {
538
+ const markers = [];
539
+ const current = new Date(start);
540
+ current.setUTCHours(0, 0, 0, 0);
541
+ if (current.getTime() < start.getTime()) current.setUTCDate(current.getUTCDate() + 1);
542
+ while (current.getTime() <= end.getTime()) {
543
+ const percent = (current.getTime() - start.getTime()) / totalDuration * 100;
544
+ if (percent >= 0 && percent <= 100) markers.push({
545
+ time: new Date(current),
546
+ percent
547
+ });
548
+ current.setUTCDate(current.getUTCDate() + 1);
549
+ }
550
+ return markers;
551
+ }, [
552
+ start,
553
+ end,
554
+ totalDuration
555
+ ]);
556
+ const formatTime = (date, showSeconds = false) => {
557
+ if (timeFormat === "utc") {
558
+ const h = String(date.getUTCHours()).padStart(2, "0");
559
+ const m = String(date.getUTCMinutes()).padStart(2, "0");
560
+ if (showSeconds) return `${h}:${m}:${String(date.getUTCSeconds()).padStart(2, "0")} UTC`;
561
+ return `${h}:${m} UTC`;
562
+ }
563
+ const options = {
564
+ hour: "2-digit",
565
+ minute: "2-digit",
566
+ ...showSeconds && { second: "2-digit" }
567
+ };
568
+ return date.toLocaleTimeString([], options);
569
+ };
570
+ const formatDuration = (ms) => {
571
+ const totalSeconds = Math.floor(ms / 1e3);
572
+ const hours = Math.floor(totalSeconds / 3600);
573
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
574
+ const seconds = totalSeconds % 60;
575
+ if (hours > 0) return `${hours}h ${minutes}m`;
576
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
577
+ return `${seconds}s`;
578
+ };
579
+ const formatUTC = (date) => {
580
+ return date.toISOString().replace("T", " ").substring(0, 19) + " UTC";
581
+ };
582
+ const getEventPosition = (event) => {
583
+ const eventStart = event.start.getTime();
584
+ const eventEnd = (event.end || new Date(eventStart + 3e5)).getTime();
585
+ const leftPercent = (eventStart - start.getTime()) / totalDuration * 100;
586
+ const widthPercent = (eventEnd - eventStart) / totalDuration * 100;
587
+ return {
588
+ left: `${Math.max(0, Math.min(100, leftPercent))}%`,
589
+ width: `${Math.max(2, Math.min(100 - leftPercent, widthPercent))}%`
590
+ };
591
+ };
592
+ const getStatusColor = (status) => {
593
+ if (!status) return tokens.colors.accent.primary;
594
+ return tokens.colors.status[status] || tokens.colors.accent.primary;
595
+ };
596
+ /**
597
+ * Returns false for events whose rendered position is entirely outside the current horizontal
598
+ * scroll viewport, so we can skip them without touching the DOM. This is cheap per-frame
599
+ * bounding-box culling; the scroll container width is read once from the ref at render time.
600
+ */
601
+ const isEventVisible = useCallback((event) => {
602
+ const container = scrollContainerRef.current;
603
+ if (!container) return true;
604
+ const viewportWidth = container.clientWidth - trackLabelWidth;
605
+ if (viewportWidth <= 0) return true;
606
+ const totalWidth = container.scrollWidth - trackLabelWidth;
607
+ const eventStartMs = event.start.getTime();
608
+ const eventEndMs = (event.end ?? new Date(eventStartMs + 3e5)).getTime();
609
+ const leftPx = (eventStartMs - start.getTime()) / totalDuration * totalWidth;
610
+ const rightPx = (eventEndMs - start.getTime()) / totalDuration * totalWidth;
611
+ const overscan = 50;
612
+ return rightPx >= scrollX - overscan && leftPx <= scrollX + viewportWidth + overscan;
613
+ }, [
614
+ start,
615
+ totalDuration,
616
+ scrollX,
617
+ trackLabelWidth
618
+ ]);
619
+ const getStatusLabel = (status) => {
620
+ return {
621
+ normal: "Normal",
622
+ standby: "Standby",
623
+ caution: "Caution",
624
+ serious: "Serious",
625
+ critical: "Critical",
626
+ off: "Off"
627
+ }[status || ""] || "Unknown";
628
+ };
629
+ const currentPlayheadTime = playheadTime || /* @__PURE__ */ new Date();
630
+ const now = currentPlayheadTime.getTime();
631
+ const playheadPercent = showPlayhead && now >= start.getTime() && now <= end.getTime() ? (now - start.getTime()) / totalDuration * 100 : null;
632
+ const lastAutoScrollRef = useRef(0);
633
+ useEffect(() => {
634
+ if (playheadPercent === null) return;
635
+ const container = scrollContainerRef.current;
636
+ if (!container) return;
637
+ const contentWidth = container.scrollWidth;
638
+ const viewportWidth = container.clientWidth;
639
+ if (contentWidth <= viewportWidth) return;
640
+ const timelineContentWidth = contentWidth - trackLabelWidth;
641
+ const playheadPx = trackLabelWidth + playheadPercent / 100 * timelineContentWidth;
642
+ const scrollLeft = container.scrollLeft;
643
+ const visibleLeft = scrollLeft + trackLabelWidth;
644
+ const visibleRight = scrollLeft + viewportWidth;
645
+ const margin = viewportWidth * .15;
646
+ if (playheadPx > visibleRight - margin || playheadPx < visibleLeft + margin) {
647
+ const nowMs = Date.now();
648
+ if (nowMs - lastAutoScrollRef.current < 300) return;
649
+ lastAutoScrollRef.current = nowMs;
650
+ const targetScroll = playheadPx - viewportWidth * .5;
651
+ container.scrollTo({
652
+ left: Math.max(0, targetScroll),
653
+ behavior: "smooth"
654
+ });
655
+ }
656
+ }, [playheadPercent, trackLabelWidth]);
657
+ const hoveredEventData = hoveredEvent ? events.find((e) => e.id === hoveredEvent) : null;
658
+ return /* @__PURE__ */ jsxs("div", {
659
+ style: { position: "relative" },
660
+ children: [
661
+ /* @__PURE__ */ jsx("div", {
662
+ ref: scrollContainerRef,
663
+ className: "zendir-timeline-scroll",
664
+ onScroll: (e) => setScrollX(e.currentTarget.scrollLeft),
665
+ style: {
666
+ overflowX: "auto",
667
+ overflowY: "hidden",
668
+ position: "relative"
669
+ },
670
+ children: /* @__PURE__ */ jsxs("div", {
671
+ style: {
672
+ width: `${zoom * 100}%`,
673
+ minWidth: "100%",
674
+ position: "relative"
675
+ },
676
+ children: [
677
+ /* @__PURE__ */ jsxs("div", {
678
+ style: {
679
+ display: "flex",
680
+ height: 32,
681
+ borderBottom: `1px solid ${tokens.colors.accent.primary}20`,
682
+ position: "sticky",
683
+ top: 0,
684
+ zIndex: Z.trackEventHovered,
685
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
686
+ ...isTransparentTheme && {
687
+ backdropFilter: "blur(12px)",
688
+ WebkitBackdropFilter: "blur(12px)"
689
+ }
690
+ },
691
+ children: [/* @__PURE__ */ jsx("div", {
692
+ style: {
693
+ width: trackLabelWidth,
694
+ flexShrink: 0,
695
+ position: "sticky",
696
+ left: 0,
697
+ zIndex: Z.trackEventHovered + 5,
698
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
699
+ ...isTransparentTheme && {
700
+ backdropFilter: "blur(12px)",
701
+ WebkitBackdropFilter: "blur(12px)"
702
+ },
703
+ borderRight: `1px solid ${tokens.colors.border.muted}30`,
704
+ display: "flex",
705
+ alignItems: "center",
706
+ paddingLeft: 12,
707
+ fontSize: "0.625rem",
708
+ fontWeight: 500,
709
+ color: tokens.colors.text.tertiary,
710
+ textTransform: "uppercase",
711
+ letterSpacing: "0.08em"
712
+ },
713
+ children: "Tracks"
714
+ }), /* @__PURE__ */ jsxs("div", {
715
+ style: {
716
+ flex: 1,
717
+ position: "relative"
718
+ },
719
+ children: [showDayMarkers && dayMarkers.map((marker, index) => /* @__PURE__ */ jsx("div", {
720
+ style: {
721
+ position: "absolute",
722
+ left: `${marker.percent}%`,
723
+ top: 0,
724
+ height: "100%",
725
+ display: "flex",
726
+ flexDirection: "column",
727
+ alignItems: "center",
728
+ zIndex: Z.dayMarker
729
+ },
730
+ children: /* @__PURE__ */ jsx("span", {
731
+ style: {
732
+ fontSize: "0.5rem",
733
+ color: tokens.colors.status.caution,
734
+ fontFamily: tokens.typography.fontFamily.mono,
735
+ whiteSpace: "nowrap",
736
+ backgroundColor: `${tokens.colors.status.caution}20`,
737
+ padding: "1px 4px",
738
+ borderRadius: 2,
739
+ marginTop: 2
740
+ },
741
+ children: marker.time.toLocaleDateString([], {
742
+ month: "short",
743
+ day: "numeric"
744
+ })
745
+ })
746
+ }, `day-${index}`)), timeLabels.map((label, index) => /* @__PURE__ */ jsx("span", {
747
+ style: {
748
+ position: "absolute",
749
+ left: `${label.percent}%`,
750
+ transform: "translateX(-50%)",
751
+ top: 8,
752
+ fontSize: "0.6875rem",
753
+ color: tokens.colors.text.tertiary,
754
+ fontFamily: tokens.typography.fontFamily.mono,
755
+ whiteSpace: "nowrap"
756
+ },
757
+ children: formatTime(label.time)
758
+ }, index))]
759
+ })]
760
+ }),
761
+ tracks.map((track) => /* @__PURE__ */ jsxs("div", {
762
+ "aria-label": track.label,
763
+ style: {
764
+ display: "flex",
765
+ height: track.height || trackHeight,
766
+ borderBottom: `1px solid ${tokens.colors.accent.primary}10`
767
+ },
768
+ children: [/* @__PURE__ */ jsx("div", {
769
+ style: {
770
+ width: trackLabelWidth,
771
+ flexShrink: 0,
772
+ position: "sticky",
773
+ left: 0,
774
+ zIndex: Z.sticky,
775
+ display: "flex",
776
+ alignItems: "center",
777
+ paddingLeft: 12,
778
+ fontSize: "0.6875rem",
779
+ fontWeight: 500,
780
+ color: tokens.colors.text.secondary,
781
+ textTransform: "uppercase",
782
+ letterSpacing: "0.05em",
783
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
784
+ ...isTransparentTheme && {
785
+ backdropFilter: "blur(12px)",
786
+ WebkitBackdropFilter: "blur(12px)"
787
+ },
788
+ borderRight: `1px solid ${tokens.colors.border.muted}30`,
789
+ boxShadow: "2px 0 8px rgba(0,0,0,0.15)"
790
+ },
791
+ children: track.label
792
+ }), /* @__PURE__ */ jsxs("div", {
793
+ style: {
794
+ flex: 1,
795
+ position: "relative",
796
+ height: "100%"
797
+ },
798
+ children: [showDayMarkers && dayMarkers.map((marker, index) => /* @__PURE__ */ jsx("div", { style: {
799
+ position: "absolute",
800
+ left: `${marker.percent}%`,
801
+ top: 0,
802
+ bottom: 0,
803
+ width: 1,
804
+ backgroundColor: `${tokens.colors.status.caution}25`,
805
+ zIndex: Z.trackEvent,
806
+ pointerEvents: "none"
807
+ } }, `day-line-${index}`)), (eventsByTrack[track.id] || []).filter(isEventVisible).map((event) => {
808
+ const { left, width } = getEventPosition(event);
809
+ const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
810
+ const color = getStatusColor(displayStatus);
811
+ const isHovered = hoveredEvent === event.id;
812
+ const overlap = eventOverlaps[event.id];
813
+ const hasOverlap = overlap && overlap.overlapCount > 0;
814
+ const teamAccent = getTimelineTeamAccent(event);
815
+ const cardHeight = hasOverlap ? 30 : 38;
816
+ const stackOffset = overlap ? overlap.stackIndex * 30 : 0;
817
+ const baseTop = ((track.height || trackHeight) - cardHeight) / 2;
818
+ return /* @__PURE__ */ jsxs("button", {
819
+ type: "button",
820
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
821
+ onClick: () => onEventClick?.(event),
822
+ onMouseEnter: (e) => {
823
+ setHoveredEvent(event.id);
824
+ const rect = e.currentTarget.getBoundingClientRect();
825
+ setTooltipPos({
826
+ x: rect.left + rect.width / 2,
827
+ y: rect.top - 8
828
+ });
829
+ },
830
+ onMouseLeave: () => {
831
+ setHoveredEvent(null);
832
+ setTooltipPos(null);
833
+ },
834
+ onFocus: (e) => {
835
+ setHoveredEvent(event.id);
836
+ const rect = e.currentTarget.getBoundingClientRect();
837
+ setTooltipPos({
838
+ x: rect.left + rect.width / 2,
839
+ y: rect.top - 8
840
+ });
841
+ },
842
+ onBlur: () => {
843
+ setHoveredEvent(null);
844
+ setTooltipPos(null);
845
+ },
846
+ className: "zendir-timeline-region",
847
+ style: {
848
+ position: "absolute",
849
+ left,
850
+ width,
851
+ minWidth: 50,
852
+ height: cardHeight,
853
+ top: Math.min(baseTop + stackOffset, (track.height || trackHeight) - cardHeight - 4),
854
+ backgroundColor: "rgba(11, 26, 40, 0.95)",
855
+ border: `1px solid ${isHovered ? color : `${tokens.colors.accent.primary}30`}`,
856
+ borderRadius: "4px",
857
+ cursor: onEventClick ? "pointer" : "default",
858
+ display: "flex",
859
+ alignItems: "center",
860
+ overflow: "hidden",
861
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
862
+ boxShadow: isHovered ? `0 8px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px ${color}60` : "0 2px 6px rgba(0, 0, 0, 0.25)",
863
+ transition: "all 150ms cubic-bezier(0.4, 0, 0.2, 1)",
864
+ transform: isHovered ? "translateY(-3px) scale(1.02)" : "none"
865
+ },
866
+ children: [
867
+ teamAccent ? /* @__PURE__ */ jsx("div", {
868
+ style: {
869
+ position: "absolute",
870
+ top: 0,
871
+ left: 0,
872
+ right: 0,
873
+ height: 2,
874
+ backgroundColor: teamAccent.color,
875
+ borderRadius: "4px 4px 0 0",
876
+ zIndex: 1,
877
+ pointerEvents: "none"
878
+ },
879
+ "aria-hidden": true
880
+ }) : null,
881
+ /* @__PURE__ */ jsxs("div", {
882
+ style: {
883
+ flex: 1,
884
+ padding: `${teamAccent ? 6 : 4}px 8px 4px 8px`,
885
+ overflow: "hidden",
886
+ minWidth: 0,
887
+ display: "flex",
888
+ flexDirection: "column",
889
+ justifyContent: "center"
890
+ },
891
+ children: [/* @__PURE__ */ jsxs("div", {
892
+ style: {
893
+ display: "flex",
894
+ alignItems: "center",
895
+ gap: 6,
896
+ minWidth: 0
897
+ },
898
+ children: [/* @__PURE__ */ jsx(TimelineStatusMarker, {
899
+ status: displayStatus,
900
+ fillColor: color,
901
+ size: hasOverlap ? 8 : 10
902
+ }), /* @__PURE__ */ jsx("span", {
903
+ style: {
904
+ fontSize: "0.6875rem",
905
+ fontWeight: 500,
906
+ color: "#fff",
907
+ whiteSpace: "nowrap",
908
+ overflow: "hidden",
909
+ textOverflow: "ellipsis",
910
+ minWidth: 0
911
+ },
912
+ children: event.title
913
+ })]
914
+ }), event.subtitle && !hasOverlap && /* @__PURE__ */ jsx("div", {
915
+ style: {
916
+ fontSize: "0.5625rem",
917
+ color: "rgba(255, 255, 255, 0.5)",
918
+ whiteSpace: "nowrap",
919
+ overflow: "hidden",
920
+ textOverflow: "ellipsis",
921
+ marginTop: 1,
922
+ paddingLeft: 16
923
+ },
924
+ children: event.subtitle
925
+ })]
926
+ }),
927
+ hasOverlap && overlap.stackIndex === 0 && /* @__PURE__ */ jsxs("div", {
928
+ style: {
929
+ position: "absolute",
930
+ top: -6,
931
+ right: -6,
932
+ minWidth: 18,
933
+ height: 18,
934
+ backgroundColor: tokens.colors.status.caution,
935
+ borderRadius: 9,
936
+ display: "flex",
937
+ alignItems: "center",
938
+ justifyContent: "center",
939
+ fontSize: "0.625rem",
940
+ fontWeight: 700,
941
+ color: "#000",
942
+ boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
943
+ zIndex: Z.sticky - 5
944
+ },
945
+ title: `${overlap.overlapCount + 1} overlapping events`,
946
+ children: ["+", overlap.overlapCount]
947
+ })
948
+ ]
949
+ }, event.id);
950
+ })]
951
+ })]
952
+ }, track.id)),
953
+ playheadPercent !== null && /* @__PURE__ */ jsxs("div", {
954
+ style: {
955
+ position: "absolute",
956
+ top: 0,
957
+ bottom: 0,
958
+ left: `calc(${trackLabelWidth}px + (100% - ${trackLabelWidth}px) * ${playheadPercent} / 100)`,
959
+ width: 2,
960
+ backgroundColor: tokens.colors.accent.primary,
961
+ boxShadow: `0 0 12px ${tokens.colors.accent.primary}`,
962
+ zIndex: Z.playhead,
963
+ pointerEvents: "none",
964
+ transition: "left 0.4s linear"
965
+ },
966
+ children: [/* @__PURE__ */ jsx("div", { style: {
967
+ position: "absolute",
968
+ top: 22,
969
+ left: -5,
970
+ width: 12,
971
+ height: 12,
972
+ backgroundColor: tokens.colors.accent.primary,
973
+ borderRadius: 2,
974
+ transform: "rotate(45deg)",
975
+ boxShadow: `0 0 8px ${tokens.colors.accent.primary}`
976
+ } }), /* @__PURE__ */ jsx("div", {
977
+ style: {
978
+ position: "absolute",
979
+ top: 4,
980
+ left: "50%",
981
+ transform: "translateX(-50%)",
982
+ backgroundColor: tokens.colors.accent.primary,
983
+ color: tokens.colors.background.base,
984
+ fontSize: "0.5625rem",
985
+ fontWeight: 500,
986
+ fontFamily: tokens.typography.fontFamily.mono,
987
+ padding: "2px 6px",
988
+ borderRadius: 3,
989
+ whiteSpace: "nowrap",
990
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)"
991
+ },
992
+ children: formatTime(currentPlayheadTime, true)
993
+ })]
994
+ })
995
+ ]
996
+ })
997
+ }),
998
+ hoveredEventData && tooltipPos && (() => {
999
+ const tooltipColor = getStatusColor(hoveredEventData.status);
1000
+ const teamTip = getTimelineTeamAccent(hoveredEventData);
1001
+ return /* @__PURE__ */ jsxs("div", {
1002
+ style: {
1003
+ position: "fixed",
1004
+ left: tooltipPos.x,
1005
+ top: tooltipPos.y,
1006
+ transform: "translate(-50%, -100%)",
1007
+ zIndex: Z.tooltip,
1008
+ pointerEvents: "none",
1009
+ animation: "zendir-tooltip-appear 150ms ease"
1010
+ },
1011
+ children: [/* @__PURE__ */ jsxs("div", {
1012
+ style: {
1013
+ backgroundColor: "rgba(15, 23, 42, 0.98)",
1014
+ border: `1px solid ${tooltipColor}50`,
1015
+ borderRadius: tokens.borderRadius.md,
1016
+ padding: "12px 16px",
1017
+ minWidth: 220,
1018
+ maxWidth: 320,
1019
+ boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${tooltipColor}30`,
1020
+ backdropFilter: "blur(12px)"
1021
+ },
1022
+ children: [
1023
+ /* @__PURE__ */ jsxs("div", {
1024
+ style: {
1025
+ display: "flex",
1026
+ alignItems: "center",
1027
+ gap: 8,
1028
+ marginBottom: 8
1029
+ },
1030
+ children: [
1031
+ /* @__PURE__ */ jsx(TimelineStatusMarker, {
1032
+ status: hoveredEventData.status,
1033
+ fillColor: tooltipColor,
1034
+ size: 10
1035
+ }),
1036
+ /* @__PURE__ */ jsx("span", {
1037
+ style: {
1038
+ fontSize: "0.75rem",
1039
+ fontWeight: 500,
1040
+ color: tooltipColor,
1041
+ textTransform: "uppercase",
1042
+ letterSpacing: "0.05em"
1043
+ },
1044
+ children: getStatusLabel(hoveredEventData.status)
1045
+ }),
1046
+ hoveredEventData.badge && /* @__PURE__ */ jsx("span", {
1047
+ style: {
1048
+ marginLeft: "auto",
1049
+ fontSize: "0.625rem",
1050
+ fontWeight: 500,
1051
+ padding: "2px 6px",
1052
+ borderRadius: tokens.borderRadius.sm,
1053
+ backgroundColor: `${tooltipColor}20`,
1054
+ color: tooltipColor
1055
+ },
1056
+ children: hoveredEventData.badge
1057
+ })
1058
+ ]
1059
+ }),
1060
+ teamTip && /* @__PURE__ */ jsxs("div", {
1061
+ style: {
1062
+ display: "flex",
1063
+ alignItems: "center",
1064
+ gap: 6,
1065
+ marginBottom: 6
1066
+ },
1067
+ children: [/* @__PURE__ */ jsx("div", { style: {
1068
+ width: 8,
1069
+ height: 8,
1070
+ borderRadius: 2,
1071
+ backgroundColor: teamTip.color,
1072
+ flexShrink: 0
1073
+ } }), /* @__PURE__ */ jsx("span", {
1074
+ style: {
1075
+ fontSize: "0.6875rem",
1076
+ color: teamTip.color,
1077
+ fontWeight: 500
1078
+ },
1079
+ children: getTimelineTeamDisplayLabel(hoveredEventData, teamTip)
1080
+ })]
1081
+ }),
1082
+ /* @__PURE__ */ jsx("div", {
1083
+ style: {
1084
+ fontSize: "0.875rem",
1085
+ fontWeight: 500,
1086
+ color: "#fff",
1087
+ marginBottom: 4
1088
+ },
1089
+ children: hoveredEventData.title
1090
+ }),
1091
+ hoveredEventData.subtitle && /* @__PURE__ */ jsx("div", {
1092
+ style: {
1093
+ fontSize: "0.75rem",
1094
+ color: "rgba(255, 255, 255, 0.6)",
1095
+ marginBottom: 12
1096
+ },
1097
+ children: hoveredEventData.subtitle
1098
+ }),
1099
+ /* @__PURE__ */ jsxs("div", {
1100
+ style: {
1101
+ display: "grid",
1102
+ gridTemplateColumns: "auto 1fr",
1103
+ gap: "6px 12px",
1104
+ fontSize: "0.6875rem",
1105
+ borderTop: `1px solid ${tokens.colors.border.muted}30`,
1106
+ paddingTop: 10
1107
+ },
1108
+ children: [
1109
+ /* @__PURE__ */ jsx("span", {
1110
+ style: { color: tokens.colors.text.tertiary },
1111
+ children: "Start:"
1112
+ }),
1113
+ /* @__PURE__ */ jsx("span", {
1114
+ style: {
1115
+ color: tokens.colors.text.primary,
1116
+ fontFamily: tokens.typography.fontFamily.mono
1117
+ },
1118
+ children: formatTime(hoveredEventData.start, true)
1119
+ }),
1120
+ hoveredEventData.end && /* @__PURE__ */ jsxs(Fragment, { children: [
1121
+ /* @__PURE__ */ jsx("span", {
1122
+ style: { color: tokens.colors.text.tertiary },
1123
+ children: "End:"
1124
+ }),
1125
+ /* @__PURE__ */ jsx("span", {
1126
+ style: {
1127
+ color: tokens.colors.text.primary,
1128
+ fontFamily: tokens.typography.fontFamily.mono
1129
+ },
1130
+ children: formatTime(hoveredEventData.end, true)
1131
+ }),
1132
+ /* @__PURE__ */ jsx("span", {
1133
+ style: { color: tokens.colors.text.tertiary },
1134
+ children: "Duration:"
1135
+ }),
1136
+ /* @__PURE__ */ jsx("span", {
1137
+ style: {
1138
+ color: tokens.colors.accent.secondary,
1139
+ fontWeight: 500
1140
+ },
1141
+ children: formatDuration(hoveredEventData.end.getTime() - hoveredEventData.start.getTime())
1142
+ })
1143
+ ] }),
1144
+ /* @__PURE__ */ jsx("span", {
1145
+ style: { color: tokens.colors.text.tertiary },
1146
+ children: "UTC:"
1147
+ }),
1148
+ /* @__PURE__ */ jsx("span", {
1149
+ style: {
1150
+ color: tokens.colors.text.secondary,
1151
+ fontFamily: tokens.typography.fontFamily.mono,
1152
+ fontSize: "0.625rem"
1153
+ },
1154
+ children: formatUTC(hoveredEventData.start)
1155
+ })
1156
+ ]
1157
+ }),
1158
+ onEventClick && /* @__PURE__ */ jsx("div", {
1159
+ style: {
1160
+ marginTop: 10,
1161
+ paddingTop: 8,
1162
+ borderTop: `1px solid ${tokens.colors.border.muted}20`,
1163
+ fontSize: "0.625rem",
1164
+ color: tokens.colors.text.tertiary,
1165
+ textAlign: "center"
1166
+ },
1167
+ children: "Click for details"
1168
+ })
1169
+ ]
1170
+ }), /* @__PURE__ */ jsx("div", { style: {
1171
+ position: "absolute",
1172
+ bottom: -6,
1173
+ left: "50%",
1174
+ transform: "translateX(-50%) rotate(45deg)",
1175
+ width: 12,
1176
+ height: 12,
1177
+ backgroundColor: "rgba(15, 23, 42, 0.98)",
1178
+ borderRight: `1px solid ${tooltipColor}50`,
1179
+ borderBottom: `1px solid ${tooltipColor}50`
1180
+ } })]
1181
+ });
1182
+ })(),
1183
+ /* @__PURE__ */ jsx("style", { children: `
1194
1184
  .zendir-timeline-region:hover {
1195
1185
  z-index: 100 !important;
1196
1186
  }
@@ -1223,382 +1213,366 @@ const ChartView = memo(function ChartView2({
1223
1213
  }
1224
1214
  }
1225
1215
  ` })
1226
- ] });
1216
+ ]
1217
+ });
1227
1218
  });
1228
- const ScatterView = memo(function ScatterView2({
1229
- events,
1230
- tracks,
1231
- start,
1232
- end,
1233
- trackHeight,
1234
- onEventClick,
1235
- timeFormat = "relative",
1236
- referenceTime,
1237
- zoom = 1
1238
- }) {
1239
- const { tokens } = useTheme();
1240
- const [hoveredEvent, setHoveredEvent] = useState(null);
1241
- const [tooltipPosition, setTooltipPosition] = useState(null);
1242
- const totalDuration = end.getTime() - start.getTime();
1243
- const trackLabelWidth = 140;
1244
- const refTime = referenceTime || start;
1245
- const formatTimeLabel = useCallback((date) => {
1246
- if (timeFormat === "relative") {
1247
- const diff = date.getTime() - refTime.getTime();
1248
- const totalSeconds = Math.floor(Math.abs(diff) / 1e3);
1249
- const hours = Math.floor(totalSeconds / 3600);
1250
- const minutes = Math.floor(totalSeconds % 3600 / 60);
1251
- const seconds = totalSeconds % 60;
1252
- if (hours > 0) {
1253
- return `${hours}h ${minutes}m`;
1254
- }
1255
- return `${minutes}m ${seconds}s`;
1256
- } else if (timeFormat === "utc") {
1257
- return date.toISOString().slice(11, 19) + " UTC";
1258
- }
1259
- return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
1260
- }, [timeFormat, refTime]);
1261
- const timeLabels = useMemo(() => {
1262
- const labels = [];
1263
- const baseLabelCount = 8;
1264
- const labelCount = Math.round(baseLabelCount * zoom);
1265
- for (let i = 0; i <= labelCount; i++) {
1266
- const percent = i / labelCount * 100;
1267
- const time = new Date(start.getTime() + totalDuration * percent / 100);
1268
- labels.push({ time, percent });
1269
- }
1270
- return labels;
1271
- }, [start, totalDuration, zoom]);
1272
- const eventsByTrack = useMemo(() => {
1273
- const grouped = {};
1274
- tracks.forEach((track) => {
1275
- grouped[track.id] = events.filter((e) => e.track === track.id);
1276
- });
1277
- const unassigned = events.filter((e) => !e.track);
1278
- if (unassigned.length > 0 && tracks.length > 0) {
1279
- grouped[tracks[0].id] = [...grouped[tracks[0].id] || [], ...unassigned];
1280
- }
1281
- return grouped;
1282
- }, [events, tracks]);
1283
- const getEventPosition = (event) => {
1284
- const eventTime = event.start.getTime();
1285
- return (eventTime - start.getTime()) / totalDuration * 100;
1286
- };
1287
- const getStatusColor = (status) => {
1288
- if (!status) return tokens.colors.accent.primary;
1289
- return tokens.colors.status[status] || tokens.colors.accent.primary;
1290
- };
1291
- const hoveredEventData = events.find((e) => e.id === hoveredEvent);
1292
- return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1293
- /* @__PURE__ */ jsxs(
1294
- "div",
1295
- {
1296
- style: {
1297
- width: `${zoom * 100}%`,
1298
- minWidth: "100%",
1299
- position: "relative"
1300
- },
1301
- children: [
1302
- /* @__PURE__ */ jsx(
1303
- "div",
1304
- {
1305
- style: {
1306
- position: "relative",
1307
- height: 32,
1308
- marginLeft: trackLabelWidth,
1309
- borderBottom: `1px solid ${tokens.colors.accent.primary}15`
1310
- },
1311
- children: timeLabels.map((label, index) => /* @__PURE__ */ jsx(
1312
- "span",
1313
- {
1314
- style: {
1315
- position: "absolute",
1316
- left: `${label.percent}%`,
1317
- transform: "translateX(-50%)",
1318
- top: 8,
1319
- fontSize: "0.6875rem",
1320
- color: tokens.colors.text.tertiary,
1321
- fontFamily: tokens.typography.fontFamily.mono,
1322
- whiteSpace: "nowrap",
1323
- fontWeight: 500
1324
- },
1325
- children: formatTimeLabel(label.time)
1326
- },
1327
- index
1328
- ))
1329
- }
1330
- ),
1331
- tracks.map((track, trackIndex) => /* @__PURE__ */ jsxs(
1332
- "div",
1333
- {
1334
- style: {
1335
- position: "relative",
1336
- display: "flex",
1337
- alignItems: "center",
1338
- height: track.height || trackHeight,
1339
- borderBottom: trackIndex < tracks.length - 1 ? `1px solid ${tokens.colors.border.muted}20` : "none"
1340
- },
1341
- children: [
1342
- /* @__PURE__ */ jsx(
1343
- "div",
1344
- {
1345
- style: {
1346
- width: trackLabelWidth,
1347
- flexShrink: 0,
1348
- padding: "0 16px",
1349
- fontSize: "0.8125rem",
1350
- fontWeight: 500,
1351
- // AstroUXDS medium (was 600)
1352
- color: tokens.colors.text.primary
1353
- },
1354
- children: track.label
1355
- }
1356
- ),
1357
- /* @__PURE__ */ jsxs(
1358
- "div",
1359
- {
1360
- style: {
1361
- flex: 1,
1362
- position: "relative",
1363
- height: "100%",
1364
- display: "flex",
1365
- alignItems: "center"
1366
- },
1367
- children: [
1368
- /* @__PURE__ */ jsx(
1369
- "div",
1370
- {
1371
- style: {
1372
- position: "absolute",
1373
- left: 0,
1374
- right: 0,
1375
- height: 1,
1376
- backgroundColor: tokens.colors.border.muted,
1377
- opacity: 0.3
1378
- }
1379
- }
1380
- ),
1381
- (eventsByTrack[track.id] || []).map((event) => {
1382
- const leftPercent = getEventPosition(event);
1383
- const color = getStatusColor(event.status);
1384
- const isHovered = hoveredEvent === event.id;
1385
- const dotSize = isHovered ? 16 : 12;
1386
- const hitboxPad = Math.max(0, (24 - dotSize) / 2);
1387
- const effectiveStatus = event.status ?? "normal";
1388
- const scatterTeamAccent = getTimelineTeamAccent(event);
1389
- return /* @__PURE__ */ jsx(
1390
- "button",
1391
- {
1392
- type: "button",
1393
- "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
1394
- onClick: () => onEventClick == null ? void 0 : onEventClick(event),
1395
- onMouseEnter: (e) => {
1396
- setHoveredEvent(event.id);
1397
- const rect = e.currentTarget.getBoundingClientRect();
1398
- setTooltipPosition({ x: rect.left + rect.width / 2, y: rect.top });
1399
- },
1400
- onMouseLeave: () => {
1401
- setHoveredEvent(null);
1402
- setTooltipPosition(null);
1403
- },
1404
- onFocus: (e) => {
1405
- setHoveredEvent(event.id);
1406
- const rect = e.currentTarget.getBoundingClientRect();
1407
- setTooltipPosition({ x: rect.left + rect.width / 2, y: rect.top });
1408
- },
1409
- onBlur: () => {
1410
- setHoveredEvent(null);
1411
- setTooltipPosition(null);
1412
- },
1413
- style: {
1414
- position: "absolute",
1415
- left: `${leftPercent}%`,
1416
- transform: "translateX(-50%)",
1417
- /* visible size + transparent padding = ≥24px touch/click target */
1418
- width: dotSize,
1419
- height: dotSize,
1420
- padding: hitboxPad,
1421
- background: "none",
1422
- border: "none",
1423
- cursor: "pointer",
1424
- transition: "all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)",
1425
- zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
1426
- lineHeight: 0,
1427
- borderRadius: "50%",
1428
- boxSizing: "content-box",
1429
- boxShadow: scatterTeamAccent ? `0 0 0 2px ${scatterTeamAccent.color}` : void 0
1430
- },
1431
- children: /* @__PURE__ */ jsx(TimelineStatusMarker, { status: effectiveStatus, fillColor: color, size: dotSize })
1432
- },
1433
- event.id
1434
- );
1435
- })
1436
- ]
1437
- }
1438
- )
1439
- ]
1440
- },
1441
- track.id
1442
- ))
1443
- ]
1444
- }
1445
- ),
1446
- hoveredEventData && tooltipPosition && (() => {
1447
- const scatterTipColor = getStatusColor(hoveredEventData.status);
1448
- const scatterTeamTip = getTimelineTeamAccent(hoveredEventData);
1449
- return /* @__PURE__ */ jsxs(
1450
- "div",
1451
- {
1452
- className: "zendir-scatter-tooltip",
1453
- style: {
1454
- position: "fixed",
1455
- left: tooltipPosition.x,
1456
- top: tooltipPosition.y - 12,
1457
- transform: "translate(-50%, -100%)",
1458
- backgroundColor: "rgba(11, 26, 40, 0.98)",
1459
- border: `1px solid ${scatterTipColor}40`,
1460
- borderRadius: tokens.borderRadius.lg,
1461
- padding: "12px 16px",
1462
- minWidth: 200,
1463
- maxWidth: 320,
1464
- zIndex: Z.tooltip,
1465
- boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${scatterTipColor}20`,
1466
- backdropFilter: "blur(12px)",
1467
- animation: "zendir-tooltip-in 150ms cubic-bezier(0.34, 1.56, 0.64, 1)"
1468
- },
1469
- children: [
1470
- /* @__PURE__ */ jsx(
1471
- "div",
1472
- {
1473
- style: {
1474
- position: "absolute",
1475
- bottom: -6,
1476
- left: "50%",
1477
- transform: "translateX(-50%) rotate(45deg)",
1478
- width: 12,
1479
- height: 12,
1480
- backgroundColor: "rgba(11, 26, 40, 0.98)",
1481
- borderRight: `1px solid ${scatterTipColor}40`,
1482
- borderBottom: `1px solid ${scatterTipColor}40`
1483
- }
1484
- }
1485
- ),
1486
- hoveredEventData.status && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }, children: [
1487
- /* @__PURE__ */ jsx(
1488
- TimelineStatusMarker,
1489
- {
1490
- status: hoveredEventData.status,
1491
- fillColor: scatterTipColor,
1492
- size: 10
1493
- }
1494
- ),
1495
- /* @__PURE__ */ jsx(
1496
- "span",
1497
- {
1498
- style: {
1499
- fontSize: "0.75rem",
1500
- fontWeight: 500,
1501
- color: scatterTipColor,
1502
- textTransform: "uppercase",
1503
- letterSpacing: "0.05em"
1504
- },
1505
- children: hoveredEventData.status
1506
- }
1507
- ),
1508
- hoveredEventData.badge && /* @__PURE__ */ jsx(
1509
- "span",
1510
- {
1511
- style: {
1512
- marginLeft: "auto",
1513
- fontSize: "0.625rem",
1514
- fontWeight: 500,
1515
- padding: "2px 6px",
1516
- borderRadius: tokens.borderRadius.sm,
1517
- backgroundColor: `${scatterTipColor}20`,
1518
- color: scatterTipColor
1519
- },
1520
- children: hoveredEventData.badge
1521
- }
1522
- )
1523
- ] }),
1524
- scatterTeamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1525
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: scatterTeamTip.color, flexShrink: 0 } }),
1526
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: scatterTeamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, scatterTeamTip) })
1527
- ] }),
1528
- /* @__PURE__ */ jsx(
1529
- "div",
1530
- {
1531
- style: {
1532
- fontSize: "0.875rem",
1533
- fontWeight: 500,
1534
- color: "#fff",
1535
- marginBottom: 8
1536
- },
1537
- children: hoveredEventData.title
1538
- }
1539
- ),
1540
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
1541
- /* @__PURE__ */ jsxs(
1542
- "div",
1543
- {
1544
- style: {
1545
- display: "flex",
1546
- justifyContent: "space-between",
1547
- fontSize: "0.75rem"
1548
- },
1549
- children: [
1550
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "Time:" }),
1551
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.secondary, fontFamily: tokens.typography.fontFamily.mono }, children: formatTimeLabel(hoveredEventData.start) })
1552
- ]
1553
- }
1554
- ),
1555
- hoveredEventData.badge && /* @__PURE__ */ jsxs(
1556
- "div",
1557
- {
1558
- style: {
1559
- display: "flex",
1560
- justifyContent: "space-between",
1561
- fontSize: "0.75rem"
1562
- },
1563
- children: [
1564
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "Trigger:" }),
1565
- /* @__PURE__ */ jsx("span", { style: { color: safeAccentText(tokens.colors.accent.primary), fontWeight: 500 }, children: hoveredEventData.badge })
1566
- ]
1567
- }
1568
- ),
1569
- /* @__PURE__ */ jsxs(
1570
- "div",
1571
- {
1572
- style: {
1573
- display: "flex",
1574
- justifyContent: "space-between",
1575
- fontSize: "0.75rem"
1576
- },
1577
- children: [
1578
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: "UTC:" }),
1579
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.secondary, fontFamily: tokens.typography.fontFamily.mono }, children: hoveredEventData.start.toISOString().replace("T", " ").slice(0, 19) })
1580
- ]
1581
- }
1582
- ),
1583
- hoveredEventData.subtitle && /* @__PURE__ */ jsx(
1584
- "div",
1585
- {
1586
- style: {
1587
- marginTop: 4,
1588
- paddingTop: 8,
1589
- borderTop: `1px solid ${tokens.colors.border.muted}`,
1590
- fontSize: "0.75rem",
1591
- color: tokens.colors.text.secondary
1592
- },
1593
- children: hoveredEventData.subtitle
1594
- }
1595
- )
1596
- ] })
1597
- ]
1598
- }
1599
- );
1600
- })(),
1601
- /* @__PURE__ */ jsx("style", { children: `
1219
+ var ScatterView = memo(function ScatterView({ events, tracks, start, end, trackHeight, onEventClick, timeFormat = "relative", referenceTime, zoom = 1 }) {
1220
+ const { tokens } = useTheme();
1221
+ const [hoveredEvent, setHoveredEvent] = useState(null);
1222
+ const [tooltipPosition, setTooltipPosition] = useState(null);
1223
+ const totalDuration = end.getTime() - start.getTime();
1224
+ const trackLabelWidth = 140;
1225
+ const refTime = referenceTime || start;
1226
+ const formatTimeLabel = useCallback((date) => {
1227
+ if (timeFormat === "relative") {
1228
+ const diff = date.getTime() - refTime.getTime();
1229
+ const totalSeconds = Math.floor(Math.abs(diff) / 1e3);
1230
+ const hours = Math.floor(totalSeconds / 3600);
1231
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1232
+ const seconds = totalSeconds % 60;
1233
+ if (hours > 0) return `${hours}h ${minutes}m`;
1234
+ return `${minutes}m ${seconds}s`;
1235
+ } else if (timeFormat === "utc") return date.toISOString().slice(11, 19) + " UTC";
1236
+ return date.toLocaleTimeString([], {
1237
+ hour: "2-digit",
1238
+ minute: "2-digit"
1239
+ });
1240
+ }, [timeFormat, refTime]);
1241
+ const timeLabels = useMemo(() => {
1242
+ const labels = [];
1243
+ const labelCount = Math.round(8 * zoom);
1244
+ for (let i = 0; i <= labelCount; i++) {
1245
+ const percent = i / labelCount * 100;
1246
+ const time = new Date(start.getTime() + totalDuration * percent / 100);
1247
+ labels.push({
1248
+ time,
1249
+ percent
1250
+ });
1251
+ }
1252
+ return labels;
1253
+ }, [
1254
+ start,
1255
+ totalDuration,
1256
+ zoom
1257
+ ]);
1258
+ const eventsByTrack = useMemo(() => {
1259
+ const grouped = {};
1260
+ tracks.forEach((track) => {
1261
+ grouped[track.id] = events.filter((e) => e.track === track.id);
1262
+ });
1263
+ const unassigned = events.filter((e) => !e.track);
1264
+ if (unassigned.length > 0 && tracks.length > 0) grouped[tracks[0].id] = [...grouped[tracks[0].id] || [], ...unassigned];
1265
+ return grouped;
1266
+ }, [events, tracks]);
1267
+ const getEventPosition = (event) => {
1268
+ return (event.start.getTime() - start.getTime()) / totalDuration * 100;
1269
+ };
1270
+ const getStatusColor = (status) => {
1271
+ if (!status) return tokens.colors.accent.primary;
1272
+ return tokens.colors.status[status] || tokens.colors.accent.primary;
1273
+ };
1274
+ const hoveredEventData = events.find((e) => e.id === hoveredEvent);
1275
+ return /* @__PURE__ */ jsxs("div", {
1276
+ style: { position: "relative" },
1277
+ children: [
1278
+ /* @__PURE__ */ jsxs("div", {
1279
+ style: {
1280
+ width: `${zoom * 100}%`,
1281
+ minWidth: "100%",
1282
+ position: "relative"
1283
+ },
1284
+ children: [/* @__PURE__ */ jsx("div", {
1285
+ style: {
1286
+ position: "relative",
1287
+ height: 32,
1288
+ marginLeft: trackLabelWidth,
1289
+ borderBottom: `1px solid ${tokens.colors.accent.primary}15`
1290
+ },
1291
+ children: timeLabels.map((label, index) => /* @__PURE__ */ jsx("span", {
1292
+ style: {
1293
+ position: "absolute",
1294
+ left: `${label.percent}%`,
1295
+ transform: "translateX(-50%)",
1296
+ top: 8,
1297
+ fontSize: "0.6875rem",
1298
+ color: tokens.colors.text.tertiary,
1299
+ fontFamily: tokens.typography.fontFamily.mono,
1300
+ whiteSpace: "nowrap",
1301
+ fontWeight: 500
1302
+ },
1303
+ children: formatTimeLabel(label.time)
1304
+ }, index))
1305
+ }), tracks.map((track, trackIndex) => /* @__PURE__ */ jsxs("div", {
1306
+ style: {
1307
+ position: "relative",
1308
+ display: "flex",
1309
+ alignItems: "center",
1310
+ height: track.height || trackHeight,
1311
+ borderBottom: trackIndex < tracks.length - 1 ? `1px solid ${tokens.colors.border.muted}20` : "none"
1312
+ },
1313
+ children: [/* @__PURE__ */ jsx("div", {
1314
+ style: {
1315
+ width: trackLabelWidth,
1316
+ flexShrink: 0,
1317
+ padding: "0 16px",
1318
+ fontSize: "0.8125rem",
1319
+ fontWeight: 500,
1320
+ color: tokens.colors.text.primary
1321
+ },
1322
+ children: track.label
1323
+ }), /* @__PURE__ */ jsxs("div", {
1324
+ style: {
1325
+ flex: 1,
1326
+ position: "relative",
1327
+ height: "100%",
1328
+ display: "flex",
1329
+ alignItems: "center"
1330
+ },
1331
+ children: [/* @__PURE__ */ jsx("div", { style: {
1332
+ position: "absolute",
1333
+ left: 0,
1334
+ right: 0,
1335
+ height: 1,
1336
+ backgroundColor: tokens.colors.border.muted,
1337
+ opacity: .3
1338
+ } }), (eventsByTrack[track.id] || []).map((event) => {
1339
+ const leftPercent = getEventPosition(event);
1340
+ const color = getStatusColor(event.status);
1341
+ const isHovered = hoveredEvent === event.id;
1342
+ const dotSize = isHovered ? 16 : 12;
1343
+ const hitboxPad = Math.max(0, (24 - dotSize) / 2);
1344
+ const effectiveStatus = event.status ?? "normal";
1345
+ const scatterTeamAccent = getTimelineTeamAccent(event);
1346
+ return /* @__PURE__ */ jsx("button", {
1347
+ type: "button",
1348
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
1349
+ onClick: () => onEventClick?.(event),
1350
+ onMouseEnter: (e) => {
1351
+ setHoveredEvent(event.id);
1352
+ const rect = e.currentTarget.getBoundingClientRect();
1353
+ setTooltipPosition({
1354
+ x: rect.left + rect.width / 2,
1355
+ y: rect.top
1356
+ });
1357
+ },
1358
+ onMouseLeave: () => {
1359
+ setHoveredEvent(null);
1360
+ setTooltipPosition(null);
1361
+ },
1362
+ onFocus: (e) => {
1363
+ setHoveredEvent(event.id);
1364
+ const rect = e.currentTarget.getBoundingClientRect();
1365
+ setTooltipPosition({
1366
+ x: rect.left + rect.width / 2,
1367
+ y: rect.top
1368
+ });
1369
+ },
1370
+ onBlur: () => {
1371
+ setHoveredEvent(null);
1372
+ setTooltipPosition(null);
1373
+ },
1374
+ style: {
1375
+ position: "absolute",
1376
+ left: `${leftPercent}%`,
1377
+ transform: "translateX(-50%)",
1378
+ width: dotSize,
1379
+ height: dotSize,
1380
+ padding: hitboxPad,
1381
+ background: "none",
1382
+ border: "none",
1383
+ cursor: "pointer",
1384
+ transition: "all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)",
1385
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
1386
+ lineHeight: 0,
1387
+ borderRadius: "50%",
1388
+ boxSizing: "content-box",
1389
+ boxShadow: scatterTeamAccent ? `0 0 0 2px ${scatterTeamAccent.color}` : void 0
1390
+ },
1391
+ children: /* @__PURE__ */ jsx(TimelineStatusMarker, {
1392
+ status: effectiveStatus,
1393
+ fillColor: color,
1394
+ size: dotSize
1395
+ })
1396
+ }, event.id);
1397
+ })]
1398
+ })]
1399
+ }, track.id))]
1400
+ }),
1401
+ hoveredEventData && tooltipPosition && (() => {
1402
+ const scatterTipColor = getStatusColor(hoveredEventData.status);
1403
+ const scatterTeamTip = getTimelineTeamAccent(hoveredEventData);
1404
+ return /* @__PURE__ */ jsxs("div", {
1405
+ className: "zendir-scatter-tooltip",
1406
+ style: {
1407
+ position: "fixed",
1408
+ left: tooltipPosition.x,
1409
+ top: tooltipPosition.y - 12,
1410
+ transform: "translate(-50%, -100%)",
1411
+ backgroundColor: "rgba(11, 26, 40, 0.98)",
1412
+ border: `1px solid ${scatterTipColor}40`,
1413
+ borderRadius: tokens.borderRadius.lg,
1414
+ padding: "12px 16px",
1415
+ minWidth: 200,
1416
+ maxWidth: 320,
1417
+ zIndex: Z.tooltip,
1418
+ boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${scatterTipColor}20`,
1419
+ backdropFilter: "blur(12px)",
1420
+ animation: "zendir-tooltip-in 150ms cubic-bezier(0.34, 1.56, 0.64, 1)"
1421
+ },
1422
+ children: [
1423
+ /* @__PURE__ */ jsx("div", { style: {
1424
+ position: "absolute",
1425
+ bottom: -6,
1426
+ left: "50%",
1427
+ transform: "translateX(-50%) rotate(45deg)",
1428
+ width: 12,
1429
+ height: 12,
1430
+ backgroundColor: "rgba(11, 26, 40, 0.98)",
1431
+ borderRight: `1px solid ${scatterTipColor}40`,
1432
+ borderBottom: `1px solid ${scatterTipColor}40`
1433
+ } }),
1434
+ hoveredEventData.status && /* @__PURE__ */ jsxs("div", {
1435
+ style: {
1436
+ display: "flex",
1437
+ alignItems: "center",
1438
+ gap: 8,
1439
+ marginBottom: 8
1440
+ },
1441
+ children: [
1442
+ /* @__PURE__ */ jsx(TimelineStatusMarker, {
1443
+ status: hoveredEventData.status,
1444
+ fillColor: scatterTipColor,
1445
+ size: 10
1446
+ }),
1447
+ /* @__PURE__ */ jsx("span", {
1448
+ style: {
1449
+ fontSize: "0.75rem",
1450
+ fontWeight: 500,
1451
+ color: scatterTipColor,
1452
+ textTransform: "uppercase",
1453
+ letterSpacing: "0.05em"
1454
+ },
1455
+ children: hoveredEventData.status
1456
+ }),
1457
+ hoveredEventData.badge && /* @__PURE__ */ jsx("span", {
1458
+ style: {
1459
+ marginLeft: "auto",
1460
+ fontSize: "0.625rem",
1461
+ fontWeight: 500,
1462
+ padding: "2px 6px",
1463
+ borderRadius: tokens.borderRadius.sm,
1464
+ backgroundColor: `${scatterTipColor}20`,
1465
+ color: scatterTipColor
1466
+ },
1467
+ children: hoveredEventData.badge
1468
+ })
1469
+ ]
1470
+ }),
1471
+ scatterTeamTip && /* @__PURE__ */ jsxs("div", {
1472
+ style: {
1473
+ display: "flex",
1474
+ alignItems: "center",
1475
+ gap: 6,
1476
+ marginBottom: 6
1477
+ },
1478
+ children: [/* @__PURE__ */ jsx("div", { style: {
1479
+ width: 8,
1480
+ height: 8,
1481
+ borderRadius: 2,
1482
+ backgroundColor: scatterTeamTip.color,
1483
+ flexShrink: 0
1484
+ } }), /* @__PURE__ */ jsx("span", {
1485
+ style: {
1486
+ fontSize: "0.6875rem",
1487
+ color: scatterTeamTip.color,
1488
+ fontWeight: 500
1489
+ },
1490
+ children: getTimelineTeamDisplayLabel(hoveredEventData, scatterTeamTip)
1491
+ })]
1492
+ }),
1493
+ /* @__PURE__ */ jsx("div", {
1494
+ style: {
1495
+ fontSize: "0.875rem",
1496
+ fontWeight: 500,
1497
+ color: "#fff",
1498
+ marginBottom: 8
1499
+ },
1500
+ children: hoveredEventData.title
1501
+ }),
1502
+ /* @__PURE__ */ jsxs("div", {
1503
+ style: {
1504
+ display: "flex",
1505
+ flexDirection: "column",
1506
+ gap: 4
1507
+ },
1508
+ children: [
1509
+ /* @__PURE__ */ jsxs("div", {
1510
+ style: {
1511
+ display: "flex",
1512
+ justifyContent: "space-between",
1513
+ fontSize: "0.75rem"
1514
+ },
1515
+ children: [/* @__PURE__ */ jsx("span", {
1516
+ style: { color: tokens.colors.text.tertiary },
1517
+ children: "Time:"
1518
+ }), /* @__PURE__ */ jsx("span", {
1519
+ style: {
1520
+ color: tokens.colors.text.secondary,
1521
+ fontFamily: tokens.typography.fontFamily.mono
1522
+ },
1523
+ children: formatTimeLabel(hoveredEventData.start)
1524
+ })]
1525
+ }),
1526
+ hoveredEventData.badge && /* @__PURE__ */ jsxs("div", {
1527
+ style: {
1528
+ display: "flex",
1529
+ justifyContent: "space-between",
1530
+ fontSize: "0.75rem"
1531
+ },
1532
+ children: [/* @__PURE__ */ jsx("span", {
1533
+ style: { color: tokens.colors.text.tertiary },
1534
+ children: "Trigger:"
1535
+ }), /* @__PURE__ */ jsx("span", {
1536
+ style: {
1537
+ color: safeAccentText(tokens.colors.accent.primary),
1538
+ fontWeight: 500
1539
+ },
1540
+ children: hoveredEventData.badge
1541
+ })]
1542
+ }),
1543
+ /* @__PURE__ */ jsxs("div", {
1544
+ style: {
1545
+ display: "flex",
1546
+ justifyContent: "space-between",
1547
+ fontSize: "0.75rem"
1548
+ },
1549
+ children: [/* @__PURE__ */ jsx("span", {
1550
+ style: { color: tokens.colors.text.tertiary },
1551
+ children: "UTC:"
1552
+ }), /* @__PURE__ */ jsx("span", {
1553
+ style: {
1554
+ color: tokens.colors.text.secondary,
1555
+ fontFamily: tokens.typography.fontFamily.mono
1556
+ },
1557
+ children: hoveredEventData.start.toISOString().replace("T", " ").slice(0, 19)
1558
+ })]
1559
+ }),
1560
+ hoveredEventData.subtitle && /* @__PURE__ */ jsx("div", {
1561
+ style: {
1562
+ marginTop: 4,
1563
+ paddingTop: 8,
1564
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
1565
+ fontSize: "0.75rem",
1566
+ color: tokens.colors.text.secondary
1567
+ },
1568
+ children: hoveredEventData.subtitle
1569
+ })
1570
+ ]
1571
+ })
1572
+ ]
1573
+ });
1574
+ })(),
1575
+ /* @__PURE__ */ jsx("style", { children: `
1602
1576
  @keyframes zendir-tooltip-in {
1603
1577
  from {
1604
1578
  opacity: 0;
@@ -1614,864 +1588,843 @@ const ScatterView = memo(function ScatterView2({
1614
1588
  position: absolute;
1615
1589
  inset: 0;
1616
1590
  border-radius: inherit;
1617
- background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, transparent 50%);
1591
+ background: linear-gradient(135deg, ${tokens.colors.accent.primary}1a 0%, transparent 50%);
1618
1592
  pointer-events: none;
1619
1593
  }
1620
1594
  ` })
1621
- ] });
1595
+ ]
1596
+ });
1622
1597
  });
1623
1598
  function buildTeamFilterOptions(events, tokens, categoryLabel = "team") {
1624
- const map = /* @__PURE__ */ new Map();
1625
- for (const event of events) {
1626
- const key = getTimelineTeamFilterKey(event);
1627
- if (map.has(key)) continue;
1628
- const accent = getTimelineTeamAccent(event);
1629
- const isNone = key === TIMELINE_FILTER_TEAM_NONE;
1630
- const isLegacy = key === TIMELINE_FILTER_TEAM_LEGACY;
1631
- const label = isNone ? `No ${categoryLabel.toLowerCase()}` : isLegacy ? "Accent only" : accent ? getTimelineTeamDisplayLabel(event, accent) : key;
1632
- const color = isNone ? "" : (accent == null ? void 0 : accent.color) ?? tokens.colors.text.tertiary;
1633
- map.set(key, { key, label, color, isNoneBucket: isNone });
1634
- }
1635
- const list = [...map.values()];
1636
- list.sort((a, b) => {
1637
- if (a.isNoneBucket !== b.isNoneBucket) return a.isNoneBucket ? 1 : -1;
1638
- if (a.key === TIMELINE_FILTER_TEAM_LEGACY) return b.key === TIMELINE_FILTER_TEAM_NONE ? -1 : 1;
1639
- if (b.key === TIMELINE_FILTER_TEAM_LEGACY) return a.key === TIMELINE_FILTER_TEAM_NONE ? 1 : -1;
1640
- return a.label.localeCompare(b.label, void 0, { sensitivity: "base" });
1641
- });
1642
- return list;
1599
+ const map = /* @__PURE__ */ new Map();
1600
+ for (const event of events) {
1601
+ const key = getTimelineTeamFilterKey(event);
1602
+ if (map.has(key)) continue;
1603
+ const accent = getTimelineTeamAccent(event);
1604
+ const isNone = key === TIMELINE_FILTER_TEAM_NONE;
1605
+ const label = isNone ? `No ${categoryLabel.toLowerCase()}` : key === "__zendir_team_legacy__" ? "Accent only" : accent ? getTimelineTeamDisplayLabel(event, accent) : key;
1606
+ const color = isNone ? "" : accent?.color ?? tokens.colors.text.tertiary;
1607
+ map.set(key, {
1608
+ key,
1609
+ label,
1610
+ color,
1611
+ isNoneBucket: isNone
1612
+ });
1613
+ }
1614
+ const list = [...map.values()];
1615
+ list.sort((a, b) => {
1616
+ if (a.isNoneBucket !== b.isNoneBucket) return a.isNoneBucket ? 1 : -1;
1617
+ if (a.key === "__zendir_team_legacy__") return b.key === "__zendir_team_none__" ? -1 : 1;
1618
+ if (b.key === "__zendir_team_legacy__") return a.key === "__zendir_team_none__" ? 1 : -1;
1619
+ return a.label.localeCompare(b.label, void 0, { sensitivity: "base" });
1620
+ });
1621
+ return list;
1643
1622
  }
1644
- const TimelineFiltersPanel = memo(function TimelineFiltersPanel2({
1645
- filter,
1646
- onFilterChange,
1647
- trackOptions,
1648
- events,
1649
- expanded,
1650
- teamLabel
1651
- }) {
1652
- const { tokens, theme } = useTheme();
1653
- const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1654
- const accentColor = tokens.colors.accent.primary;
1655
- const teamOptions = useMemo(() => buildTeamFilterOptions(events, tokens, teamLabel), [events, tokens, teamLabel]);
1656
- const patch = useCallback(
1657
- (partial) => {
1658
- onFilterChange(normalizeTimelineFilter({ ...filter, ...partial }));
1659
- },
1660
- [filter, onFilterChange]
1661
- );
1662
- const clearAll = useCallback(() => {
1663
- onFilterChange({});
1664
- }, [onFilterChange]);
1665
- const toggleTrack = useCallback(
1666
- (id) => {
1667
- const cur = filter.tracks ?? [];
1668
- const next = cur.includes(id) ? cur.filter((t) => t !== id) : [...cur, id];
1669
- patch({ tracks: next.length ? next : void 0 });
1670
- },
1671
- [filter.tracks, patch]
1672
- );
1673
- const toggleStatus = useCallback(
1674
- (st) => {
1675
- const cur = filter.status ?? [];
1676
- const next = cur.includes(st) ? cur.filter((s) => s !== st) : [...cur, st];
1677
- patch({ status: next.length ? next : void 0 });
1678
- },
1679
- [filter.status, patch]
1680
- );
1681
- const toggleTeam = useCallback(
1682
- (teamKey) => {
1683
- const cur = filter.teams ?? [];
1684
- const next = cur.includes(teamKey) ? cur.filter((k) => k !== teamKey) : [...cur, teamKey];
1685
- patch({ teams: next.length ? next : void 0 });
1686
- },
1687
- [filter.teams, patch]
1688
- );
1689
- const pillStyle = useCallback(
1690
- (active, tintColor) => {
1691
- const c = tintColor || accentColor;
1692
- return {
1693
- padding: "2px 8px",
1694
- borderRadius: tokens.borderRadius.md,
1695
- fontSize: tokens.typography.fontSize.xs,
1696
- fontWeight: 500,
1697
- textTransform: "uppercase",
1698
- letterSpacing: "0.06em",
1699
- lineHeight: 1.4,
1700
- border: `1px solid ${active ? `${c}66` : tokens.colors.border.muted}`,
1701
- backgroundColor: active ? `${c}22` : "transparent",
1702
- color: active ? c : tokens.colors.text.secondary,
1703
- cursor: "pointer",
1704
- transition: tokens.animation.fast
1705
- };
1706
- },
1707
- [accentColor, tokens]
1708
- );
1709
- const sectionHeader = (text) => /* @__PURE__ */ jsx(
1710
- "div",
1711
- {
1712
- style: {
1713
- fontSize: tokens.typography.fontSize.xs,
1714
- fontWeight: 700,
1715
- letterSpacing: "0.06em",
1716
- textTransform: "uppercase",
1717
- color: tokens.colors.text.tertiary,
1718
- marginBottom: 8
1719
- },
1720
- children: text
1721
- }
1722
- );
1723
- if (!expanded) return null;
1724
- return /* @__PURE__ */ jsxs(
1725
- "div",
1726
- {
1727
- id: "zendir-timeline-filters-panel",
1728
- style: {
1729
- padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1730
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
1731
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1732
- ...isTransparentTheme && { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" },
1733
- display: "flex",
1734
- flexDirection: "column",
1735
- gap: tokens.spacing.smd
1736
- },
1737
- children: [
1738
- trackOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by track", children: [
1739
- sectionHeader("Tracks"),
1740
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: trackOptions.map((t) => {
1741
- var _a;
1742
- const active = ((_a = filter.tracks) == null ? void 0 : _a.includes(t.id)) ?? false;
1743
- return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => toggleTrack(t.id), style: pillStyle(active), children: t.label }, t.id);
1744
- }) })
1745
- ] }) : null,
1746
- /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by status", children: [
1747
- sectionHeader("Status"),
1748
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ALL_TIMELINE_STATUSES.map((st) => {
1749
- var _a;
1750
- const active = ((_a = filter.status) == null ? void 0 : _a.includes(st)) ?? false;
1751
- const statusColor = st ? tokens.colors.status[st] : accentColor;
1752
- return /* @__PURE__ */ jsxs(
1753
- "button",
1754
- {
1755
- type: "button",
1756
- "aria-pressed": active,
1757
- onClick: () => toggleStatus(st),
1758
- style: {
1759
- ...pillStyle(active, statusColor),
1760
- display: "inline-flex",
1761
- alignItems: "center",
1762
- gap: 6
1763
- },
1764
- children: [
1765
- /* @__PURE__ */ jsx(
1766
- "span",
1767
- {
1768
- style: {
1769
- width: 8,
1770
- height: 8,
1771
- borderRadius: "50%",
1772
- backgroundColor: active ? statusColor : `${statusColor}50`,
1773
- flexShrink: 0,
1774
- transition: tokens.animation.fast
1775
- }
1776
- }
1777
- ),
1778
- st
1779
- ]
1780
- },
1781
- st
1782
- );
1783
- }) })
1784
- ] }),
1785
- /* @__PURE__ */ jsxs(
1786
- "div",
1787
- {
1788
- style: {
1789
- display: "flex",
1790
- flexWrap: "wrap",
1791
- gap: `${tokens.spacing.smd} ${tokens.spacing.lg}`,
1792
- alignItems: "flex-start"
1793
- },
1794
- children: [
1795
- /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by duration", style: { minWidth: 0 }, children: [
1796
- sectionHeader("Duration"),
1797
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ["all", "point", "range"].map((key) => {
1798
- const active = (filter.eventShape ?? "all") === key;
1799
- return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => patch({ eventShape: key }), style: pillStyle(active), children: key === "all" ? "All" : key === "point" ? "Instant" : "Range" }, key);
1800
- }) })
1801
- ] }),
1802
- teamOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `Filter by ${teamLabel.toLowerCase()}`, style: { flex: 1, minWidth: 180 }, children: [
1803
- sectionHeader(teamLabel),
1804
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: teamOptions.map((opt) => {
1805
- var _a, _b, _c;
1806
- const active = ((_a = filter.teams) == null ? void 0 : _a.includes(opt.key)) ?? false;
1807
- return /* @__PURE__ */ jsxs(
1808
- "button",
1809
- {
1810
- type: "button",
1811
- "aria-pressed": active,
1812
- onClick: () => toggleTeam(opt.key),
1813
- style: {
1814
- ...pillStyle(active, opt.color || void 0),
1815
- display: "inline-flex",
1816
- alignItems: "center",
1817
- gap: 6,
1818
- maxWidth: "100%"
1819
- },
1820
- children: [
1821
- opt.isNoneBucket ? /* @__PURE__ */ jsx(
1822
- "span",
1823
- {
1824
- style: {
1825
- width: 8,
1826
- height: 8,
1827
- borderRadius: "50%",
1828
- border: `${((_c = (_b = tokens.borders) == null ? void 0 : _b.width) == null ? void 0 : _c.thick) ?? "2px"} solid ${tokens.colors.border.muted}`,
1829
- flexShrink: 0,
1830
- boxSizing: "border-box"
1831
- }
1832
- }
1833
- ) : /* @__PURE__ */ jsx(
1834
- "span",
1835
- {
1836
- style: {
1837
- width: 8,
1838
- height: 8,
1839
- borderRadius: "50%",
1840
- backgroundColor: opt.color,
1841
- flexShrink: 0
1842
- }
1843
- }
1844
- ),
1845
- /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: opt.label })
1846
- ]
1847
- },
1848
- opt.key
1849
- );
1850
- }) })
1851
- ] }) : null
1852
- ]
1853
- }
1854
- ),
1855
- isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx(Button, { variant: "borderless", size: "small", onClick: clearAll, children: "Clear all" }) }) : null
1856
- ]
1857
- }
1858
- );
1623
+ /** Expandable filter panel — rendered below the header when expanded. Search + toggle live in the header. */
1624
+ var TimelineFiltersPanel = memo(function TimelineFiltersPanel({ filter, onFilterChange, trackOptions, events, expanded, teamLabel }) {
1625
+ const { tokens, theme } = useTheme();
1626
+ const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1627
+ const accentColor = tokens.colors.accent.primary;
1628
+ const teamOptions = useMemo(() => buildTeamFilterOptions(events, tokens, teamLabel), [
1629
+ events,
1630
+ tokens,
1631
+ teamLabel
1632
+ ]);
1633
+ const patch = useCallback((partial) => {
1634
+ onFilterChange(normalizeTimelineFilter({
1635
+ ...filter,
1636
+ ...partial
1637
+ }));
1638
+ }, [filter, onFilterChange]);
1639
+ const clearAll = useCallback(() => {
1640
+ onFilterChange({});
1641
+ }, [onFilterChange]);
1642
+ const toggleTrack = useCallback((id) => {
1643
+ const cur = filter.tracks ?? [];
1644
+ const next = cur.includes(id) ? cur.filter((t) => t !== id) : [...cur, id];
1645
+ patch({ tracks: next.length ? next : void 0 });
1646
+ }, [filter.tracks, patch]);
1647
+ const toggleStatus = useCallback((st) => {
1648
+ const cur = filter.status ?? [];
1649
+ const next = cur.includes(st) ? cur.filter((s) => s !== st) : [...cur, st];
1650
+ patch({ status: next.length ? next : void 0 });
1651
+ }, [filter.status, patch]);
1652
+ const toggleTeam = useCallback((teamKey) => {
1653
+ const cur = filter.teams ?? [];
1654
+ const next = cur.includes(teamKey) ? cur.filter((k) => k !== teamKey) : [...cur, teamKey];
1655
+ patch({ teams: next.length ? next : void 0 });
1656
+ }, [filter.teams, patch]);
1657
+ const pillStyle = useCallback((active, tintColor) => {
1658
+ const c = tintColor || accentColor;
1659
+ return {
1660
+ padding: "2px 8px",
1661
+ borderRadius: tokens.borderRadius.md,
1662
+ fontSize: tokens.typography.fontSize.xs,
1663
+ fontWeight: 500,
1664
+ textTransform: "uppercase",
1665
+ letterSpacing: "0.06em",
1666
+ lineHeight: 1.4,
1667
+ border: `1px solid ${active ? `${c}66` : tokens.colors.border.muted}`,
1668
+ backgroundColor: active ? `${c}22` : "transparent",
1669
+ color: active ? c : tokens.colors.text.secondary,
1670
+ cursor: "pointer",
1671
+ transition: tokens.animation.fast
1672
+ };
1673
+ }, [accentColor, tokens]);
1674
+ const sectionHeader = (text) => /* @__PURE__ */ jsx("div", {
1675
+ style: {
1676
+ fontSize: tokens.typography.fontSize.xs,
1677
+ fontWeight: 700,
1678
+ letterSpacing: "0.06em",
1679
+ textTransform: "uppercase",
1680
+ color: tokens.colors.text.tertiary,
1681
+ marginBottom: 8
1682
+ },
1683
+ children: text
1684
+ });
1685
+ if (!expanded) return null;
1686
+ return /* @__PURE__ */ jsxs("div", {
1687
+ id: "zendir-timeline-filters-panel",
1688
+ style: {
1689
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1690
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
1691
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1692
+ ...isTransparentTheme && {
1693
+ backdropFilter: "blur(8px)",
1694
+ WebkitBackdropFilter: "blur(8px)"
1695
+ },
1696
+ display: "flex",
1697
+ flexDirection: "column",
1698
+ gap: tokens.spacing.smd
1699
+ },
1700
+ children: [
1701
+ trackOptions.length > 0 ? /* @__PURE__ */ jsxs("div", {
1702
+ role: "group",
1703
+ "aria-label": "Filter by track",
1704
+ children: [sectionHeader("Tracks"), /* @__PURE__ */ jsx("div", {
1705
+ style: {
1706
+ display: "flex",
1707
+ flexWrap: "wrap",
1708
+ gap: tokens.spacing.xs
1709
+ },
1710
+ children: trackOptions.map((t) => {
1711
+ const active = filter.tracks?.includes(t.id) ?? false;
1712
+ return /* @__PURE__ */ jsx("button", {
1713
+ type: "button",
1714
+ "aria-pressed": active,
1715
+ onClick: () => toggleTrack(t.id),
1716
+ style: pillStyle(active),
1717
+ children: t.label
1718
+ }, t.id);
1719
+ })
1720
+ })]
1721
+ }) : null,
1722
+ /* @__PURE__ */ jsxs("div", {
1723
+ role: "group",
1724
+ "aria-label": "Filter by status",
1725
+ children: [sectionHeader("Status"), /* @__PURE__ */ jsx("div", {
1726
+ style: {
1727
+ display: "flex",
1728
+ flexWrap: "wrap",
1729
+ gap: tokens.spacing.xs
1730
+ },
1731
+ children: ALL_TIMELINE_STATUSES.map((st) => {
1732
+ const active = filter.status?.includes(st) ?? false;
1733
+ const statusColor = st ? tokens.colors.status[st] : accentColor;
1734
+ return /* @__PURE__ */ jsxs("button", {
1735
+ type: "button",
1736
+ "aria-pressed": active,
1737
+ onClick: () => toggleStatus(st),
1738
+ style: {
1739
+ ...pillStyle(active, statusColor),
1740
+ display: "inline-flex",
1741
+ alignItems: "center",
1742
+ gap: 6
1743
+ },
1744
+ children: [/* @__PURE__ */ jsx("span", { style: {
1745
+ width: 8,
1746
+ height: 8,
1747
+ borderRadius: "50%",
1748
+ backgroundColor: active ? statusColor : `${statusColor}50`,
1749
+ flexShrink: 0,
1750
+ transition: tokens.animation.fast
1751
+ } }), st]
1752
+ }, st);
1753
+ })
1754
+ })]
1755
+ }),
1756
+ /* @__PURE__ */ jsxs("div", {
1757
+ style: {
1758
+ display: "flex",
1759
+ flexWrap: "wrap",
1760
+ gap: `${tokens.spacing.smd} ${tokens.spacing.lg}`,
1761
+ alignItems: "flex-start"
1762
+ },
1763
+ children: [/* @__PURE__ */ jsxs("div", {
1764
+ role: "group",
1765
+ "aria-label": "Filter by duration",
1766
+ style: { minWidth: 0 },
1767
+ children: [sectionHeader("Duration"), /* @__PURE__ */ jsx("div", {
1768
+ style: {
1769
+ display: "flex",
1770
+ flexWrap: "wrap",
1771
+ gap: tokens.spacing.xs
1772
+ },
1773
+ children: [
1774
+ "all",
1775
+ "point",
1776
+ "range"
1777
+ ].map((key) => {
1778
+ const active = (filter.eventShape ?? "all") === key;
1779
+ return /* @__PURE__ */ jsx("button", {
1780
+ type: "button",
1781
+ "aria-pressed": active,
1782
+ onClick: () => patch({ eventShape: key }),
1783
+ style: pillStyle(active),
1784
+ children: key === "all" ? "All" : key === "point" ? "Instant" : "Range"
1785
+ }, key);
1786
+ })
1787
+ })]
1788
+ }), teamOptions.length > 0 ? /* @__PURE__ */ jsxs("div", {
1789
+ role: "group",
1790
+ "aria-label": `Filter by ${teamLabel.toLowerCase()}`,
1791
+ style: {
1792
+ flex: 1,
1793
+ minWidth: 180
1794
+ },
1795
+ children: [sectionHeader(teamLabel), /* @__PURE__ */ jsx("div", {
1796
+ style: {
1797
+ display: "flex",
1798
+ flexWrap: "wrap",
1799
+ gap: tokens.spacing.xs
1800
+ },
1801
+ children: teamOptions.map((opt) => {
1802
+ const active = filter.teams?.includes(opt.key) ?? false;
1803
+ return /* @__PURE__ */ jsxs("button", {
1804
+ type: "button",
1805
+ "aria-pressed": active,
1806
+ onClick: () => toggleTeam(opt.key),
1807
+ style: {
1808
+ ...pillStyle(active, opt.color || void 0),
1809
+ display: "inline-flex",
1810
+ alignItems: "center",
1811
+ gap: 6,
1812
+ maxWidth: "100%"
1813
+ },
1814
+ children: [opt.isNoneBucket ? /* @__PURE__ */ jsx("span", { style: {
1815
+ width: 8,
1816
+ height: 8,
1817
+ borderRadius: "50%",
1818
+ border: `${tokens.borders?.width?.thick ?? "2px"} solid ${tokens.colors.border.muted}`,
1819
+ flexShrink: 0,
1820
+ boxSizing: "border-box"
1821
+ } }) : /* @__PURE__ */ jsx("span", { style: {
1822
+ width: 8,
1823
+ height: 8,
1824
+ borderRadius: "50%",
1825
+ backgroundColor: opt.color,
1826
+ flexShrink: 0
1827
+ } }), /* @__PURE__ */ jsx("span", {
1828
+ style: {
1829
+ overflow: "hidden",
1830
+ textOverflow: "ellipsis",
1831
+ whiteSpace: "nowrap"
1832
+ },
1833
+ children: opt.label
1834
+ })]
1835
+ }, opt.key);
1836
+ })
1837
+ })]
1838
+ }) : null]
1839
+ }),
1840
+ isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx("div", {
1841
+ style: {
1842
+ display: "flex",
1843
+ justifyContent: "flex-end"
1844
+ },
1845
+ children: /* @__PURE__ */ jsx(Button, {
1846
+ variant: "borderless",
1847
+ size: "small",
1848
+ onClick: clearAll,
1849
+ children: "Clear all"
1850
+ })
1851
+ }) : null
1852
+ ]
1853
+ });
1859
1854
  });
1860
- const UnifiedTimeline = memo(function UnifiedTimeline2({
1861
- title = "Timeline",
1862
- events,
1863
- tracks = [],
1864
- start: propStart,
1865
- end: propEnd,
1866
- defaultView = "chart",
1867
- viewMode: controlledViewMode,
1868
- onViewModeChange,
1869
- onEventClick,
1870
- showViewToggle = true,
1871
- maxHeight,
1872
- trackHeight = 56,
1873
- className = "",
1874
- timeFormat = "absolute",
1875
- zoomable = false,
1876
- initialZoom = 1,
1877
- showFilters: _showFilters = true,
1878
- filtersDefaultExpanded = false,
1879
- hideEmptyTracksWhenFiltered = true,
1880
- filter: _filter,
1881
- onFilterChange: _onFilterChange,
1882
- showCount: _showCount = true,
1883
- loading = false,
1884
- referenceTime,
1885
- showPlayhead = true,
1886
- playheadTime,
1887
- onPlayheadChange: _onPlayheadChangeTimeline,
1888
- showDayMarkers = true,
1889
- teamLabel: teamLabelProp
1890
- }) {
1891
- const { tokens, theme } = useTheme();
1892
- const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1893
- const { isMobile } = useBreakpoint();
1894
- const categoryCtx = useCategoryPalette();
1895
- const teamLabel = teamLabelProp ?? (categoryCtx == null ? void 0 : categoryCtx.categoryLabel) ?? "Teams";
1896
- const [internalViewMode, setInternalViewMode] = useState(defaultView);
1897
- const [zoom, setZoom] = useState(initialZoom);
1898
- const [_internalFilter, _setInternalFilter] = useState({});
1899
- const [filtersExpanded, setFiltersExpanded] = useState(filtersDefaultExpanded);
1900
- const filter = _filter !== void 0 ? _filter : _internalFilter;
1901
- const filterChipCount = _showFilters ? countTimelineFilterChips(filter) : 0;
1902
- const setFilter = useCallback(
1903
- (next) => {
1904
- const n = normalizeTimelineFilter(next);
1905
- if (_filter === void 0) _setInternalFilter(n);
1906
- _onFilterChange == null ? void 0 : _onFilterChange(n);
1907
- },
1908
- [_filter, _onFilterChange]
1909
- );
1910
- const patchFilter = useCallback(
1911
- (partial) => {
1912
- setFilter(normalizeTimelineFilter({ ...filter, ...partial }));
1913
- },
1914
- [filter, setFilter]
1915
- );
1916
- const viewMode = controlledViewMode ?? internalViewMode;
1917
- const handleViewModeChange = useCallback((mode) => {
1918
- setInternalViewMode(mode);
1919
- onViewModeChange == null ? void 0 : onViewModeChange(mode);
1920
- }, [onViewModeChange]);
1921
- const { start, end } = useMemo(() => {
1922
- if (propStart && propEnd) {
1923
- return { start: propStart, end: propEnd };
1924
- }
1925
- if (events.length === 0) {
1926
- const now = /* @__PURE__ */ new Date();
1927
- return {
1928
- start: new Date(now.getTime() - 36e5),
1929
- end: new Date(now.getTime() + 36e5)
1930
- };
1931
- }
1932
- let minTime = events[0].start.getTime();
1933
- let maxTime = events[0].start.getTime();
1934
- events.forEach((event) => {
1935
- minTime = Math.min(minTime, event.start.getTime());
1936
- maxTime = Math.max(maxTime, (event.end || event.start).getTime());
1937
- });
1938
- const duration = maxTime - minTime;
1939
- const padding = duration * 0.1;
1940
- return {
1941
- start: new Date(minTime - padding),
1942
- end: new Date(maxTime + padding)
1943
- };
1944
- }, [events, propStart, propEnd]);
1945
- const effectiveTracks = useMemo(() => {
1946
- if (tracks.length > 0) return tracks;
1947
- const trackSet = /* @__PURE__ */ new Set();
1948
- events.forEach((event) => {
1949
- if (event.track) trackSet.add(event.track);
1950
- });
1951
- if (trackSet.size === 0) {
1952
- return [{ id: "default", label: "Events", height: trackHeight }];
1953
- }
1954
- return Array.from(trackSet).map((id) => ({
1955
- id,
1956
- label: id,
1957
- height: trackHeight
1958
- }));
1959
- }, [events, tracks, trackHeight]);
1960
- const filteredEvents = useMemo(
1961
- () => events.filter((e) => matchesTimelineFilter(e, filter)),
1962
- [events, filter]
1963
- );
1964
- const effectiveTracksForViews = useMemo(() => {
1965
- if (!hideEmptyTracksWhenFiltered || !isTimelineFilterActive(filter)) return effectiveTracks;
1966
- const ids = new Set(filteredEvents.map((e) => e.track || "default"));
1967
- return effectiveTracks.filter((t) => ids.has(t.id));
1968
- }, [effectiveTracks, filteredEvents, filter, hideEmptyTracksWhenFiltered]);
1969
- const trackOptionsForToolbar = useMemo(() => {
1970
- if (tracks.length > 0) return tracks.map((t) => ({ id: t.id, label: t.label }));
1971
- const u = /* @__PURE__ */ new Set();
1972
- events.forEach((e) => {
1973
- if (e.track) u.add(e.track);
1974
- });
1975
- if (u.size === 0) return [{ id: "default", label: "Events" }];
1976
- return [...u].sort().map((id) => ({ id, label: id }));
1977
- }, [events, tracks]);
1978
- const sortedEvents = useMemo(() => {
1979
- return [...filteredEvents].sort((a, b) => b.start.getTime() - a.start.getTime());
1980
- }, [filteredEvents]);
1981
- const countLabel = !_showCount ? title : `${title} (${filteredEvents.length}${filteredEvents.length !== events.length ? ` / ${events.length}` : ""})`;
1982
- return /* @__PURE__ */ jsxs(
1983
- "div",
1984
- {
1985
- className: classNames("zendir-unified-timeline", className),
1986
- style: {
1987
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1988
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
1989
- ...tokens.colors.border.cardStyle ?? { border: `1px solid ${tokens.colors.border.muted}` },
1990
- borderRadius: tokens.borderRadius.lg,
1991
- overflow: "hidden"
1992
- },
1993
- children: [
1994
- /* @__PURE__ */ jsxs(
1995
- "div",
1996
- {
1997
- style: {
1998
- display: "flex",
1999
- flexDirection: isMobile ? "column" : "row",
2000
- alignItems: isMobile ? "stretch" : "center",
2001
- padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
2002
- gap: isMobile ? tokens.spacing.xs : tokens.spacing.sm,
2003
- borderBottom: `1px solid ${tokens.colors.border.muted}`
2004
- },
2005
- children: [
2006
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm }, children: [
2007
- /* @__PURE__ */ jsx(
2008
- "h3",
2009
- {
2010
- style: {
2011
- margin: 0,
2012
- fontSize: "1rem",
2013
- fontWeight: 500,
2014
- // AstroUXDS medium
2015
- color: tokens.colors.text.primary,
2016
- whiteSpace: "nowrap",
2017
- flexShrink: 0
2018
- },
2019
- children: countLabel
2020
- }
2021
- ),
2022
- !isMobile && /* @__PURE__ */ jsx("div", { style: { flex: 1 } })
2023
- ] }),
2024
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm, flex: isMobile ? void 0 : 1, justifyContent: isMobile ? "space-between" : "flex-end" }, children: [
2025
- _showFilters && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.xs, flex: isMobile ? 1 : void 0, flexShrink: 0 }, children: [
2026
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: isMobile ? "100%" : 180 }, children: [
2027
- /* @__PURE__ */ jsx(
2028
- Icon,
2029
- {
2030
- name: "search",
2031
- size: 14,
2032
- style: {
2033
- position: "absolute",
2034
- left: 8,
2035
- top: "50%",
2036
- transform: "translateY(-50%)",
2037
- color: tokens.colors.text.tertiary,
2038
- pointerEvents: "none"
2039
- }
2040
- }
2041
- ),
2042
- /* @__PURE__ */ jsx(
2043
- "input",
2044
- {
2045
- type: "search",
2046
- value: filter.search ?? "",
2047
- onChange: (e) => patchFilter({ search: e.target.value || void 0 }),
2048
- placeholder: "Search…",
2049
- "aria-label": "Search timeline events",
2050
- style: {
2051
- width: "100%",
2052
- boxSizing: "border-box",
2053
- height: tokens.elementSize.sm,
2054
- paddingLeft: 28,
2055
- paddingRight: 8,
2056
- borderRadius: tokens.borderRadius.md,
2057
- border: `1px solid ${tokens.colors.border.muted}`,
2058
- backgroundColor: tokens.colors.background.base,
2059
- color: tokens.colors.text.primary,
2060
- fontSize: tokens.typography.fontSize.sm,
2061
- fontFamily: "inherit",
2062
- outline: "none",
2063
- transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)"
2064
- },
2065
- onFocus: (e) => {
2066
- e.target.style.borderColor = tokens.colors.accent.primary;
2067
- e.target.style.boxShadow = `0 0 0 3px ${tokens.colors.accent.primary}50, 0 0 20px ${tokens.colors.accent.primary}15`;
2068
- },
2069
- onBlur: (e) => {
2070
- e.target.style.borderColor = tokens.colors.border.muted;
2071
- e.target.style.boxShadow = "none";
2072
- }
2073
- }
2074
- )
2075
- ] }),
2076
- /* @__PURE__ */ jsx(Tooltip, { content: filtersExpanded ? "Hide filters" : "Show filters", children: /* @__PURE__ */ jsxs(
2077
- "button",
2078
- {
2079
- type: "button",
2080
- "aria-label": filtersExpanded ? "Hide filters" : "Show filters",
2081
- "aria-expanded": filtersExpanded,
2082
- "aria-controls": "zendir-timeline-filters-panel",
2083
- onClick: () => setFiltersExpanded((v) => !v),
2084
- style: {
2085
- position: "relative",
2086
- display: "flex",
2087
- alignItems: "center",
2088
- justifyContent: "center",
2089
- width: 28,
2090
- height: 28,
2091
- border: `1px solid ${filterChipCount > 0 ? `${tokens.colors.accent.primary}80` : tokens.colors.border.muted}`,
2092
- borderRadius: tokens.borderRadius.sm,
2093
- backgroundColor: filterChipCount > 0 ? `${tokens.colors.accent.primary}12` : "transparent",
2094
- color: filterChipCount > 0 ? tokens.colors.accent.primary : tokens.colors.text.secondary,
2095
- cursor: "pointer",
2096
- transition: tokens.animation.fast
2097
- },
2098
- children: [
2099
- /* @__PURE__ */ jsx(Icon, { name: "filter", size: 16 }),
2100
- filterChipCount > 0 ? /* @__PURE__ */ jsx(
2101
- "span",
2102
- {
2103
- style: {
2104
- position: "absolute",
2105
- top: -4,
2106
- right: -4,
2107
- minWidth: 14,
2108
- height: 14,
2109
- borderRadius: "50%",
2110
- backgroundColor: tokens.colors.accent.primary,
2111
- color: "#fff",
2112
- fontSize: "0.5625rem",
2113
- fontWeight: 700,
2114
- display: "flex",
2115
- alignItems: "center",
2116
- justifyContent: "center",
2117
- lineHeight: 1
2118
- },
2119
- children: filterChipCount
2120
- }
2121
- ) : null
2122
- ]
2123
- }
2124
- ) })
2125
- ] }),
2126
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }, children: [
2127
- zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs(
2128
- "div",
2129
- {
2130
- style: {
2131
- display: "flex",
2132
- alignItems: "center",
2133
- gap: 4,
2134
- paddingRight: 12,
2135
- borderRight: `1px solid ${tokens.colors.border.muted}`
2136
- },
2137
- children: [
2138
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom out", children: /* @__PURE__ */ jsx(
2139
- "button",
2140
- {
2141
- type: "button",
2142
- "aria-label": "Zoom out",
2143
- onClick: () => setZoom(Math.max(zoom - 0.5, 1)),
2144
- disabled: zoom <= 1,
2145
- style: {
2146
- width: 28,
2147
- height: 28,
2148
- display: "flex",
2149
- alignItems: "center",
2150
- justifyContent: "center",
2151
- backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
2152
- border: `1px solid ${tokens.colors.border.muted}`,
2153
- borderRadius: tokens.borderRadius.sm,
2154
- color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2155
- cursor: zoom <= 1 ? "not-allowed" : "pointer",
2156
- fontSize: "1rem",
2157
- fontWeight: 500,
2158
- // AstroUXDS medium (was 600)
2159
- opacity: zoom <= 1 ? 0.5 : 1
2160
- },
2161
- children: "−"
2162
- }
2163
- ) }),
2164
- /* @__PURE__ */ jsxs(
2165
- "span",
2166
- {
2167
- style: {
2168
- minWidth: 40,
2169
- textAlign: "center",
2170
- fontSize: "0.75rem",
2171
- fontFamily: tokens.typography.fontFamily.mono,
2172
- color: tokens.colors.text.secondary
2173
- },
2174
- children: [
2175
- Math.round(zoom * 100),
2176
- "%"
2177
- ]
2178
- }
2179
- ),
2180
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom in", children: /* @__PURE__ */ jsx(
2181
- "button",
2182
- {
2183
- type: "button",
2184
- "aria-label": "Zoom in",
2185
- onClick: () => setZoom(Math.min(zoom + 0.5, 4)),
2186
- disabled: zoom >= 4,
2187
- style: {
2188
- width: 28,
2189
- height: 28,
2190
- display: "flex",
2191
- alignItems: "center",
2192
- justifyContent: "center",
2193
- backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
2194
- border: `1px solid ${tokens.colors.border.muted}`,
2195
- borderRadius: tokens.borderRadius.sm,
2196
- color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2197
- cursor: zoom >= 4 ? "not-allowed" : "pointer",
2198
- fontSize: "1rem",
2199
- fontWeight: 500,
2200
- // AstroUXDS medium (was 600)
2201
- opacity: zoom >= 4 ? 0.5 : 1
2202
- },
2203
- children: "+"
2204
- }
2205
- ) }),
2206
- /* @__PURE__ */ jsx(Tooltip, { content: "Reset zoom", children: /* @__PURE__ */ jsx(
2207
- "button",
2208
- {
2209
- type: "button",
2210
- "aria-label": "Reset zoom",
2211
- onClick: () => setZoom(1),
2212
- style: {
2213
- height: 28,
2214
- padding: "0 10px",
2215
- display: "flex",
2216
- alignItems: "center",
2217
- justifyContent: "center",
2218
- backgroundColor: tokens.colors.background.elevated,
2219
- border: `1px solid ${tokens.colors.border.muted}`,
2220
- borderRadius: tokens.borderRadius.sm,
2221
- color: tokens.colors.text.secondary,
2222
- cursor: "pointer",
2223
- fontSize: "0.6875rem",
2224
- fontWeight: 500
2225
- },
2226
- children: "FIT"
2227
- }
2228
- ) })
2229
- ]
2230
- }
2231
- ),
2232
- showViewToggle && /* @__PURE__ */ jsx(
2233
- "div",
2234
- {
2235
- role: "group",
2236
- "aria-label": "View mode",
2237
- style: {
2238
- display: "flex",
2239
- gap: "2px",
2240
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
2241
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
2242
- padding: "4px",
2243
- borderRadius: tokens.borderRadius.lg
2244
- },
2245
- children: ["chart", "list", "scatter"].map((mode) => {
2246
- const isActive = viewMode === mode;
2247
- const icons = {
2248
- chart: "chart",
2249
- list: "list",
2250
- scatter: "timeline"
2251
- };
2252
- const labels = {
2253
- chart: "Gantt Chart",
2254
- list: "List View",
2255
- scatter: "Scatter View"
2256
- };
2257
- return /* @__PURE__ */ jsx(Tooltip, { content: labels[mode], children: /* @__PURE__ */ jsx(
2258
- "button",
2259
- {
2260
- type: "button",
2261
- "aria-label": labels[mode],
2262
- "aria-pressed": isActive,
2263
- onClick: () => handleViewModeChange(mode),
2264
- style: {
2265
- display: "flex",
2266
- alignItems: "center",
2267
- justifyContent: "center",
2268
- width: "32px",
2269
- height: "32px",
2270
- backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
2271
- border: "none",
2272
- borderRadius: tokens.borderRadius.md,
2273
- color: isActive ? "#ffffff" : tokens.colors.text.secondary,
2274
- cursor: "pointer",
2275
- transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
2276
- transform: isActive ? "scale(1)" : "scale(0.95)",
2277
- boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
2278
- },
2279
- onMouseEnter: (e) => {
2280
- if (!isActive) {
2281
- e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
2282
- e.currentTarget.style.transform = "scale(1)";
2283
- }
2284
- },
2285
- onMouseLeave: (e) => {
2286
- if (!isActive) {
2287
- e.currentTarget.style.backgroundColor = "transparent";
2288
- e.currentTarget.style.transform = "scale(0.95)";
2289
- }
2290
- },
2291
- children: /* @__PURE__ */ jsx(Icon, { name: icons[mode], size: 16 })
2292
- }
2293
- ) }, mode);
2294
- })
2295
- }
2296
- )
2297
- ] }),
2298
- " "
2299
- ] }),
2300
- " "
2301
- ]
2302
- }
2303
- ),
2304
- " ",
2305
- _showFilters ? /* @__PURE__ */ jsx(
2306
- TimelineFiltersPanel,
2307
- {
2308
- filter,
2309
- onFilterChange: setFilter,
2310
- trackOptions: trackOptionsForToolbar,
2311
- events,
2312
- expanded: filtersExpanded,
2313
- teamLabel
2314
- }
2315
- ) : null,
2316
- loading && /* @__PURE__ */ jsx(
2317
- "div",
2318
- {
2319
- style: {
2320
- position: "absolute",
2321
- inset: 0,
2322
- display: "flex",
2323
- alignItems: "center",
2324
- justifyContent: "center",
2325
- backgroundColor: `${tokens.colors.background.overlay}80`,
2326
- backdropFilter: "blur(4px)",
2327
- zIndex: Z.loadingOverlay
2328
- },
2329
- children: /* @__PURE__ */ jsx(
2330
- "div",
2331
- {
2332
- style: {
2333
- width: 40,
2334
- height: 40,
2335
- border: `3px solid ${tokens.colors.border.muted}`,
2336
- borderTopColor: tokens.colors.accent.primary,
2337
- borderRadius: "50%",
2338
- animation: "zendir-spin 1s linear infinite"
2339
- }
2340
- }
2341
- )
2342
- }
2343
- ),
2344
- /* @__PURE__ */ jsx(
2345
- "div",
2346
- {
2347
- style: {
2348
- padding: viewMode === "list" ? "16px 20px" : "16px",
2349
- maxHeight,
2350
- overflowY: maxHeight ? "auto" : void 0,
2351
- overflowX: viewMode !== "list" ? "auto" : void 0
2352
- },
2353
- children: events.length === 0 ? /* @__PURE__ */ jsxs(
2354
- "div",
2355
- {
2356
- style: {
2357
- display: "flex",
2358
- flexDirection: "column",
2359
- alignItems: "center",
2360
- justifyContent: "center",
2361
- padding: "48px 0",
2362
- color: tokens.colors.text.tertiary,
2363
- gap: "12px"
2364
- },
2365
- children: [
2366
- /* @__PURE__ */ jsx(Icon, { name: "clock", size: 32 }),
2367
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem" }, children: "No events to display" })
2368
- ]
2369
- }
2370
- ) : filteredEvents.length === 0 ? /* @__PURE__ */ jsxs(
2371
- "div",
2372
- {
2373
- style: {
2374
- display: "flex",
2375
- flexDirection: "column",
2376
- alignItems: "center",
2377
- justifyContent: "center",
2378
- padding: "48px 24px",
2379
- color: tokens.colors.text.tertiary,
2380
- gap: 12,
2381
- textAlign: "center"
2382
- },
2383
- children: [
2384
- /* @__PURE__ */ jsx(Icon, { name: "search", size: 32 }),
2385
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", color: tokens.colors.text.secondary }, children: "No events match the current filters." }),
2386
- _showFilters && isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx(
2387
- "button",
2388
- {
2389
- type: "button",
2390
- onClick: () => setFilter({}),
2391
- style: {
2392
- marginTop: 4,
2393
- padding: "8px 16px",
2394
- borderRadius: tokens.borderRadius.md,
2395
- border: `1px solid ${tokens.colors.accent.primary}`,
2396
- backgroundColor: `${tokens.colors.accent.primary}18`,
2397
- color: tokens.colors.accent.primary,
2398
- cursor: "pointer",
2399
- fontSize: "0.8125rem",
2400
- fontWeight: 500
2401
- },
2402
- children: "Clear filters"
2403
- }
2404
- ) : null
2405
- ]
2406
- }
2407
- ) : viewMode === "list" ? (
2408
- // List View
2409
- sortedEvents.map((event, index) => /* @__PURE__ */ jsx(
2410
- EventListItem,
2411
- {
2412
- event,
2413
- isLast: index === sortedEvents.length - 1,
2414
- onEventClick
2415
- },
2416
- event.id
2417
- ))
2418
- ) : viewMode === "scatter" ? (
2419
- // Scatter View (Dots on timeline) - uses same zoom as chart
2420
- /* @__PURE__ */ jsx(
2421
- ScatterView,
2422
- {
2423
- events: filteredEvents,
2424
- tracks: effectiveTracksForViews,
2425
- start,
2426
- end,
2427
- trackHeight,
2428
- onEventClick,
2429
- timeFormat,
2430
- referenceTime: referenceTime || start,
2431
- zoom
2432
- }
2433
- )
2434
- ) : (
2435
- // Chart View (Gantt)
2436
- /* @__PURE__ */ jsx(
2437
- ChartView,
2438
- {
2439
- events: filteredEvents,
2440
- tracks: effectiveTracksForViews,
2441
- start,
2442
- end,
2443
- trackHeight,
2444
- onEventClick,
2445
- zoom,
2446
- showPlayhead,
2447
- playheadTime,
2448
- onPlayheadChange: _onPlayheadChangeTimeline,
2449
- showDayMarkers,
2450
- timeFormat
2451
- }
2452
- )
2453
- )
2454
- }
2455
- ),
2456
- /* @__PURE__ */ jsx("style", { children: `
1855
+ var UnifiedTimeline = memo(function UnifiedTimeline({ title = "Timeline", events, tracks = [], start: propStart, end: propEnd, defaultView = "chart", viewMode: controlledViewMode, onViewModeChange, onEventClick, showViewToggle = true, maxHeight, trackHeight = 56, className = "", timeFormat = "absolute", zoomable = false, initialZoom = 1, showFilters: _showFilters = true, filtersDefaultExpanded = false, hideEmptyTracksWhenFiltered = true, filter: _filter, onFilterChange: _onFilterChange, showCount: _showCount = true, loading = false, referenceTime, showPlayhead = true, playheadTime, onPlayheadChange: _onPlayheadChangeTimeline, showDayMarkers = true, teamLabel: teamLabelProp }) {
1856
+ const { tokens, theme } = useTheme();
1857
+ const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1858
+ const { isMobile } = useBreakpoint();
1859
+ const categoryCtx = useCategoryPalette();
1860
+ /** Explicit prop → CategoryProvider label → default "Teams" */
1861
+ const teamLabel = teamLabelProp ?? categoryCtx?.categoryLabel ?? "Teams";
1862
+ const [internalViewMode, setInternalViewMode] = useState(defaultView);
1863
+ const [zoom, setZoom] = useState(initialZoom);
1864
+ const [_internalFilter, _setInternalFilter] = useState({});
1865
+ const [filtersExpanded, setFiltersExpanded] = useState(filtersDefaultExpanded);
1866
+ const filter = _filter !== void 0 ? _filter : _internalFilter;
1867
+ const filterChipCount = _showFilters ? countTimelineFilterChips(filter) : 0;
1868
+ const setFilter = useCallback((next) => {
1869
+ const n = normalizeTimelineFilter(next);
1870
+ if (_filter === void 0) _setInternalFilter(n);
1871
+ _onFilterChange?.(n);
1872
+ }, [_filter, _onFilterChange]);
1873
+ const patchFilter = useCallback((partial) => {
1874
+ setFilter(normalizeTimelineFilter({
1875
+ ...filter,
1876
+ ...partial
1877
+ }));
1878
+ }, [filter, setFilter]);
1879
+ const viewMode = controlledViewMode ?? internalViewMode;
1880
+ const handleViewModeChange = useCallback((mode) => {
1881
+ setInternalViewMode(mode);
1882
+ onViewModeChange?.(mode);
1883
+ }, [onViewModeChange]);
1884
+ const { start, end } = useMemo(() => {
1885
+ if (propStart && propEnd) return {
1886
+ start: propStart,
1887
+ end: propEnd
1888
+ };
1889
+ if (events.length === 0) {
1890
+ const now = /* @__PURE__ */ new Date();
1891
+ return {
1892
+ start: /* @__PURE__ */ new Date(now.getTime() - 36e5),
1893
+ end: new Date(now.getTime() + 36e5)
1894
+ };
1895
+ }
1896
+ let minTime = events[0].start.getTime();
1897
+ let maxTime = events[0].start.getTime();
1898
+ events.forEach((event) => {
1899
+ minTime = Math.min(minTime, event.start.getTime());
1900
+ maxTime = Math.max(maxTime, (event.end || event.start).getTime());
1901
+ });
1902
+ const padding = (maxTime - minTime) * .1;
1903
+ return {
1904
+ start: new Date(minTime - padding),
1905
+ end: new Date(maxTime + padding)
1906
+ };
1907
+ }, [
1908
+ events,
1909
+ propStart,
1910
+ propEnd
1911
+ ]);
1912
+ const effectiveTracks = useMemo(() => {
1913
+ if (tracks.length > 0) return tracks;
1914
+ const trackSet = /* @__PURE__ */ new Set();
1915
+ events.forEach((event) => {
1916
+ if (event.track) trackSet.add(event.track);
1917
+ });
1918
+ if (trackSet.size === 0) return [{
1919
+ id: "default",
1920
+ label: "Events",
1921
+ height: trackHeight
1922
+ }];
1923
+ return Array.from(trackSet).map((id) => ({
1924
+ id,
1925
+ label: id,
1926
+ height: trackHeight
1927
+ }));
1928
+ }, [
1929
+ events,
1930
+ tracks,
1931
+ trackHeight
1932
+ ]);
1933
+ const filteredEvents = useMemo(() => events.filter((e) => matchesTimelineFilter(e, filter)), [events, filter]);
1934
+ const effectiveTracksForViews = useMemo(() => {
1935
+ if (!hideEmptyTracksWhenFiltered || !isTimelineFilterActive(filter)) return effectiveTracks;
1936
+ const ids = new Set(filteredEvents.map((e) => e.track || "default"));
1937
+ return effectiveTracks.filter((t) => ids.has(t.id));
1938
+ }, [
1939
+ effectiveTracks,
1940
+ filteredEvents,
1941
+ filter,
1942
+ hideEmptyTracksWhenFiltered
1943
+ ]);
1944
+ const trackOptionsForToolbar = useMemo(() => {
1945
+ if (tracks.length > 0) return tracks.map((t) => ({
1946
+ id: t.id,
1947
+ label: t.label
1948
+ }));
1949
+ const u = /* @__PURE__ */ new Set();
1950
+ events.forEach((e) => {
1951
+ if (e.track) u.add(e.track);
1952
+ });
1953
+ if (u.size === 0) return [{
1954
+ id: "default",
1955
+ label: "Events"
1956
+ }];
1957
+ return [...u].sort().map((id) => ({
1958
+ id,
1959
+ label: id
1960
+ }));
1961
+ }, [events, tracks]);
1962
+ const sortedEvents = useMemo(() => {
1963
+ return [...filteredEvents].sort((a, b) => b.start.getTime() - a.start.getTime());
1964
+ }, [filteredEvents]);
1965
+ const countLabel = !_showCount ? title : `${title} (${filteredEvents.length}${filteredEvents.length !== events.length ? ` / ${events.length}` : ""})`;
1966
+ return /* @__PURE__ */ jsxs("div", {
1967
+ className: classNames("zendir-unified-timeline", className),
1968
+ style: {
1969
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1970
+ ...isTransparentTheme && {
1971
+ backdropFilter: "blur(12px)",
1972
+ WebkitBackdropFilter: "blur(12px)"
1973
+ },
1974
+ ...tokens.colors.border.cardStyle ?? { border: `1px solid ${tokens.colors.border.muted}` },
1975
+ borderRadius: tokens.borderRadius.lg,
1976
+ overflow: "hidden"
1977
+ },
1978
+ children: [
1979
+ /* @__PURE__ */ jsxs("div", {
1980
+ style: {
1981
+ display: "flex",
1982
+ flexDirection: isMobile ? "column" : "row",
1983
+ alignItems: isMobile ? "stretch" : "center",
1984
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1985
+ gap: isMobile ? tokens.spacing.xs : tokens.spacing.sm,
1986
+ borderBottom: `1px solid ${tokens.colors.border.muted}`
1987
+ },
1988
+ children: [
1989
+ /* @__PURE__ */ jsxs("div", {
1990
+ style: {
1991
+ display: "flex",
1992
+ alignItems: "center",
1993
+ gap: tokens.spacing.sm
1994
+ },
1995
+ children: [/* @__PURE__ */ jsx("h3", {
1996
+ style: {
1997
+ margin: 0,
1998
+ fontSize: "1rem",
1999
+ fontWeight: 500,
2000
+ color: tokens.colors.text.primary,
2001
+ whiteSpace: "nowrap",
2002
+ flexShrink: 0
2003
+ },
2004
+ children: countLabel
2005
+ }), !isMobile && /* @__PURE__ */ jsx("div", { style: { flex: 1 } })]
2006
+ }),
2007
+ /* @__PURE__ */ jsxs("div", {
2008
+ style: {
2009
+ display: "flex",
2010
+ alignItems: "center",
2011
+ gap: tokens.spacing.sm,
2012
+ flex: isMobile ? void 0 : 1,
2013
+ justifyContent: isMobile ? "space-between" : "flex-end"
2014
+ },
2015
+ children: [
2016
+ _showFilters && /* @__PURE__ */ jsxs("div", {
2017
+ style: {
2018
+ display: "flex",
2019
+ alignItems: "center",
2020
+ gap: tokens.spacing.xs,
2021
+ flex: isMobile ? 1 : void 0,
2022
+ flexShrink: 0
2023
+ },
2024
+ children: [/* @__PURE__ */ jsxs("div", {
2025
+ style: {
2026
+ position: "relative",
2027
+ width: isMobile ? "100%" : 180
2028
+ },
2029
+ children: [/* @__PURE__ */ jsx(Icon, {
2030
+ name: "search",
2031
+ size: 14,
2032
+ style: {
2033
+ position: "absolute",
2034
+ left: 8,
2035
+ top: "50%",
2036
+ transform: "translateY(-50%)",
2037
+ color: tokens.colors.text.tertiary,
2038
+ pointerEvents: "none"
2039
+ }
2040
+ }), /* @__PURE__ */ jsx("input", {
2041
+ type: "search",
2042
+ value: filter.search ?? "",
2043
+ onChange: (e) => patchFilter({ search: e.target.value || void 0 }),
2044
+ placeholder: "Search",
2045
+ "aria-label": "Search timeline events",
2046
+ style: {
2047
+ width: "100%",
2048
+ boxSizing: "border-box",
2049
+ height: tokens.elementSize.sm,
2050
+ paddingLeft: 28,
2051
+ paddingRight: 8,
2052
+ borderRadius: tokens.borderRadius.md,
2053
+ border: `1px solid ${tokens.colors.border.muted}`,
2054
+ backgroundColor: tokens.colors.background.base,
2055
+ color: tokens.colors.text.primary,
2056
+ fontSize: tokens.typography.fontSize.sm,
2057
+ fontFamily: "inherit",
2058
+ outline: "none",
2059
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)"
2060
+ },
2061
+ onFocus: (e) => {
2062
+ e.target.style.borderColor = tokens.colors.accent.primary;
2063
+ e.target.style.boxShadow = `0 0 0 3px ${tokens.colors.accent.primary}50, 0 0 20px ${tokens.colors.accent.primary}15`;
2064
+ },
2065
+ onBlur: (e) => {
2066
+ e.target.style.borderColor = tokens.colors.border.muted;
2067
+ e.target.style.boxShadow = "none";
2068
+ }
2069
+ })]
2070
+ }), /* @__PURE__ */ jsx(Tooltip, {
2071
+ content: filtersExpanded ? "Hide filters" : "Show filters",
2072
+ children: /* @__PURE__ */ jsxs("button", {
2073
+ type: "button",
2074
+ "aria-label": filtersExpanded ? "Hide filters" : "Show filters",
2075
+ "aria-expanded": filtersExpanded,
2076
+ "aria-controls": "zendir-timeline-filters-panel",
2077
+ onClick: () => setFiltersExpanded((v) => !v),
2078
+ style: {
2079
+ position: "relative",
2080
+ display: "flex",
2081
+ alignItems: "center",
2082
+ justifyContent: "center",
2083
+ width: 28,
2084
+ height: 28,
2085
+ border: `1px solid ${filterChipCount > 0 ? `${tokens.colors.accent.primary}80` : tokens.colors.border.muted}`,
2086
+ borderRadius: tokens.borderRadius.sm,
2087
+ backgroundColor: filterChipCount > 0 ? `${tokens.colors.accent.primary}12` : "transparent",
2088
+ color: filterChipCount > 0 ? tokens.colors.accent.primary : tokens.colors.text.secondary,
2089
+ cursor: "pointer",
2090
+ transition: tokens.animation.fast
2091
+ },
2092
+ children: [/* @__PURE__ */ jsx(Icon, {
2093
+ name: "filter",
2094
+ size: 16
2095
+ }), filterChipCount > 0 ? /* @__PURE__ */ jsx("span", {
2096
+ style: {
2097
+ position: "absolute",
2098
+ top: -4,
2099
+ right: -4,
2100
+ minWidth: 14,
2101
+ height: 14,
2102
+ borderRadius: "50%",
2103
+ backgroundColor: tokens.colors.accent.primary,
2104
+ color: "#fff",
2105
+ fontSize: "0.5625rem",
2106
+ fontWeight: 700,
2107
+ display: "flex",
2108
+ alignItems: "center",
2109
+ justifyContent: "center",
2110
+ lineHeight: 1
2111
+ },
2112
+ children: filterChipCount
2113
+ }) : null]
2114
+ })
2115
+ })]
2116
+ }),
2117
+ /* @__PURE__ */ jsxs("div", {
2118
+ style: {
2119
+ display: "flex",
2120
+ alignItems: "center",
2121
+ gap: 12,
2122
+ flexShrink: 0
2123
+ },
2124
+ children: [zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs("div", {
2125
+ style: {
2126
+ display: "flex",
2127
+ alignItems: "center",
2128
+ gap: 4,
2129
+ paddingRight: 12,
2130
+ borderRight: `1px solid ${tokens.colors.border.muted}`
2131
+ },
2132
+ children: [
2133
+ /* @__PURE__ */ jsx(Tooltip, {
2134
+ content: "Zoom out",
2135
+ children: /* @__PURE__ */ jsx("button", {
2136
+ type: "button",
2137
+ "aria-label": "Zoom out",
2138
+ onClick: () => setZoom(Math.max(zoom - .5, 1)),
2139
+ disabled: zoom <= 1,
2140
+ style: {
2141
+ width: 28,
2142
+ height: 28,
2143
+ display: "flex",
2144
+ alignItems: "center",
2145
+ justifyContent: "center",
2146
+ backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
2147
+ border: `1px solid ${tokens.colors.border.muted}`,
2148
+ borderRadius: tokens.borderRadius.sm,
2149
+ color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2150
+ cursor: zoom <= 1 ? "not-allowed" : "pointer",
2151
+ fontSize: "1rem",
2152
+ fontWeight: 500,
2153
+ opacity: zoom <= 1 ? .5 : 1
2154
+ },
2155
+ children: "−"
2156
+ })
2157
+ }),
2158
+ /* @__PURE__ */ jsxs("span", {
2159
+ style: {
2160
+ minWidth: 40,
2161
+ textAlign: "center",
2162
+ fontSize: "0.75rem",
2163
+ fontFamily: tokens.typography.fontFamily.mono,
2164
+ color: tokens.colors.text.secondary
2165
+ },
2166
+ children: [Math.round(zoom * 100), "%"]
2167
+ }),
2168
+ /* @__PURE__ */ jsx(Tooltip, {
2169
+ content: "Zoom in",
2170
+ children: /* @__PURE__ */ jsx("button", {
2171
+ type: "button",
2172
+ "aria-label": "Zoom in",
2173
+ onClick: () => setZoom(Math.min(zoom + .5, 4)),
2174
+ disabled: zoom >= 4,
2175
+ style: {
2176
+ width: 28,
2177
+ height: 28,
2178
+ display: "flex",
2179
+ alignItems: "center",
2180
+ justifyContent: "center",
2181
+ backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
2182
+ border: `1px solid ${tokens.colors.border.muted}`,
2183
+ borderRadius: tokens.borderRadius.sm,
2184
+ color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2185
+ cursor: zoom >= 4 ? "not-allowed" : "pointer",
2186
+ fontSize: "1rem",
2187
+ fontWeight: 500,
2188
+ opacity: zoom >= 4 ? .5 : 1
2189
+ },
2190
+ children: "+"
2191
+ })
2192
+ }),
2193
+ /* @__PURE__ */ jsx(Tooltip, {
2194
+ content: "Reset zoom",
2195
+ children: /* @__PURE__ */ jsx("button", {
2196
+ type: "button",
2197
+ "aria-label": "Reset zoom",
2198
+ onClick: () => setZoom(1),
2199
+ style: {
2200
+ height: 28,
2201
+ padding: "0 10px",
2202
+ display: "flex",
2203
+ alignItems: "center",
2204
+ justifyContent: "center",
2205
+ backgroundColor: tokens.colors.background.elevated,
2206
+ border: `1px solid ${tokens.colors.border.muted}`,
2207
+ borderRadius: tokens.borderRadius.sm,
2208
+ color: tokens.colors.text.secondary,
2209
+ cursor: "pointer",
2210
+ fontSize: "0.6875rem",
2211
+ fontWeight: 500
2212
+ },
2213
+ children: "FIT"
2214
+ })
2215
+ })
2216
+ ]
2217
+ }), showViewToggle && /* @__PURE__ */ jsx("div", {
2218
+ role: "group",
2219
+ "aria-label": "View mode",
2220
+ style: {
2221
+ display: "flex",
2222
+ gap: "2px",
2223
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
2224
+ ...isTransparentTheme && {
2225
+ backdropFilter: "blur(12px)",
2226
+ WebkitBackdropFilter: "blur(12px)"
2227
+ },
2228
+ padding: "4px",
2229
+ borderRadius: tokens.borderRadius.lg
2230
+ },
2231
+ children: [
2232
+ "chart",
2233
+ "list",
2234
+ "scatter"
2235
+ ].map((mode) => {
2236
+ const isActive = viewMode === mode;
2237
+ const icons = {
2238
+ chart: "chart",
2239
+ list: "list",
2240
+ scatter: "timeline"
2241
+ };
2242
+ const labels = {
2243
+ chart: "Gantt Chart",
2244
+ list: "List View",
2245
+ scatter: "Scatter View"
2246
+ };
2247
+ return /* @__PURE__ */ jsx(Tooltip, {
2248
+ content: labels[mode],
2249
+ children: /* @__PURE__ */ jsx("button", {
2250
+ type: "button",
2251
+ "aria-label": labels[mode],
2252
+ "aria-pressed": isActive,
2253
+ onClick: () => handleViewModeChange(mode),
2254
+ style: {
2255
+ display: "flex",
2256
+ alignItems: "center",
2257
+ justifyContent: "center",
2258
+ width: "32px",
2259
+ height: "32px",
2260
+ backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
2261
+ border: "none",
2262
+ borderRadius: tokens.borderRadius.md,
2263
+ color: isActive ? "#ffffff" : tokens.colors.text.secondary,
2264
+ cursor: "pointer",
2265
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
2266
+ transform: isActive ? "scale(1)" : "scale(0.95)",
2267
+ boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
2268
+ },
2269
+ onMouseEnter: (e) => {
2270
+ if (!isActive) {
2271
+ e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
2272
+ e.currentTarget.style.transform = "scale(1)";
2273
+ }
2274
+ },
2275
+ onMouseLeave: (e) => {
2276
+ if (!isActive) {
2277
+ e.currentTarget.style.backgroundColor = "transparent";
2278
+ e.currentTarget.style.transform = "scale(0.95)";
2279
+ }
2280
+ },
2281
+ children: /* @__PURE__ */ jsx(Icon, {
2282
+ name: icons[mode],
2283
+ size: 16
2284
+ })
2285
+ })
2286
+ }, mode);
2287
+ })
2288
+ })]
2289
+ }),
2290
+ " "
2291
+ ]
2292
+ }),
2293
+ " "
2294
+ ]
2295
+ }),
2296
+ " ",
2297
+ _showFilters ? /* @__PURE__ */ jsx(TimelineFiltersPanel, {
2298
+ filter,
2299
+ onFilterChange: setFilter,
2300
+ trackOptions: trackOptionsForToolbar,
2301
+ events,
2302
+ expanded: filtersExpanded,
2303
+ teamLabel
2304
+ }) : null,
2305
+ loading && /* @__PURE__ */ jsx("div", {
2306
+ style: {
2307
+ position: "absolute",
2308
+ inset: 0,
2309
+ display: "flex",
2310
+ alignItems: "center",
2311
+ justifyContent: "center",
2312
+ backgroundColor: `${tokens.colors.background.overlay}80`,
2313
+ backdropFilter: "blur(4px)",
2314
+ zIndex: Z.loadingOverlay
2315
+ },
2316
+ children: /* @__PURE__ */ jsx("div", { style: {
2317
+ width: 40,
2318
+ height: 40,
2319
+ border: `3px solid ${tokens.colors.border.muted}`,
2320
+ borderTopColor: tokens.colors.accent.primary,
2321
+ borderRadius: "50%",
2322
+ animation: "zendir-spin 1s linear infinite"
2323
+ } })
2324
+ }),
2325
+ /* @__PURE__ */ jsx("div", {
2326
+ style: {
2327
+ padding: viewMode === "list" ? "16px 20px" : "16px",
2328
+ maxHeight,
2329
+ overflowY: maxHeight ? "auto" : void 0,
2330
+ overflowX: viewMode !== "list" ? "auto" : void 0
2331
+ },
2332
+ children: events.length === 0 ? /* @__PURE__ */ jsxs("div", {
2333
+ style: {
2334
+ display: "flex",
2335
+ flexDirection: "column",
2336
+ alignItems: "center",
2337
+ justifyContent: "center",
2338
+ padding: "48px 0",
2339
+ color: tokens.colors.text.tertiary,
2340
+ gap: "12px"
2341
+ },
2342
+ children: [/* @__PURE__ */ jsx(Icon, {
2343
+ name: "clock",
2344
+ size: 32
2345
+ }), /* @__PURE__ */ jsx("span", {
2346
+ style: { fontSize: "0.875rem" },
2347
+ children: "No events to display"
2348
+ })]
2349
+ }) : filteredEvents.length === 0 ? /* @__PURE__ */ jsxs("div", {
2350
+ style: {
2351
+ display: "flex",
2352
+ flexDirection: "column",
2353
+ alignItems: "center",
2354
+ justifyContent: "center",
2355
+ padding: "48px 24px",
2356
+ color: tokens.colors.text.tertiary,
2357
+ gap: 12,
2358
+ textAlign: "center"
2359
+ },
2360
+ children: [
2361
+ /* @__PURE__ */ jsx(Icon, {
2362
+ name: "search",
2363
+ size: 32
2364
+ }),
2365
+ /* @__PURE__ */ jsx("span", {
2366
+ style: {
2367
+ fontSize: "0.875rem",
2368
+ color: tokens.colors.text.secondary
2369
+ },
2370
+ children: "No events match the current filters."
2371
+ }),
2372
+ _showFilters && isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx("button", {
2373
+ type: "button",
2374
+ onClick: () => setFilter({}),
2375
+ style: {
2376
+ marginTop: 4,
2377
+ padding: "8px 16px",
2378
+ borderRadius: tokens.borderRadius.md,
2379
+ border: `1px solid ${tokens.colors.accent.primary}`,
2380
+ backgroundColor: `${tokens.colors.accent.primary}18`,
2381
+ color: tokens.colors.accent.primary,
2382
+ cursor: "pointer",
2383
+ fontSize: "0.8125rem",
2384
+ fontWeight: 500
2385
+ },
2386
+ children: "Clear filters"
2387
+ }) : null
2388
+ ]
2389
+ }) : viewMode === "list" ? sortedEvents.map((event, index) => /* @__PURE__ */ jsx(EventListItem, {
2390
+ event,
2391
+ isLast: index === sortedEvents.length - 1,
2392
+ onEventClick
2393
+ }, event.id)) : viewMode === "scatter" ? /* @__PURE__ */ jsx(ScatterView, {
2394
+ events: filteredEvents,
2395
+ tracks: effectiveTracksForViews,
2396
+ start,
2397
+ end,
2398
+ trackHeight,
2399
+ onEventClick,
2400
+ timeFormat,
2401
+ referenceTime: referenceTime || start,
2402
+ zoom
2403
+ }) : /* @__PURE__ */ jsx(ChartView, {
2404
+ events: filteredEvents,
2405
+ tracks: effectiveTracksForViews,
2406
+ start,
2407
+ end,
2408
+ trackHeight,
2409
+ onEventClick,
2410
+ zoom,
2411
+ showPlayhead,
2412
+ playheadTime,
2413
+ onPlayheadChange: _onPlayheadChangeTimeline,
2414
+ showDayMarkers,
2415
+ timeFormat
2416
+ })
2417
+ }),
2418
+ /* @__PURE__ */ jsx("style", { children: `
2457
2419
  @keyframes zendir-spin {
2458
2420
  from { transform: rotate(0deg); }
2459
2421
  to { transform: rotate(360deg); }
2460
2422
  }
2461
2423
  ` })
2462
- ]
2463
- }
2464
- );
2424
+ ]
2425
+ });
2465
2426
  });
2466
- export {
2467
- TIMELINE_FILTER_TEAM_LEGACY,
2468
- TIMELINE_FILTER_TEAM_NONE,
2469
- UnifiedTimeline,
2470
- getTimelineEventDisplayStatus,
2471
- getTimelineTeamAccent,
2472
- getTimelineTeamDisplayLabel,
2473
- getTimelineTeamFilterKey,
2474
- isTimelineFilterActive,
2475
- matchesTimelineFilter
2476
- };
2477
- //# sourceMappingURL=UnifiedTimeline.js.map
2427
+ //#endregion
2428
+ export { TIMELINE_FILTER_TEAM_LEGACY, TIMELINE_FILTER_TEAM_NONE, UnifiedTimeline, UnifiedTimeline as default, getTimelineEventDisplayStatus, getTimelineTeamAccent, getTimelineTeamDisplayLabel, getTimelineTeamFilterKey, isTimelineFilterActive, matchesTimelineFilter };
2429
+
2430
+ //# sourceMappingURL=UnifiedTimeline.js.map