@deck.gl-community/widgets 9.2.5 → 9.3.0-beta.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 (295) hide show
  1. package/README.md +11 -1
  2. package/dist/graph-widgets/_deprecate/long-press-button.d.ts.map +1 -0
  3. package/dist/graph-widgets/_deprecate/long-press-button.js.map +1 -0
  4. package/dist/graph-widgets/_deprecate/view-control-widget.d.ts.map +1 -0
  5. package/dist/graph-widgets/_deprecate/view-control-widget.js.map +1 -0
  6. package/dist/{widgets → graph-widgets}/long-press-button.d.ts +1 -0
  7. package/dist/graph-widgets/long-press-button.d.ts.map +1 -0
  8. package/dist/{widgets → graph-widgets}/long-press-button.js +1 -0
  9. package/dist/graph-widgets/long-press-button.js.map +1 -0
  10. package/dist/graph-widgets/long-press-controller.d.ts.map +1 -0
  11. package/dist/graph-widgets/long-press-controller.js.map +1 -0
  12. package/dist/{widgets → graph-widgets}/pan-widget.d.ts +3 -2
  13. package/dist/graph-widgets/pan-widget.d.ts.map +1 -0
  14. package/dist/{widgets → graph-widgets}/pan-widget.js +19 -15
  15. package/dist/graph-widgets/pan-widget.js.map +1 -0
  16. package/dist/{widgets → graph-widgets}/zoom-range-widget.d.ts +3 -3
  17. package/dist/graph-widgets/zoom-range-widget.d.ts.map +1 -0
  18. package/dist/{widgets → graph-widgets}/zoom-range-widget.js +24 -23
  19. package/dist/graph-widgets/zoom-range-widget.js.map +1 -0
  20. package/dist/html-overlay-widgets/html-cluster-widget.d.ts.map +1 -0
  21. package/dist/html-overlay-widgets/html-cluster-widget.js.map +1 -0
  22. package/dist/{widgets → html-overlay-widgets}/html-overlay-item.d.ts +1 -0
  23. package/dist/html-overlay-widgets/html-overlay-item.d.ts.map +1 -0
  24. package/dist/html-overlay-widgets/html-overlay-item.js.map +1 -0
  25. package/dist/{widgets → html-overlay-widgets}/html-overlay-widget.d.ts +8 -2
  26. package/dist/html-overlay-widgets/html-overlay-widget.d.ts.map +1 -0
  27. package/dist/{widgets → html-overlay-widgets}/html-overlay-widget.js +21 -7
  28. package/dist/html-overlay-widgets/html-overlay-widget.js.map +1 -0
  29. package/dist/{widgets → html-overlay-widgets}/html-tooltip-widget.d.ts +1 -0
  30. package/dist/html-overlay-widgets/html-tooltip-widget.d.ts.map +1 -0
  31. package/dist/html-overlay-widgets/html-tooltip-widget.js.map +1 -0
  32. package/dist/index.cjs +5119 -87
  33. package/dist/index.cjs.map +4 -4
  34. package/dist/index.d.ts +33 -12
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +27 -6
  37. package/dist/index.js.map +1 -1
  38. package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.d.ts +18 -0
  39. package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.d.ts.map +1 -0
  40. package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.js +47 -0
  41. package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.js.map +1 -0
  42. package/dist/keyboard-shortcuts/keyboard-shortcuts.d.ts +22 -0
  43. package/dist/keyboard-shortcuts/keyboard-shortcuts.d.ts.map +1 -0
  44. package/dist/keyboard-shortcuts/keyboard-shortcuts.js +83 -0
  45. package/dist/keyboard-shortcuts/keyboard-shortcuts.js.map +1 -0
  46. package/dist/lib/settings/settings.d.ts +17 -0
  47. package/dist/lib/settings/settings.d.ts.map +1 -0
  48. package/dist/lib/settings/settings.js +140 -0
  49. package/dist/lib/settings/settings.js.map +1 -0
  50. package/dist/ready-to-upstream-widgets/reset-view-widget.d.ts +21 -0
  51. package/dist/ready-to-upstream-widgets/reset-view-widget.d.ts.map +1 -0
  52. package/dist/ready-to-upstream-widgets/reset-view-widget.js +29 -0
  53. package/dist/ready-to-upstream-widgets/reset-view-widget.js.map +1 -0
  54. package/dist/widget-components/icon-button.d.ts +12 -0
  55. package/dist/widget-components/icon-button.d.ts.map +1 -0
  56. package/dist/widget-components/icon-button.js +12 -0
  57. package/dist/widget-components/icon-button.js.map +1 -0
  58. package/dist/widget-components/select-widget-component.d.ts +15 -0
  59. package/dist/widget-components/select-widget-component.d.ts.map +1 -0
  60. package/dist/widget-components/select-widget-component.js +229 -0
  61. package/dist/widget-components/select-widget-component.js.map +1 -0
  62. package/dist/widget-panels/box-widget.d.ts +43 -0
  63. package/dist/widget-panels/box-widget.d.ts.map +1 -0
  64. package/dist/widget-panels/box-widget.js +191 -0
  65. package/dist/widget-panels/box-widget.js.map +1 -0
  66. package/dist/widget-panels/box-widget.test.d.ts +2 -0
  67. package/dist/widget-panels/box-widget.test.d.ts.map +1 -0
  68. package/dist/widget-panels/box-widget.test.js +41 -0
  69. package/dist/widget-panels/box-widget.test.js.map +1 -0
  70. package/dist/widget-panels/full-screen-panel-widget.d.ts +33 -0
  71. package/dist/widget-panels/full-screen-panel-widget.d.ts.map +1 -0
  72. package/dist/widget-panels/full-screen-panel-widget.js +153 -0
  73. package/dist/widget-panels/full-screen-panel-widget.js.map +1 -0
  74. package/dist/widget-panels/full-screen-panel-widget.test.d.ts +2 -0
  75. package/dist/widget-panels/full-screen-panel-widget.test.d.ts.map +1 -0
  76. package/dist/widget-panels/full-screen-panel-widget.test.js +40 -0
  77. package/dist/widget-panels/full-screen-panel-widget.test.js.map +1 -0
  78. package/dist/widget-panels/heap-memory-widget.d.ts +26 -0
  79. package/dist/widget-panels/heap-memory-widget.d.ts.map +1 -0
  80. package/dist/widget-panels/heap-memory-widget.js +156 -0
  81. package/dist/widget-panels/heap-memory-widget.js.map +1 -0
  82. package/dist/widget-panels/keyboard-shortcuts-widget.d.ts +46 -0
  83. package/dist/widget-panels/keyboard-shortcuts-widget.d.ts.map +1 -0
  84. package/dist/widget-panels/keyboard-shortcuts-widget.js +301 -0
  85. package/dist/widget-panels/keyboard-shortcuts-widget.js.map +1 -0
  86. package/dist/widget-panels/modal-widget.d.ts +62 -0
  87. package/dist/widget-panels/modal-widget.d.ts.map +1 -0
  88. package/dist/widget-panels/modal-widget.js +309 -0
  89. package/dist/widget-panels/modal-widget.js.map +1 -0
  90. package/dist/widget-panels/modal-widget.test.d.ts +2 -0
  91. package/dist/widget-panels/modal-widget.test.d.ts.map +1 -0
  92. package/dist/widget-panels/modal-widget.test.js +103 -0
  93. package/dist/widget-panels/modal-widget.test.js.map +1 -0
  94. package/dist/widget-panels/omni-box-widget.d.ts +59 -0
  95. package/dist/widget-panels/omni-box-widget.d.ts.map +1 -0
  96. package/dist/widget-panels/omni-box-widget.js +562 -0
  97. package/dist/widget-panels/omni-box-widget.js.map +1 -0
  98. package/dist/widget-panels/omni-box-widget.test.d.ts +2 -0
  99. package/dist/widget-panels/omni-box-widget.test.d.ts.map +1 -0
  100. package/dist/widget-panels/omni-box-widget.test.js +49 -0
  101. package/dist/widget-panels/omni-box-widget.test.js.map +1 -0
  102. package/dist/widget-panels/reset-view-widget.d.ts +20 -0
  103. package/dist/widget-panels/reset-view-widget.d.ts.map +1 -0
  104. package/dist/widget-panels/reset-view-widget.js +28 -0
  105. package/dist/widget-panels/reset-view-widget.js.map +1 -0
  106. package/dist/widget-panels/settings-panel.d.ts +49 -0
  107. package/dist/widget-panels/settings-panel.d.ts.map +1 -0
  108. package/dist/widget-panels/settings-panel.js +263 -0
  109. package/dist/widget-panels/settings-panel.js.map +1 -0
  110. package/dist/widget-panels/settings-panel.test.d.ts +2 -0
  111. package/dist/widget-panels/settings-panel.test.d.ts.map +1 -0
  112. package/dist/widget-panels/settings-panel.test.js +217 -0
  113. package/dist/widget-panels/settings-panel.test.js.map +1 -0
  114. package/dist/widget-panels/sidebar-widget.d.ts +65 -0
  115. package/dist/widget-panels/sidebar-widget.d.ts.map +1 -0
  116. package/dist/widget-panels/sidebar-widget.js +339 -0
  117. package/dist/widget-panels/sidebar-widget.js.map +1 -0
  118. package/dist/widget-panels/sidebar-widget.test.d.ts +2 -0
  119. package/dist/widget-panels/sidebar-widget.test.d.ts.map +1 -0
  120. package/dist/widget-panels/sidebar-widget.test.js +175 -0
  121. package/dist/widget-panels/sidebar-widget.test.js.map +1 -0
  122. package/dist/widget-panels/stats-panel.d.ts +34 -0
  123. package/dist/widget-panels/stats-panel.d.ts.map +1 -0
  124. package/dist/widget-panels/stats-panel.js +61 -0
  125. package/dist/widget-panels/stats-panel.js.map +1 -0
  126. package/dist/widget-panels/stats-panel.test.d.ts +2 -0
  127. package/dist/widget-panels/stats-panel.test.d.ts.map +1 -0
  128. package/dist/widget-panels/stats-panel.test.js +36 -0
  129. package/dist/widget-panels/stats-panel.test.js.map +1 -0
  130. package/dist/widget-panels/text-editor-panel-monaco-runtime.d.ts +17 -0
  131. package/dist/widget-panels/text-editor-panel-monaco-runtime.d.ts.map +1 -0
  132. package/dist/widget-panels/text-editor-panel-monaco-runtime.js +69 -0
  133. package/dist/widget-panels/text-editor-panel-monaco-runtime.js.map +1 -0
  134. package/dist/widget-panels/text-editor-panel.d.ts +42 -0
  135. package/dist/widget-panels/text-editor-panel.d.ts.map +1 -0
  136. package/dist/widget-panels/text-editor-panel.js +249 -0
  137. package/dist/widget-panels/text-editor-panel.js.map +1 -0
  138. package/dist/widget-panels/text-editor-panel.test.d.ts +2 -0
  139. package/dist/widget-panels/text-editor-panel.test.d.ts.map +1 -0
  140. package/dist/widget-panels/text-editor-panel.test.js +393 -0
  141. package/dist/widget-panels/text-editor-panel.test.js.map +1 -0
  142. package/dist/widget-panels/time-measure-widget.d.ts +49 -0
  143. package/dist/widget-panels/time-measure-widget.d.ts.map +1 -0
  144. package/dist/widget-panels/time-measure-widget.js +351 -0
  145. package/dist/widget-panels/time-measure-widget.js.map +1 -0
  146. package/dist/widget-panels/toast-manager.d.ts +24 -0
  147. package/dist/widget-panels/toast-manager.d.ts.map +1 -0
  148. package/dist/widget-panels/toast-manager.js +96 -0
  149. package/dist/widget-panels/toast-manager.js.map +1 -0
  150. package/dist/widget-panels/toast-manager.test.d.ts +2 -0
  151. package/dist/widget-panels/toast-manager.test.d.ts.map +1 -0
  152. package/dist/widget-panels/toast-manager.test.js +75 -0
  153. package/dist/widget-panels/toast-manager.test.js.map +1 -0
  154. package/dist/widget-panels/toast-widget.d.ts +20 -0
  155. package/dist/widget-panels/toast-widget.d.ts.map +1 -0
  156. package/dist/widget-panels/toast-widget.js +207 -0
  157. package/dist/widget-panels/toast-widget.js.map +1 -0
  158. package/dist/widget-panels/toast-widget.test.d.ts +2 -0
  159. package/dist/widget-panels/toast-widget.test.d.ts.map +1 -0
  160. package/dist/widget-panels/toast-widget.test.js +81 -0
  161. package/dist/widget-panels/toast-widget.test.js.map +1 -0
  162. package/dist/widget-panels/toggle-widget.d.ts +34 -0
  163. package/dist/widget-panels/toggle-widget.d.ts.map +1 -0
  164. package/dist/widget-panels/toggle-widget.js +46 -0
  165. package/dist/widget-panels/toggle-widget.js.map +1 -0
  166. package/dist/widget-panels/toolbar-widget.d.ts +53 -0
  167. package/dist/widget-panels/toolbar-widget.d.ts.map +1 -0
  168. package/dist/widget-panels/toolbar-widget.js +160 -0
  169. package/dist/widget-panels/toolbar-widget.js.map +1 -0
  170. package/dist/widget-panels/toolbar-widget.test.d.ts +2 -0
  171. package/dist/widget-panels/toolbar-widget.test.d.ts.map +1 -0
  172. package/dist/widget-panels/toolbar-widget.test.js +105 -0
  173. package/dist/widget-panels/toolbar-widget.test.js.map +1 -0
  174. package/dist/widget-panels/widget-containers.d.ts +275 -0
  175. package/dist/widget-panels/widget-containers.d.ts.map +1 -0
  176. package/dist/widget-panels/widget-containers.js +761 -0
  177. package/dist/widget-panels/widget-containers.js.map +1 -0
  178. package/dist/widget-panels/widget-containers.test.d.ts +2 -0
  179. package/dist/widget-panels/widget-containers.test.d.ts.map +1 -0
  180. package/dist/widget-panels/widget-containers.test.js +337 -0
  181. package/dist/widget-panels/widget-containers.test.js.map +1 -0
  182. package/dist/widget-panels/y-zoom-widget.d.ts +66 -0
  183. package/dist/widget-panels/y-zoom-widget.d.ts.map +1 -0
  184. package/dist/widget-panels/y-zoom-widget.js +264 -0
  185. package/dist/widget-panels/y-zoom-widget.js.map +1 -0
  186. package/dist/widget-panels/y-zoom-widget.test.d.ts +2 -0
  187. package/dist/widget-panels/y-zoom-widget.test.d.ts.map +1 -0
  188. package/dist/widget-panels/y-zoom-widget.test.js +71 -0
  189. package/dist/widget-panels/y-zoom-widget.test.js.map +1 -0
  190. package/dist/widgets/heap-memory-widget.d.ts +26 -0
  191. package/dist/widgets/heap-memory-widget.d.ts.map +1 -0
  192. package/dist/widgets/heap-memory-widget.js +158 -0
  193. package/dist/widgets/heap-memory-widget.js.map +1 -0
  194. package/dist/widgets/keyboard-shortcuts-widget.d.ts +28 -0
  195. package/dist/widgets/keyboard-shortcuts-widget.d.ts.map +1 -0
  196. package/dist/widgets/keyboard-shortcuts-widget.js +125 -0
  197. package/dist/widgets/keyboard-shortcuts-widget.js.map +1 -0
  198. package/dist/widgets/omni-box-widget.d.ts +59 -0
  199. package/dist/widgets/omni-box-widget.d.ts.map +1 -0
  200. package/dist/widgets/omni-box-widget.js +493 -0
  201. package/dist/widgets/omni-box-widget.js.map +1 -0
  202. package/dist/widgets/settings-widget.d.ts +64 -0
  203. package/dist/widgets/settings-widget.d.ts.map +1 -0
  204. package/dist/widgets/settings-widget.js +148 -0
  205. package/dist/widgets/settings-widget.js.map +1 -0
  206. package/dist/widgets/view-manager-utils.d.ts +1 -1
  207. package/dist/widgets/view-manager-utils.d.ts.map +1 -1
  208. package/dist/widgets/view-manager-utils.js.map +1 -1
  209. package/package.json +4 -3
  210. package/src/{widgets → graph-widgets}/long-press-button.tsx +1 -0
  211. package/src/{widgets → graph-widgets}/pan-widget.tsx +30 -23
  212. package/src/{widgets → graph-widgets}/zoom-range-widget.tsx +36 -34
  213. package/src/{widgets → html-overlay-widgets}/html-overlay-item.tsx +1 -0
  214. package/src/{widgets → html-overlay-widgets}/html-overlay-widget.tsx +32 -9
  215. package/src/{widgets → html-overlay-widgets}/html-tooltip-widget.tsx +1 -0
  216. package/src/index.ts +109 -12
  217. package/src/keyboard-shortcuts/keyboard-shortcuts-manager.ts +58 -0
  218. package/src/keyboard-shortcuts/keyboard-shortcuts.ts +113 -0
  219. package/src/keyboard-shortcuts/keyboard-shortcuts.ts.disabled +107 -0
  220. package/src/lib/settings/settings.ts +203 -0
  221. package/src/ready-to-upstream-widgets/reset-view-widget.tsx +57 -0
  222. package/src/widget-components/icon-button.tsx +38 -0
  223. package/src/widget-components/select-widget-component.tsx +354 -0
  224. package/src/widget-panels/box-widget.test.tsx +50 -0
  225. package/src/widget-panels/box-widget.tsx +284 -0
  226. package/src/widget-panels/full-screen-panel-widget.test.tsx +49 -0
  227. package/src/widget-panels/full-screen-panel-widget.tsx +223 -0
  228. package/src/widget-panels/heap-memory-widget.tsx +221 -0
  229. package/src/widget-panels/keyboard-shortcuts-widget.tsx +511 -0
  230. package/src/widget-panels/modal-widget.test.tsx +124 -0
  231. package/src/widget-panels/modal-widget.tsx +464 -0
  232. package/src/widget-panels/omni-box-widget.test.tsx +59 -0
  233. package/src/widget-panels/omni-box-widget.tsx +849 -0
  234. package/src/widget-panels/reset-view-widget.tsx +56 -0
  235. package/src/widget-panels/settings-panel.test.tsx +286 -0
  236. package/src/widget-panels/settings-panel.tsx +619 -0
  237. package/src/widget-panels/sidebar-widget.test.tsx +215 -0
  238. package/src/widget-panels/sidebar-widget.tsx +525 -0
  239. package/src/widget-panels/stats-panel.test.tsx +41 -0
  240. package/src/widget-panels/stats-panel.tsx +108 -0
  241. package/src/widget-panels/text-editor-panel-monaco-runtime.ts +97 -0
  242. package/src/widget-panels/text-editor-panel.test.tsx +618 -0
  243. package/src/widget-panels/text-editor-panel.tsx +375 -0
  244. package/src/widget-panels/time-measure-widget.tsx +445 -0
  245. package/src/widget-panels/toast-manager.test.ts +98 -0
  246. package/src/widget-panels/toast-manager.ts +134 -0
  247. package/src/widget-panels/toast-widget.test.tsx +105 -0
  248. package/src/widget-panels/toast-widget.tsx +293 -0
  249. package/src/widget-panels/toggle-widget.tsx +93 -0
  250. package/src/widget-panels/toolbar-widget.test.ts +129 -0
  251. package/src/widget-panels/toolbar-widget.tsx +293 -0
  252. package/src/widget-panels/widget-containers.test.tsx +453 -0
  253. package/src/widget-panels/widget-containers.tsx +1330 -0
  254. package/src/widget-panels/worker-modules.d.ts +7 -0
  255. package/src/widget-panels/y-zoom-widget.test.tsx +101 -0
  256. package/src/widget-panels/y-zoom-widget.tsx +376 -0
  257. package/src/widgets/heap-memory-widget.tsx +223 -0
  258. package/src/widgets/keyboard-shortcuts-widget.tsx +245 -0
  259. package/src/widgets/omni-box-widget.tsx +768 -0
  260. package/src/widgets/settings-widget.tsx +277 -0
  261. package/src/widgets/view-manager-utils.ts +1 -1
  262. package/dist/_deprecate/long-press-button.d.ts.map +0 -1
  263. package/dist/_deprecate/long-press-button.js.map +0 -1
  264. package/dist/_deprecate/view-control-widget.d.ts.map +0 -1
  265. package/dist/_deprecate/view-control-widget.js.map +0 -1
  266. package/dist/widgets/html-cluster-widget.d.ts.map +0 -1
  267. package/dist/widgets/html-cluster-widget.js.map +0 -1
  268. package/dist/widgets/html-overlay-item.d.ts.map +0 -1
  269. package/dist/widgets/html-overlay-item.js.map +0 -1
  270. package/dist/widgets/html-overlay-widget.d.ts.map +0 -1
  271. package/dist/widgets/html-overlay-widget.js.map +0 -1
  272. package/dist/widgets/html-tooltip-widget.d.ts.map +0 -1
  273. package/dist/widgets/html-tooltip-widget.js.map +0 -1
  274. package/dist/widgets/long-press-button.d.ts.map +0 -1
  275. package/dist/widgets/long-press-button.js.map +0 -1
  276. package/dist/widgets/long-press-controller.d.ts.map +0 -1
  277. package/dist/widgets/long-press-controller.js.map +0 -1
  278. package/dist/widgets/pan-widget.d.ts.map +0 -1
  279. package/dist/widgets/pan-widget.js.map +0 -1
  280. package/dist/widgets/zoom-range-widget.d.ts.map +0 -1
  281. package/dist/widgets/zoom-range-widget.js.map +0 -1
  282. /package/dist/{_deprecate → graph-widgets/_deprecate}/long-press-button.d.ts +0 -0
  283. /package/dist/{_deprecate → graph-widgets/_deprecate}/long-press-button.js +0 -0
  284. /package/dist/{_deprecate → graph-widgets/_deprecate}/view-control-widget.d.ts +0 -0
  285. /package/dist/{_deprecate → graph-widgets/_deprecate}/view-control-widget.js +0 -0
  286. /package/dist/{widgets → graph-widgets}/long-press-controller.d.ts +0 -0
  287. /package/dist/{widgets → graph-widgets}/long-press-controller.js +0 -0
  288. /package/dist/{widgets → html-overlay-widgets}/html-cluster-widget.d.ts +0 -0
  289. /package/dist/{widgets → html-overlay-widgets}/html-cluster-widget.js +0 -0
  290. /package/dist/{widgets → html-overlay-widgets}/html-overlay-item.js +0 -0
  291. /package/dist/{widgets → html-overlay-widgets}/html-tooltip-widget.js +0 -0
  292. /package/src/{_deprecate → graph-widgets/_deprecate}/long-press-button.tsx +0 -0
  293. /package/src/{_deprecate → graph-widgets/_deprecate}/view-control-widget.tsx +0 -0
  294. /package/src/{widgets → graph-widgets}/long-press-controller.ts +0 -0
  295. /package/src/{widgets → html-overlay-widgets}/html-cluster-widget.ts +0 -0
@@ -0,0 +1,525 @@
1
+ /** @jsxImportSource preact */
2
+ import {Widget} from '@deck.gl/core';
3
+ import {render} from 'preact';
4
+
5
+ import {asPanelContainer, WidgetContainerRenderer} from './widget-containers';
6
+ import {makeTextIcon} from '../widget-components/icon-button';
7
+
8
+ import type {WidgetContainer, WidgetPanel} from './widget-containers';
9
+ import type {WidgetPlacement, WidgetProps} from '@deck.gl/core';
10
+ import type {JSX} from 'preact';
11
+
12
+ /** Sidebar widget properties. */
13
+ export type SidebarWidgetProps = WidgetProps & {
14
+ /** Trigger icon alias for legacy compatibility. */
15
+ icon?: string;
16
+ /** The content container to show in the sidebar. */
17
+ container?: WidgetContainer;
18
+ /** Optional shorthand panel. When supplied, shown directly inside the sidebar. */
19
+ panel?: WidgetPanel;
20
+ /** Preferred sidebar edge. */
21
+ side?: 'left' | 'right';
22
+ /** Sidebar width in pixels. */
23
+ widthPx?: number;
24
+ /** Container placement inside the selected widget container. */
25
+ placement?: WidgetPlacement;
26
+ /** Sidebar header title. */
27
+ title?: string;
28
+ /** Uncontrolled default open state. */
29
+ defaultOpen?: boolean;
30
+ /**
31
+ * Controlled open state. If supplied, callers own open/closed state.
32
+ */
33
+ open?: boolean;
34
+ /** Called when user intent changes open/closed state. */
35
+ onOpenChange?: (open: boolean) => void;
36
+ /** Optional trigger label. */
37
+ triggerLabel?: string;
38
+ /** Optional trigger icon. Defaults to a panel-like glyph. */
39
+ triggerIcon?: string;
40
+ /**
41
+ * Hides the trigger. Useful when trigger is implemented externally.
42
+ */
43
+ hideTrigger?: boolean;
44
+ /**
45
+ * Whether to render the built-in icon trigger button.
46
+ * If false, a text trigger button is used unless hidden.
47
+ */
48
+ button?: boolean;
49
+ };
50
+
51
+ const SIDEBAR_WIDGET_CLASS = 'deck-widget-sidebar';
52
+ const SIDEBAR_TRIGGER_ICON = makeTextIcon('☰', 16, 24);
53
+ const SIDEBAR_HANDLE_WIDTH_PX = 36;
54
+ const SIDEBAR_HANDLE_GAP_PX = 8;
55
+ const SIDEBAR_TRANSITION_MS = 320;
56
+ const SIDEBAR_OVERLAY_Z_INDEX = '35';
57
+
58
+ /**
59
+ * Resolves the trigger icon from legacy and new prop names.
60
+ */
61
+ function resolveTriggerIcon({icon, triggerIcon}: {icon?: string; triggerIcon?: string}): string {
62
+ if (icon !== undefined) {
63
+ return icon;
64
+ }
65
+
66
+ if (triggerIcon !== undefined) {
67
+ return triggerIcon;
68
+ }
69
+
70
+ return SIDEBAR_TRIGGER_ICON;
71
+ }
72
+
73
+ /**
74
+ * Renders a sidebar container with an edge-mounted trigger and animated slide-over panel.
75
+ */
76
+ function SidebarWidgetView({
77
+ container,
78
+ side,
79
+ title,
80
+ triggerLabel,
81
+ open,
82
+ button,
83
+ hideTrigger,
84
+ panelWidthPx,
85
+ onOpenChange
86
+ }: {
87
+ container: WidgetContainer;
88
+ side: 'left' | 'right';
89
+ title?: string;
90
+ triggerLabel: string;
91
+ open: boolean;
92
+ button: boolean;
93
+ hideTrigger: boolean;
94
+ panelWidthPx: number;
95
+ onOpenChange: (next: boolean) => void;
96
+ }) {
97
+ const shouldRenderShell = open || !hideTrigger;
98
+ const panelWidthWithHandlePx = hideTrigger
99
+ ? panelWidthPx
100
+ : panelWidthPx + SIDEBAR_HANDLE_WIDTH_PX + SIDEBAR_HANDLE_GAP_PX;
101
+ const handleChevron = getSidebarHandleChevron(side, open);
102
+ const handleLabel = open ? `Close ${triggerLabel}` : triggerLabel;
103
+
104
+ return (
105
+ <div>
106
+ {!shouldRenderShell ? null : (
107
+ <aside style={SIDEBAR_PANEL_WRAPPER_STYLE} aria-label={title ?? triggerLabel}>
108
+ <div
109
+ data-sidebar-shell=""
110
+ style={SIDEBAR_SHELL_STYLE(side, panelWidthPx, panelWidthWithHandlePx, open)}
111
+ onPointerDown={stopSidebarEventPropagation}
112
+ onPointerMove={stopSidebarEventPropagation}
113
+ onPointerUp={stopSidebarEventPropagation}
114
+ onMouseDown={stopSidebarEventPropagation}
115
+ onMouseMove={stopSidebarEventPropagation}
116
+ onMouseUp={stopSidebarEventPropagation}
117
+ onTouchStart={stopSidebarEventPropagation}
118
+ onTouchMove={stopSidebarEventPropagation}
119
+ onTouchEnd={stopSidebarEventPropagation}
120
+ onClick={stopSidebarEventPropagation}
121
+ onDblClick={stopSidebarEventPropagation}
122
+ onContextMenu={stopSidebarEventPropagation}
123
+ onWheel={stopSidebarEventPropagation}
124
+ >
125
+ {!hideTrigger && (
126
+ <div data-sidebar-handle="" style={SIDEBAR_HANDLE_WRAPPER_STYLE}>
127
+ {button ? (
128
+ <button
129
+ type="button"
130
+ data-sidebar-handle-button=""
131
+ aria-label={handleLabel}
132
+ title={handleLabel}
133
+ style={SIDEBAR_HANDLE_BUTTON_STYLE}
134
+ onClick={() => onOpenChange(!open)}
135
+ >
136
+ <span aria-hidden="true" style={SIDEBAR_HANDLE_CHEVRON_STYLE}>
137
+ {handleChevron}
138
+ </span>
139
+ </button>
140
+ ) : (
141
+ <div className="deck-widget-button" style={SIDEBAR_HANDLE_BUTTON_WRAPPER_STYLE}>
142
+ <button
143
+ type="button"
144
+ aria-label={open ? `Close ${triggerLabel}` : triggerLabel}
145
+ style={SIDEBAR_TRIGGER_STYLE}
146
+ onPointerUp={() => onOpenChange(!open)}
147
+ >
148
+ {triggerLabel}
149
+ </button>
150
+ </div>
151
+ )}
152
+ </div>
153
+ )}
154
+ <div
155
+ style={SIDEBAR_PANEL_STYLE(side, panelWidthPx, open)}
156
+ role="dialog"
157
+ aria-hidden={!open}
158
+ >
159
+ {title ? (
160
+ <header style={SIDEBAR_HEADER_STYLE}>
161
+ <span>{title}</span>
162
+ </header>
163
+ ) : null}
164
+ <div style={SIDEBAR_CONTENT_STYLE}>
165
+ <WidgetContainerRenderer container={container} />
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </aside>
170
+ )}
171
+ </div>
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Prevents pointer and wheel events from reaching the underlying deck canvas.
177
+ */
178
+ function stopSidebarEventPropagation(event: Event): void {
179
+ event.stopPropagation();
180
+ }
181
+
182
+ /**
183
+ * Normalizes sidebar width configuration into a practical render value.
184
+ */
185
+ function normalizeSidebarWidthPx(widthPx: number): number {
186
+ const clamped = Math.max(220, Math.floor(widthPx));
187
+ return Number.isFinite(clamped) ? clamped : 360;
188
+ }
189
+
190
+ /**
191
+ * A reusable deck widget that renders a side-anchored panel with configurable container content.
192
+ */
193
+ export class SidebarWidget extends Widget<SidebarWidgetProps> {
194
+ static defaultProps: Required<SidebarWidgetProps> = {
195
+ ...Widget.defaultProps,
196
+ id: 'sidebar-widget',
197
+ panel: undefined!,
198
+ container: {
199
+ kind: 'accordeon',
200
+ props: {
201
+ panels: []
202
+ }
203
+ },
204
+ side: 'right',
205
+ widthPx: 360,
206
+ placement: 'top-right',
207
+ title: undefined!,
208
+ defaultOpen: true,
209
+ open: undefined!,
210
+ onOpenChange: undefined!,
211
+ icon: undefined!,
212
+ hideTrigger: false,
213
+ triggerLabel: 'Open sidebar',
214
+ triggerIcon: SIDEBAR_TRIGGER_ICON,
215
+ button: false
216
+ };
217
+
218
+ className = SIDEBAR_WIDGET_CLASS;
219
+ placement: WidgetPlacement = SidebarWidget.defaultProps.placement;
220
+ side: 'left' | 'right' = SidebarWidget.defaultProps.side;
221
+ widthPx = SidebarWidget.defaultProps.widthPx;
222
+ title: string | undefined = SidebarWidget.defaultProps.title;
223
+ triggerLabel = SidebarWidget.defaultProps.triggerLabel;
224
+ hideTrigger = SidebarWidget.defaultProps.hideTrigger;
225
+ triggerIcon = SidebarWidget.defaultProps.triggerIcon;
226
+ button = SidebarWidget.defaultProps.button;
227
+ isOpen = false;
228
+ #hasOpenStateInitialized = false;
229
+ #container: WidgetContainer = SidebarWidget.defaultProps.container;
230
+ #isControlled = false;
231
+ #openChange: ((open: boolean) => void) | undefined = undefined;
232
+ #rootElement: HTMLElement | null = null;
233
+ #overlayParent: HTMLElement | null = null;
234
+
235
+ constructor(props: Partial<SidebarWidgetProps> = {}) {
236
+ super({
237
+ ...SidebarWidget.defaultProps,
238
+ ...props,
239
+ container: props.container ?? asContainer(props.panel),
240
+ triggerIcon: resolveTriggerIcon(props)
241
+ } as SidebarWidgetProps);
242
+ this.setProps(this.props);
243
+ }
244
+
245
+ setProps(props: Partial<SidebarWidgetProps>): void {
246
+ this.#setDisplayProps(props);
247
+ this.#setContainerProps(props);
248
+ this.#setOpenProps(props);
249
+ this.#render();
250
+ super.setProps(props);
251
+ }
252
+
253
+ onAdd(): void {
254
+ this.#render();
255
+ }
256
+
257
+ onRemove(): void {
258
+ if (this.#rootElement) {
259
+ render(null, this.#rootElement);
260
+ }
261
+ }
262
+
263
+ onRenderHTML(rootElement: HTMLElement): void {
264
+ this.#rootElement = rootElement;
265
+ this.#overlayParent ??= this.#resolveOverlayParent(rootElement);
266
+ if (this.#overlayParent && rootElement.parentElement !== this.#overlayParent) {
267
+ this.#overlayParent.append(rootElement);
268
+ }
269
+ const className = ['deck-widget', this.className, this.props.className]
270
+ .filter(Boolean)
271
+ .join(' ');
272
+ rootElement.className = className;
273
+ rootElement.style.position = 'absolute';
274
+ rootElement.style.top = 'var(--widget-margin, 12px)';
275
+ rootElement.style.bottom = 'var(--widget-margin, 12px)';
276
+ rootElement.style.left = this.side === 'left' ? '-1px' : 'var(--widget-margin, 12px)';
277
+ rootElement.style.right = this.side === 'right' ? '-1px' : 'var(--widget-margin, 12px)';
278
+ rootElement.style.width = 'auto';
279
+ rootElement.style.height = 'auto';
280
+ rootElement.style.margin = '0';
281
+ rootElement.style.overflow = 'hidden';
282
+ rootElement.style.pointerEvents = 'none';
283
+ rootElement.style.zIndex = SIDEBAR_OVERLAY_Z_INDEX;
284
+ (this.props as {_widgetContainer?: 'overlay'})._widgetContainer = 'overlay';
285
+ this.#render();
286
+ }
287
+
288
+ #handleOpenChange = (nextOpen: boolean) => {
289
+ if (!this.#isControlled) {
290
+ this.isOpen = nextOpen;
291
+ }
292
+ this.#openChange?.(nextOpen);
293
+ this.#render();
294
+ };
295
+
296
+ #render = () => {
297
+ if (!this.#rootElement) {
298
+ return;
299
+ }
300
+
301
+ render(
302
+ <SidebarWidgetView
303
+ container={this.#container}
304
+ side={this.side}
305
+ title={this.title}
306
+ triggerLabel={this.triggerLabel}
307
+ open={this.isOpen}
308
+ button={this.button}
309
+ hideTrigger={this.hideTrigger}
310
+ panelWidthPx={this.widthPx}
311
+ onOpenChange={this.#handleOpenChange}
312
+ />,
313
+ this.#rootElement
314
+ );
315
+ };
316
+
317
+ #setDisplayProps(props: Partial<SidebarWidgetProps>): void {
318
+ if (props.icon !== undefined) {
319
+ this.triggerIcon = props.icon;
320
+ }
321
+ if (props.placement !== undefined) {
322
+ this.placement = props.placement;
323
+ }
324
+ if ('title' in props) {
325
+ this.title = props.title;
326
+ }
327
+ if (props.side !== undefined) {
328
+ this.side = props.side;
329
+ }
330
+ if (props.widthPx !== undefined) {
331
+ this.widthPx = normalizeSidebarWidthPx(props.widthPx);
332
+ }
333
+ if (props.triggerLabel !== undefined) {
334
+ this.triggerLabel = props.triggerLabel;
335
+ }
336
+ if (props.triggerIcon !== undefined) {
337
+ this.triggerIcon = props.triggerIcon;
338
+ }
339
+ if (props.hideTrigger !== undefined) {
340
+ this.hideTrigger = props.hideTrigger;
341
+ }
342
+ if (props.button !== undefined) {
343
+ this.button = props.button;
344
+ }
345
+ }
346
+
347
+ #setContainerProps(props: Partial<SidebarWidgetProps>): void {
348
+ if (props.container !== undefined) {
349
+ this.#container = props.container;
350
+ } else if (props.panel !== undefined) {
351
+ this.#container = asContainer(props.panel);
352
+ }
353
+ if (props.onOpenChange !== undefined) {
354
+ this.#openChange = props.onOpenChange;
355
+ }
356
+ }
357
+
358
+ #setOpenProps(props: Partial<SidebarWidgetProps>): void {
359
+ this.#isControlled = props.open !== undefined;
360
+ if (props.open !== undefined) {
361
+ this.isOpen = props.open;
362
+ this.#hasOpenStateInitialized = true;
363
+ return;
364
+ }
365
+ if (!this.#hasOpenStateInitialized && props.defaultOpen !== undefined) {
366
+ this.isOpen = props.defaultOpen;
367
+ this.#hasOpenStateInitialized = true;
368
+ }
369
+ }
370
+
371
+ #resolveOverlayParent(rootElement: HTMLElement): HTMLElement | null {
372
+ const explicitContainer = this.props._container;
373
+ if (explicitContainer && typeof explicitContainer !== 'string') {
374
+ return explicitContainer;
375
+ }
376
+ return rootElement.parentElement?.parentElement;
377
+ }
378
+ }
379
+
380
+ function asContainer(panel?: WidgetPanel): WidgetContainer {
381
+ if (panel === undefined) {
382
+ return {
383
+ kind: 'accordeon',
384
+ props: {
385
+ panels: []
386
+ }
387
+ };
388
+ }
389
+ return asPanelContainer(panel);
390
+ }
391
+
392
+ /**
393
+ * Returns the directional chevron shown in the built-in sidebar handle.
394
+ */
395
+ function getSidebarHandleChevron(side: 'left' | 'right', open: boolean): string {
396
+ if (side === 'left') {
397
+ return open ? '‹' : '›';
398
+ }
399
+
400
+ return open ? '›' : '‹';
401
+ }
402
+
403
+ const SIDEBAR_TRIGGER_STYLE: JSX.CSSProperties = {
404
+ border: '1px solid var(--menu-border, rgba(148, 163, 184, 0.35))',
405
+ borderRadius: '6px',
406
+ background: 'var(--menu-background, #fff)',
407
+ color: 'var(--button-text, rgb(24, 24, 26))',
408
+ fontSize: '12px',
409
+ lineHeight: '1.1',
410
+ padding: '8px 10px',
411
+ cursor: 'pointer'
412
+ };
413
+
414
+ const SIDEBAR_PANEL_WRAPPER_STYLE: JSX.CSSProperties = {
415
+ position: 'absolute',
416
+ inset: '0',
417
+ pointerEvents: 'none',
418
+ zIndex: 31
419
+ };
420
+
421
+ const SIDEBAR_SHELL_STYLE = (
422
+ side: 'left' | 'right',
423
+ panelWidthPx: number,
424
+ panelWidthWithHandlePx: number,
425
+ open: boolean
426
+ ): JSX.CSSProperties => ({
427
+ position: 'absolute',
428
+ top: '0',
429
+ bottom: '0',
430
+ [side]: '0',
431
+ width: `${panelWidthWithHandlePx}px`,
432
+ display: 'flex',
433
+ flexDirection: side === 'left' ? 'row-reverse' : 'row',
434
+ alignItems: 'flex-start',
435
+ pointerEvents: 'auto',
436
+ transform: open
437
+ ? 'translateX(0px)'
438
+ : `translateX(${side === 'left' ? -panelWidthPx : panelWidthPx}px)`,
439
+ transition: `transform ${SIDEBAR_TRANSITION_MS}ms cubic-bezier(0.22, 1, 0.36, 1)`,
440
+ willChange: 'transform',
441
+ gap: `${SIDEBAR_HANDLE_GAP_PX}px`
442
+ });
443
+
444
+ const SIDEBAR_HANDLE_WRAPPER_STYLE: JSX.CSSProperties = {
445
+ width: `${SIDEBAR_HANDLE_WIDTH_PX}px`,
446
+ display: 'flex',
447
+ alignItems: 'flex-start',
448
+ justifyContent: 'center',
449
+ pointerEvents: 'auto'
450
+ };
451
+
452
+ const SIDEBAR_HANDLE_BUTTON_WRAPPER_STYLE: JSX.CSSProperties = {
453
+ display: 'flex',
454
+ alignItems: 'center',
455
+ justifyContent: 'center'
456
+ };
457
+
458
+ const SIDEBAR_HANDLE_BUTTON_STYLE: JSX.CSSProperties = {
459
+ width: `${SIDEBAR_HANDLE_WIDTH_PX}px`,
460
+ minWidth: `${SIDEBAR_HANDLE_WIDTH_PX}px`,
461
+ height: '40px',
462
+ border: 'var(--button-inner-stroke, 1px solid rgba(148, 163, 184, 0.35))',
463
+ borderRadius: '2px',
464
+ background: 'var(--button-background, #fff)',
465
+ backdropFilter: 'var(--button-backdrop-filter, unset)',
466
+ color: 'var(--button-text, rgb(24, 24, 26))',
467
+ boxShadow: 'var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25))',
468
+ display: 'flex',
469
+ alignItems: 'center',
470
+ justifyContent: 'center',
471
+ cursor: 'pointer',
472
+ pointerEvents: 'auto',
473
+ padding: '0'
474
+ };
475
+
476
+ const SIDEBAR_HANDLE_CHEVRON_STYLE: JSX.CSSProperties = {
477
+ display: 'block',
478
+ fontSize: '22px',
479
+ fontWeight: 700,
480
+ lineHeight: '1',
481
+ transform: 'translateY(-1px)',
482
+ color: 'var(--button-icon-idle, #616166)',
483
+ transition: `transform ${SIDEBAR_TRANSITION_MS}ms cubic-bezier(0.22, 1, 0.36, 1)`
484
+ };
485
+
486
+ const SIDEBAR_PANEL_STYLE = (
487
+ side: 'left' | 'right',
488
+ panelWidthPx: number,
489
+ open: boolean
490
+ ): JSX.CSSProperties => ({
491
+ pointerEvents: 'auto',
492
+ width: `${panelWidthPx}px`,
493
+ maxWidth: `min(84vw, ${panelWidthPx}px)`,
494
+ minWidth: `${Math.min(panelWidthPx, 260)}px`,
495
+ height: '100%',
496
+ borderLeft:
497
+ side === 'right' ? '1px solid var(--menu-border, rgba(148, 163, 184, 0.35))' : undefined,
498
+ borderRight:
499
+ side === 'left' ? '1px solid var(--menu-border, rgba(148, 163, 184, 0.35))' : undefined,
500
+ background: 'var(--menu-background, #fff)',
501
+ color: 'var(--menu-text, rgb(24, 24, 26))',
502
+ boxShadow: 'var(--menu-shadow, -8px 0 25px rgba(0, 0, 0, 0.22))',
503
+ display: 'flex',
504
+ flexDirection: 'column',
505
+ opacity: open ? 1 : 0.98
506
+ });
507
+
508
+ const SIDEBAR_HEADER_STYLE: JSX.CSSProperties = {
509
+ display: 'flex',
510
+ alignItems: 'center',
511
+ justifyContent: 'flex-start',
512
+ gap: '10px',
513
+ padding: '10px 12px',
514
+ borderBottom: 'var(--menu-divider, var(--menu-border, 1px solid rgba(148, 163, 184, 0.25)))',
515
+ background: 'var(--menu-weak-background, var(--button-background, var(--menu-background, #fff)))',
516
+ color: 'var(--menu-text, rgb(24, 24, 26))',
517
+ fontSize: '13px',
518
+ fontWeight: 700
519
+ };
520
+
521
+ const SIDEBAR_CONTENT_STYLE: JSX.CSSProperties = {
522
+ flex: 1,
523
+ overflow: 'auto',
524
+ padding: '10px'
525
+ };
@@ -0,0 +1,41 @@
1
+ /** @jsxImportSource preact */
2
+ import {render} from 'preact';
3
+ import {afterEach, describe, expect, it} from 'vitest';
4
+ import {Stats} from '@probe.gl/stats';
5
+
6
+ import {StatsPanel} from './stats-panel';
7
+
8
+ afterEach(() => {
9
+ document.body.innerHTML = '';
10
+ });
11
+
12
+ describe('StatsPanel', () => {
13
+ it('renders provided stats and labels', () => {
14
+ const stats = new Stats({
15
+ id: 'tileset-stats',
16
+ stats: [{name: 'Visible Tiles'}, {name: 'Tiles In Cache'}]
17
+ });
18
+ stats.get('Visible Tiles').addCount(12);
19
+ stats.get('Tiles In Cache').addCount(34);
20
+
21
+ const panel = new StatsPanel({
22
+ id: 'stats-panel',
23
+ title: 'Stats',
24
+ stats,
25
+ statNames: ['Visible Tiles', 'Tiles In Cache'],
26
+ labels: {
27
+ 'Visible Tiles': 'Visible tiles across views',
28
+ 'Tiles In Cache': 'Total tiles in cache'
29
+ }
30
+ });
31
+
32
+ const root = document.createElement('div');
33
+ document.body.appendChild(root);
34
+ render(panel.content, root);
35
+
36
+ expect(root.textContent).toContain('Visible tiles across views');
37
+ expect(root.textContent).toContain('12');
38
+ expect(root.textContent).toContain('Total tiles in cache');
39
+ expect(root.textContent).toContain('34');
40
+ });
41
+ });
@@ -0,0 +1,108 @@
1
+ /** @jsxImportSource preact */
2
+ import {useMemo} from 'preact/hooks';
3
+
4
+ import {useEffectiveWidgetPanelThemeMode} from './widget-containers';
5
+
6
+ import type {WidgetPanel, WidgetPanelTheme} from './widget-containers';
7
+ import type {JSX} from 'preact';
8
+ import type {Stats} from '@probe.gl/stats';
9
+
10
+ /** Props for {@link StatsPanel}. */
11
+ export type StatsPanelProps = {
12
+ /** Stable panel id used by parent containers. */
13
+ id: string;
14
+ /** Visible heading text for the panel. */
15
+ title: string;
16
+ /** Probe.gl stats bag rendered by this panel. */
17
+ stats: Stats;
18
+ /** Optional stat names to render and their order. Defaults to all stats in insertion order. */
19
+ statNames?: string[];
20
+ /** Optional label mapping for displayed stat names. */
21
+ labels?: Partial<Record<string, string>>;
22
+ /** Optional class name applied to the outer panel content wrapper. */
23
+ className?: string;
24
+ /** Optional theme override applied to this panel subtree. */
25
+ theme?: WidgetPanelTheme;
26
+ };
27
+
28
+ /** Widget panel that renders a compact table of probe.gl stats. */
29
+ export class StatsPanel implements WidgetPanel {
30
+ /** Stable panel id used by parent containers. */
31
+ id: string;
32
+ /** Visible heading text for the panel. */
33
+ title: string;
34
+ /** Optional theme override applied to this panel subtree. */
35
+ theme?: WidgetPanelTheme;
36
+ /** Rendered Preact content for this panel. */
37
+ content: JSX.Element;
38
+
39
+ /** Creates a stats panel for one probe.gl {@link Stats} bag. */
40
+ constructor(props: StatsPanelProps) {
41
+ this.id = props.id;
42
+ this.title = props.title;
43
+ this.theme = props.theme ?? 'inherit';
44
+ this.content = <StatsPanelContent {...props} />;
45
+ }
46
+ }
47
+
48
+ /** Renders the stats rows used by {@link StatsPanel}. */
49
+ function StatsPanelContent({stats, statNames, labels, className}: StatsPanelProps): JSX.Element {
50
+ const themeMode = useEffectiveWidgetPanelThemeMode();
51
+ const rows = useMemo(() => {
52
+ const table = stats.getTable();
53
+ const names = statNames ?? Object.keys(table);
54
+ return names
55
+ .filter((name) => table[name])
56
+ .map((name) => ({
57
+ name,
58
+ label: labels?.[name] ?? name,
59
+ value: table[name].count
60
+ }));
61
+ }, [labels, statNames, stats]);
62
+
63
+ /** Theme-aware colors used by the stats table. */
64
+ const colors =
65
+ themeMode === 'dark'
66
+ ? {
67
+ text: '#f8fafc',
68
+ muted: 'rgba(226, 232, 240, 0.78)',
69
+ divider: 'rgba(148, 163, 184, 0.22)'
70
+ }
71
+ : {
72
+ text: '#0f172a',
73
+ muted: 'rgba(15, 23, 42, 0.72)',
74
+ divider: 'rgba(15, 23, 42, 0.12)'
75
+ };
76
+
77
+ return (
78
+ <div className={className} style={{display: 'grid', gap: '8px'}}>
79
+ {rows.map((row, index) => (
80
+ <div
81
+ key={row.name}
82
+ style={{
83
+ display: 'grid',
84
+ gridTemplateColumns: '1fr auto',
85
+ gap: '12px',
86
+ alignItems: 'baseline',
87
+ paddingTop: index === 0 ? '0' : '8px',
88
+ borderTop: index === 0 ? 'none' : `1px solid ${colors.divider}`
89
+ }}
90
+ >
91
+ <span
92
+ style={{color: colors.muted, font: '600 12px/1.4 ui-sans-serif,system-ui,sans-serif'}}
93
+ >
94
+ {row.label}
95
+ </span>
96
+ <span
97
+ style={{
98
+ color: colors.text,
99
+ font: '700 12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace'
100
+ }}
101
+ >
102
+ {row.value}
103
+ </span>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ );
108
+ }