@deck.gl-community/widgets 9.2.8 → 9.3.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +9 -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 +1 -1
  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 +2 -2
  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 +5102 -82
  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 +2 -2
  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,849 @@
1
+ /** @jsxImportSource preact */
2
+ import {Widget} from '@deck.gl/core';
3
+ import {render} from 'preact';
4
+ import {useCallback, useEffect, useRef, useState} from 'preact/hooks';
5
+
6
+ import type {Deck, Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
7
+ import type {ComponentChildren, JSX} from 'preact';
8
+
9
+ const OPTION_ROW_HEIGHT_PX = 32;
10
+ const MAX_VISIBLE_OPTION_COUNT = 4;
11
+ const BLUR_CLOSE_DELAY_MS = 100;
12
+ const OMNIBOX_MAX_WIDTH_PX = 520;
13
+ const OMNIBOX_HORIZONTAL_MARGIN_PX = 12;
14
+ const FALLBACK_WIDGET_MARGIN_PX = 8;
15
+ const WIDGET_FONT_FAMILY =
16
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif";
17
+ const OMNIBOX_CONTROL_HEIGHT = 'max(32px, calc(var(--button-size, 28px) - 2px))';
18
+ const OMNIBOX_INPUT_CLASS = 'deck-widget-omni-box-input';
19
+ const OMNIBOX_BUTTON_CLASS = 'deck-widget-omni-box-button';
20
+ const OMNIBOX_OPTION_CLASS = 'deck-widget-omni-box-option';
21
+ const OMNIBOX_OPTION_ACTIVE_CLASS = 'deck-widget-omni-box-option-active';
22
+
23
+ const ROOT_STYLE: Partial<CSSStyleDeclaration> = {
24
+ position: 'fixed',
25
+ transform: 'translateX(-50%)',
26
+ margin: '0',
27
+ zIndex: '2',
28
+ pointerEvents: 'auto'
29
+ };
30
+
31
+ const WRAPPER_STYLE: JSX.CSSProperties = {
32
+ width: '100%',
33
+ display: 'flex',
34
+ flexDirection: 'column',
35
+ gap: 'var(--menu-gap, 4px)'
36
+ };
37
+
38
+ const INPUT_ROW_STYLE: JSX.CSSProperties = {
39
+ width: '100%',
40
+ display: 'grid',
41
+ gridTemplateColumns: '1fr auto auto auto',
42
+ gap: '1px',
43
+ padding: '1px',
44
+ overflow: 'hidden',
45
+ backgroundColor: 'var(--button-stroke, rgba(255, 255, 255, 0.3))',
46
+ borderRadius: 'var(--button-corner-radius, 8px)',
47
+ boxShadow: 'var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25))'
48
+ };
49
+
50
+ const CONTROL_SURFACE_STYLE: JSX.CSSProperties = {
51
+ height: OMNIBOX_CONTROL_HEIGHT,
52
+ backgroundColor: 'var(--button-background, #fff)',
53
+ backdropFilter: 'var(--button-backdrop-filter, unset)',
54
+ WebkitBackdropFilter: 'var(--button-backdrop-filter, unset)',
55
+ border: 'var(--button-inner-stroke, unset)'
56
+ };
57
+
58
+ const INPUT_STYLE: JSX.CSSProperties = {
59
+ ...CONTROL_SURFACE_STYLE,
60
+ width: '100%',
61
+ minWidth: 0,
62
+ boxSizing: 'border-box',
63
+ padding: '0 12px',
64
+ borderRadius: 'calc(var(--button-corner-radius, 8px) - 1px)',
65
+ borderTopRightRadius: 0,
66
+ borderBottomRightRadius: 0,
67
+ color: 'var(--button-text, rgb(24, 24, 26))',
68
+ fontSize: '13px',
69
+ lineHeight: 1.2,
70
+ fontFamily: WIDGET_FONT_FAMILY
71
+ };
72
+
73
+ const NAV_BUTTON_STYLE: JSX.CSSProperties = {
74
+ ...CONTROL_SURFACE_STYLE,
75
+ minWidth: 'calc(var(--button-size, 28px) - 2px)',
76
+ padding: '0 10px',
77
+ borderRadius: 0,
78
+ color: 'var(--button-icon-idle, rgba(97, 97, 102, 1))',
79
+ fontSize: '13px',
80
+ fontWeight: 600,
81
+ fontFamily: WIDGET_FONT_FAMILY,
82
+ cursor: 'pointer',
83
+ userSelect: 'none',
84
+ display: 'flex',
85
+ alignItems: 'center',
86
+ justifyContent: 'center'
87
+ };
88
+
89
+ const NAV_BUTTON_DISABLED_STYLE: JSX.CSSProperties = {
90
+ opacity: 0.45,
91
+ cursor: 'not-allowed'
92
+ };
93
+
94
+ const LAST_NAV_BUTTON_STYLE: JSX.CSSProperties = {
95
+ borderTopRightRadius: 'calc(var(--button-corner-radius, 8px) - 1px)',
96
+ borderBottomRightRadius: 'calc(var(--button-corner-radius, 8px) - 1px)'
97
+ };
98
+
99
+ const DROPDOWN_STYLE: JSX.CSSProperties = {
100
+ borderRadius: 'var(--button-corner-radius, 8px)',
101
+ border: 'var(--menu-border, unset)',
102
+ backgroundColor: 'var(--menu-background, #fff)',
103
+ backdropFilter: 'var(--menu-backdrop-filter, unset)',
104
+ WebkitBackdropFilter: 'var(--menu-backdrop-filter, unset)',
105
+ boxShadow: 'var(--menu-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25))',
106
+ color: 'var(--menu-text, rgb(24, 24, 26))',
107
+ overflowY: 'auto',
108
+ maxHeight: `${OPTION_ROW_HEIGHT_PX * MAX_VISIBLE_OPTION_COUNT}px`,
109
+ padding: '4px 0'
110
+ };
111
+
112
+ const DEFAULT_OPTION_CONTENT_STYLE: JSX.CSSProperties = {
113
+ width: '100%',
114
+ height: '100%',
115
+ display: 'flex',
116
+ alignItems: 'center',
117
+ justifyContent: 'space-between',
118
+ gap: '10px'
119
+ };
120
+
121
+ function stopEventPropagation(event: Event): void {
122
+ event.stopPropagation();
123
+ if (
124
+ typeof (event as {stopImmediatePropagation?: () => void}).stopImmediatePropagation ===
125
+ 'function'
126
+ ) {
127
+ (event as {stopImmediatePropagation: () => void}).stopImmediatePropagation();
128
+ }
129
+ }
130
+
131
+ function isEditableTarget(target: EventTarget | null): boolean {
132
+ if (!(target instanceof Element)) {
133
+ return false;
134
+ }
135
+
136
+ if (target instanceof HTMLInputElement) {
137
+ return target.type !== 'button' && target.type !== 'checkbox' && target.type !== 'radio';
138
+ }
139
+
140
+ if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
141
+ return true;
142
+ }
143
+
144
+ return target instanceof HTMLElement ? target.isContentEditable : false;
145
+ }
146
+
147
+ function getWidgetMarginPx(element: HTMLElement): number {
148
+ const value = window.getComputedStyle(element).getPropertyValue('--widget-margin').trim();
149
+ const parsed = Number.parseFloat(value);
150
+ return Number.isFinite(parsed) ? parsed : FALLBACK_WIDGET_MARGIN_PX;
151
+ }
152
+
153
+ function getDeckCanvasRect(deck: Deck | undefined): DOMRect | null {
154
+ const canvas = (deck as (Deck & {canvas?: HTMLCanvasElement | null}) | undefined)?.canvas;
155
+ if (!canvas) {
156
+ return null;
157
+ }
158
+ return canvas.getBoundingClientRect();
159
+ }
160
+
161
+ export type OmniBoxOption = {
162
+ id: string;
163
+ label: string;
164
+ value?: string;
165
+ description?: string;
166
+ data?: unknown;
167
+ };
168
+
169
+ export type OmniBoxOptionProvider =
170
+ | ((query: string) => Promise<ReadonlyArray<OmniBoxOption>>)
171
+ | ((query: string) => ReadonlyArray<OmniBoxOption>);
172
+
173
+ export type OmniBoxRenderOptionArgs = {
174
+ option: OmniBoxOption;
175
+ index: number;
176
+ isActive: boolean;
177
+ query: string;
178
+ };
179
+
180
+ export type OmniBoxWidgetProps = WidgetProps & {
181
+ placement?: WidgetPlacement;
182
+ placeholder?: string;
183
+ minQueryLength?: number;
184
+ defaultOpen?: boolean;
185
+ topOffsetPx?: number;
186
+ getOptions?: OmniBoxOptionProvider;
187
+ renderOption?: (args: OmniBoxRenderOptionArgs) => ComponentChildren;
188
+ onSelectOption?: (option: OmniBoxOption) => void;
189
+ onActiveOptionChange?: (option: OmniBoxOption | null) => void;
190
+ onNavigateOption?: (option: OmniBoxOption) => void;
191
+ onQueryChange?: (query: string) => void;
192
+ };
193
+
194
+ type OmniBoxWidgetViewProps = {
195
+ placeholder: string;
196
+ minQueryLength: number;
197
+ defaultOpen: boolean;
198
+ getOptions: OmniBoxOptionProvider;
199
+ renderOption?: (args: OmniBoxRenderOptionArgs) => ComponentChildren;
200
+ onSelectOption?: (option: OmniBoxOption) => void;
201
+ onActiveOptionChange?: (option: OmniBoxOption | null) => void;
202
+ onNavigateOption?: (option: OmniBoxOption) => void;
203
+ onQueryChange?: (query: string) => void;
204
+ };
205
+
206
+ function DefaultOptionContent({option}: {option: OmniBoxOption}) {
207
+ return (
208
+ <div style={DEFAULT_OPTION_CONTENT_STYLE}>
209
+ <span
210
+ style={{
211
+ fontSize: '12px',
212
+ color: 'var(--menu-text, rgb(24, 24, 26))',
213
+ overflow: 'hidden',
214
+ textOverflow: 'ellipsis',
215
+ whiteSpace: 'nowrap'
216
+ }}
217
+ >
218
+ {option.label}
219
+ </span>
220
+ {option.description && (
221
+ <span
222
+ style={{
223
+ fontSize: '11px',
224
+ color: 'var(--menu-text, rgb(24, 24, 26))',
225
+ opacity: 0.7,
226
+ overflow: 'hidden',
227
+ textOverflow: 'ellipsis',
228
+ whiteSpace: 'nowrap'
229
+ }}
230
+ >
231
+ {option.description}
232
+ </span>
233
+ )}
234
+ </div>
235
+ );
236
+ }
237
+
238
+ function OmniBoxWidgetStyles() {
239
+ return (
240
+ <style>{`
241
+ .deck-widget-omni-box .${OMNIBOX_INPUT_CLASS}:focus,
242
+ .deck-widget-omni-box .${OMNIBOX_BUTTON_CLASS}:focus,
243
+ .deck-widget-omni-box .${OMNIBOX_OPTION_CLASS}:focus {
244
+ outline: none;
245
+ }
246
+
247
+ .deck-widget-omni-box [data-omni-box-controls='true'] {
248
+ background-color: var(--button-stroke, rgba(255, 255, 255, 0.3));
249
+ }
250
+
251
+ .deck-widget-omni-box [data-omni-box-dropdown='true'] {
252
+ background-color: var(--menu-background, #fff);
253
+ backdrop-filter: var(--menu-backdrop-filter, unset);
254
+ -webkit-backdrop-filter: var(--menu-backdrop-filter, unset);
255
+ box-shadow: var(--menu-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25));
256
+ }
257
+
258
+ .deck-widget-omni-box .${OMNIBOX_INPUT_CLASS}::placeholder {
259
+ color: var(--button-icon-idle, rgba(97, 97, 102, 1));
260
+ opacity: 1;
261
+ }
262
+
263
+ .deck-widget-omni-box .${OMNIBOX_BUTTON_CLASS}:not(:disabled):hover {
264
+ color: var(--button-icon-hover, rgba(24, 24, 26, 1));
265
+ }
266
+
267
+ .deck-widget-omni-box .${OMNIBOX_OPTION_CLASS}:not(.${OMNIBOX_OPTION_ACTIVE_CLASS}):hover {
268
+ background: var(--menu-item-hover, rgba(0, 0, 0, 0.08));
269
+ }
270
+ `}</style>
271
+ );
272
+ }
273
+
274
+ // eslint-disable-next-line max-statements
275
+ function OmniBoxWidgetView({
276
+ placeholder,
277
+ minQueryLength,
278
+ defaultOpen,
279
+ getOptions,
280
+ renderOption,
281
+ onSelectOption,
282
+ onActiveOptionChange,
283
+ onNavigateOption,
284
+ onQueryChange
285
+ }: OmniBoxWidgetViewProps) {
286
+ const [query, setQuery] = useState('');
287
+ const [options, setOptions] = useState<ReadonlyArray<OmniBoxOption>>([]);
288
+ const [activeOptionIndex, setActiveOptionIndex] = useState<number>(-1);
289
+ const [isLoading, setIsLoading] = useState(false);
290
+ const [isFocused, setIsFocused] = useState(false);
291
+ const [isHidden, setIsHidden] = useState(() => !defaultOpen);
292
+
293
+ const inputRef = useRef<HTMLInputElement | null>(null);
294
+ const dropdownRef = useRef<HTMLDivElement | null>(null);
295
+ const optionElementRefs = useRef<Array<HTMLButtonElement | null>>([]);
296
+ const requestVersionRef = useRef(0);
297
+ const blurTimeoutRef = useRef<number | null>(null);
298
+
299
+ const clearBlurTimeout = useCallback(() => {
300
+ if (blurTimeoutRef.current !== null) {
301
+ window.clearTimeout(blurTimeoutRef.current);
302
+ blurTimeoutRef.current = null;
303
+ }
304
+ }, []);
305
+
306
+ useEffect(() => {
307
+ return () => {
308
+ clearBlurTimeout();
309
+ };
310
+ }, [clearBlurTimeout]);
311
+
312
+ useEffect(() => {
313
+ const handleWindowKeyDown = (event: KeyboardEvent) => {
314
+ if (event.key !== '/' || event.altKey || event.ctrlKey || event.metaKey) {
315
+ return;
316
+ }
317
+
318
+ if (isEditableTarget(event.target)) {
319
+ return;
320
+ }
321
+
322
+ event.preventDefault();
323
+ stopEventPropagation(event);
324
+ clearBlurTimeout();
325
+ setIsHidden(false);
326
+ setIsFocused(true);
327
+
328
+ window.requestAnimationFrame(() => {
329
+ inputRef.current?.focus();
330
+ });
331
+ };
332
+
333
+ window.addEventListener('keydown', handleWindowKeyDown, true);
334
+ return () => {
335
+ window.removeEventListener('keydown', handleWindowKeyDown, true);
336
+ };
337
+ }, [clearBlurTimeout]);
338
+
339
+ useEffect(() => {
340
+ setIsHidden(!defaultOpen);
341
+ }, [defaultOpen]);
342
+
343
+ useEffect(() => {
344
+ onQueryChange?.(query);
345
+
346
+ const normalizedQuery = query.trim();
347
+ if (normalizedQuery.length < minQueryLength) {
348
+ setOptions([]);
349
+ setActiveOptionIndex(-1);
350
+ setIsLoading(false);
351
+ return;
352
+ }
353
+
354
+ const requestVersion = requestVersionRef.current + 1;
355
+ requestVersionRef.current = requestVersion;
356
+ setIsLoading(true);
357
+
358
+ Promise.resolve(getOptions(normalizedQuery))
359
+ .then((nextOptions) => {
360
+ if (requestVersionRef.current !== requestVersion) {
361
+ return;
362
+ }
363
+ setOptions(nextOptions);
364
+ setActiveOptionIndex(nextOptions.length > 0 ? 0 : -1);
365
+ })
366
+ .catch(() => {
367
+ if (requestVersionRef.current !== requestVersion) {
368
+ return;
369
+ }
370
+ setOptions([]);
371
+ setActiveOptionIndex(-1);
372
+ })
373
+ .finally(() => {
374
+ if (requestVersionRef.current === requestVersion) {
375
+ setIsLoading(false);
376
+ }
377
+ });
378
+ }, [getOptions, minQueryLength, onQueryChange, query]);
379
+
380
+ useEffect(() => {
381
+ if (activeOptionIndex < 0 || activeOptionIndex >= options.length) {
382
+ onActiveOptionChange?.(null);
383
+ return;
384
+ }
385
+ onActiveOptionChange?.(options[activeOptionIndex] ?? null);
386
+ }, [activeOptionIndex, onActiveOptionChange, options]);
387
+
388
+ useEffect(() => {
389
+ optionElementRefs.current = optionElementRefs.current.slice(0, options.length);
390
+ }, [options.length]);
391
+
392
+ useEffect(() => {
393
+ if (!isFocused || activeOptionIndex < 0 || activeOptionIndex >= options.length) {
394
+ return;
395
+ }
396
+
397
+ const dropdownElement = dropdownRef.current;
398
+ const optionElement = optionElementRefs.current[activeOptionIndex];
399
+ if (!dropdownElement || !optionElement) {
400
+ return;
401
+ }
402
+
403
+ const optionTop = optionElement.offsetTop;
404
+ const optionBottom = optionTop + optionElement.offsetHeight;
405
+ const viewportTop = dropdownElement.scrollTop;
406
+ const viewportBottom = viewportTop + dropdownElement.clientHeight;
407
+
408
+ if (optionTop < viewportTop) {
409
+ dropdownElement.scrollTop = optionTop;
410
+ return;
411
+ }
412
+
413
+ if (optionBottom > viewportBottom) {
414
+ dropdownElement.scrollTop = optionBottom - dropdownElement.clientHeight;
415
+ }
416
+ }, [activeOptionIndex, isFocused, options.length]);
417
+
418
+ const selectOption = useCallback(
419
+ (option: OmniBoxOption) => {
420
+ setQuery(option.value ?? option.label);
421
+ setIsFocused(false);
422
+ setOptions([]);
423
+ setActiveOptionIndex(-1);
424
+ onSelectOption?.(option);
425
+ },
426
+ [onSelectOption]
427
+ );
428
+
429
+ const moveActiveOptionBy = useCallback(
430
+ (delta: -1 | 1, {navigate = false}: {navigate?: boolean} = {}) => {
431
+ if (!options.length) {
432
+ return;
433
+ }
434
+ const currentIndex = activeOptionIndex >= 0 ? activeOptionIndex : 0;
435
+ const nextIndex = (currentIndex + delta + options.length) % options.length;
436
+ const nextOption = options[nextIndex];
437
+ if (!nextOption) {
438
+ return;
439
+ }
440
+ setActiveOptionIndex(nextIndex);
441
+ setIsFocused(true);
442
+ if (navigate) {
443
+ onNavigateOption?.(nextOption);
444
+ }
445
+ },
446
+ [activeOptionIndex, onNavigateOption, options]
447
+ );
448
+
449
+ const handleHide = useCallback(
450
+ (event?: Event) => {
451
+ if (event) {
452
+ stopEventPropagation(event);
453
+ }
454
+
455
+ clearBlurTimeout();
456
+ requestVersionRef.current += 1;
457
+ setQuery('');
458
+ setOptions([]);
459
+ setActiveOptionIndex(-1);
460
+ setIsLoading(false);
461
+ setIsFocused(false);
462
+ setIsHidden(true);
463
+ },
464
+ [clearBlurTimeout]
465
+ );
466
+
467
+ const handleInput: JSX.GenericEventHandler<HTMLInputElement> = useCallback((event) => {
468
+ stopEventPropagation(event as unknown as Event);
469
+ setQuery(event.currentTarget.value);
470
+ setIsFocused(true);
471
+ }, []);
472
+
473
+ const handleFocus: JSX.FocusEventHandler<HTMLInputElement> = useCallback(() => {
474
+ clearBlurTimeout();
475
+ setIsFocused(true);
476
+ }, [clearBlurTimeout]);
477
+
478
+ const handleBlur: JSX.FocusEventHandler<HTMLInputElement> = useCallback(() => {
479
+ clearBlurTimeout();
480
+ blurTimeoutRef.current = window.setTimeout(() => {
481
+ setIsFocused(false);
482
+ setActiveOptionIndex(-1);
483
+ }, BLUR_CLOSE_DELAY_MS);
484
+ }, [clearBlurTimeout]);
485
+
486
+ const handleKeyDown: JSX.KeyboardEventHandler<HTMLInputElement> = useCallback(
487
+ (event) => {
488
+ stopEventPropagation(event as unknown as Event);
489
+
490
+ if (event.key === 'ArrowDown') {
491
+ event.preventDefault();
492
+ moveActiveOptionBy(1);
493
+ return;
494
+ }
495
+
496
+ if (event.key === 'ArrowUp') {
497
+ event.preventDefault();
498
+ moveActiveOptionBy(-1);
499
+ return;
500
+ }
501
+
502
+ if (event.key === 'Enter') {
503
+ if (activeOptionIndex >= 0 && activeOptionIndex < options.length) {
504
+ event.preventDefault();
505
+ const option = options[activeOptionIndex];
506
+ if (option) {
507
+ selectOption(option);
508
+ }
509
+ }
510
+ return;
511
+ }
512
+
513
+ if (event.key === 'Escape') {
514
+ event.preventDefault();
515
+ handleHide(event as unknown as Event);
516
+ }
517
+ },
518
+ [activeOptionIndex, handleHide, moveActiveOptionBy, options, selectOption]
519
+ );
520
+
521
+ const handlePointerEvent: JSX.PointerEventHandler<HTMLElement> = useCallback((event) => {
522
+ stopEventPropagation(event as unknown as Event);
523
+ }, []);
524
+
525
+ const handleMouseEvent: JSX.MouseEventHandler<HTMLElement> = useCallback((event) => {
526
+ stopEventPropagation(event as unknown as Event);
527
+ }, []);
528
+
529
+ const handleWheelEvent: JSX.WheelEventHandler<HTMLElement> = useCallback((event) => {
530
+ stopEventPropagation(event as unknown as Event);
531
+ }, []);
532
+
533
+ const hasMatches = options.length > 0;
534
+ const normalizedQuery = query.trim();
535
+ const shouldShowDropdown =
536
+ !isHidden &&
537
+ isFocused &&
538
+ normalizedQuery.length >= minQueryLength &&
539
+ (isLoading || options.length > 0);
540
+
541
+ if (isHidden) {
542
+ return null;
543
+ }
544
+
545
+ return (
546
+ <div
547
+ style={WRAPPER_STYLE}
548
+ onPointerDown={handlePointerEvent}
549
+ onPointerMove={handlePointerEvent}
550
+ onPointerUp={handlePointerEvent}
551
+ onMouseDown={handleMouseEvent}
552
+ onMouseMove={handleMouseEvent}
553
+ onMouseUp={handleMouseEvent}
554
+ onWheel={handleWheelEvent}
555
+ >
556
+ <OmniBoxWidgetStyles />
557
+ <div data-omni-box-controls="true" style={INPUT_ROW_STYLE}>
558
+ <input
559
+ className={OMNIBOX_INPUT_CLASS}
560
+ ref={inputRef}
561
+ type="text"
562
+ value={query}
563
+ placeholder={placeholder}
564
+ style={INPUT_STYLE}
565
+ onInput={handleInput}
566
+ onFocus={handleFocus}
567
+ onBlur={handleBlur}
568
+ onKeyDown={handleKeyDown}
569
+ aria-label="OmniBox"
570
+ />
571
+
572
+ <button
573
+ className={OMNIBOX_BUTTON_CLASS}
574
+ type="button"
575
+ title="Previous match"
576
+ aria-label="Previous match"
577
+ disabled={!hasMatches}
578
+ style={{
579
+ ...NAV_BUTTON_STYLE,
580
+ ...(hasMatches ? {} : NAV_BUTTON_DISABLED_STYLE)
581
+ }}
582
+ onMouseDown={(event) => {
583
+ event.preventDefault();
584
+ stopEventPropagation(event as unknown as Event);
585
+ }}
586
+ onClick={(event) => {
587
+ stopEventPropagation(event as unknown as Event);
588
+ moveActiveOptionBy(-1, {navigate: true});
589
+ }}
590
+ >
591
+ {'<'}
592
+ </button>
593
+
594
+ <button
595
+ className={OMNIBOX_BUTTON_CLASS}
596
+ type="button"
597
+ title="Next match"
598
+ aria-label="Next match"
599
+ disabled={!hasMatches}
600
+ style={{
601
+ ...NAV_BUTTON_STYLE,
602
+ ...(hasMatches ? {} : NAV_BUTTON_DISABLED_STYLE)
603
+ }}
604
+ onMouseDown={(event) => {
605
+ event.preventDefault();
606
+ stopEventPropagation(event as unknown as Event);
607
+ }}
608
+ onClick={(event) => {
609
+ stopEventPropagation(event as unknown as Event);
610
+ moveActiveOptionBy(1, {navigate: true});
611
+ }}
612
+ >
613
+ {'>'}
614
+ </button>
615
+
616
+ <button
617
+ className={OMNIBOX_BUTTON_CLASS}
618
+ type="button"
619
+ title="Hide OmniBox"
620
+ aria-label="Hide OmniBox"
621
+ style={{
622
+ ...NAV_BUTTON_STYLE,
623
+ ...LAST_NAV_BUTTON_STYLE
624
+ }}
625
+ onMouseDown={(event) => {
626
+ event.preventDefault();
627
+ stopEventPropagation(event as unknown as Event);
628
+ }}
629
+ onClick={(event) => {
630
+ handleHide(event as unknown as Event);
631
+ }}
632
+ >
633
+ ×
634
+ </button>
635
+ </div>
636
+
637
+ {shouldShowDropdown && (
638
+ <div
639
+ ref={dropdownRef}
640
+ role="listbox"
641
+ data-omni-box-dropdown="true"
642
+ style={DROPDOWN_STYLE}
643
+ aria-label="OmniBox suggestions"
644
+ >
645
+ {isLoading && (
646
+ <div
647
+ style={{
648
+ height: `${OPTION_ROW_HEIGHT_PX}px`,
649
+ display: 'flex',
650
+ alignItems: 'center',
651
+ padding: '0 12px',
652
+ fontSize: '12px',
653
+ color: 'var(--menu-text, rgb(24, 24, 26))',
654
+ opacity: 0.7,
655
+ fontFamily: WIDGET_FONT_FAMILY
656
+ }}
657
+ >
658
+ Searching…
659
+ </div>
660
+ )}
661
+
662
+ {!isLoading &&
663
+ options.map((option, index) => {
664
+ const isActive = index === activeOptionIndex;
665
+ const content = renderOption?.({
666
+ option,
667
+ index,
668
+ isActive,
669
+ query
670
+ }) ?? <DefaultOptionContent option={option} />;
671
+
672
+ return (
673
+ <button
674
+ key={option.id}
675
+ className={[OMNIBOX_OPTION_CLASS, isActive ? OMNIBOX_OPTION_ACTIVE_CLASS : '']
676
+ .filter(Boolean)
677
+ .join(' ')}
678
+ ref={(element) => {
679
+ optionElementRefs.current[index] = element;
680
+ }}
681
+ type="button"
682
+ role="option"
683
+ aria-selected={isActive}
684
+ onMouseDown={(event) => {
685
+ event.preventDefault();
686
+ stopEventPropagation(event as unknown as Event);
687
+ }}
688
+ onClick={(event) => {
689
+ stopEventPropagation(event as unknown as Event);
690
+ selectOption(option);
691
+ }}
692
+ style={{
693
+ width: '100%',
694
+ border: 0,
695
+ height: `${OPTION_ROW_HEIGHT_PX}px`,
696
+ display: 'flex',
697
+ alignItems: 'stretch',
698
+ textAlign: 'left',
699
+ padding: '0 12px',
700
+ cursor: 'pointer',
701
+ backgroundColor: isActive
702
+ ? 'var(--menu-item-hover, rgba(0, 0, 0, 0.08))'
703
+ : 'transparent',
704
+ color: 'var(--menu-text, rgb(24, 24, 26))',
705
+ fontFamily: WIDGET_FONT_FAMILY
706
+ }}
707
+ >
708
+ {content}
709
+ </button>
710
+ );
711
+ })}
712
+ </div>
713
+ )}
714
+ </div>
715
+ );
716
+ }
717
+
718
+ export class OmniBoxWidget extends Widget<OmniBoxWidgetProps> {
719
+ static override defaultProps = {
720
+ ...Widget.defaultProps,
721
+ id: 'omni-box',
722
+ placement: 'top-left',
723
+ placeholder: 'Search trace blocks…',
724
+ minQueryLength: 1,
725
+ defaultOpen: false,
726
+ topOffsetPx: undefined,
727
+ getOptions: (() => []) as OmniBoxOptionProvider,
728
+ renderOption: undefined,
729
+ onSelectOption: undefined,
730
+ onActiveOptionChange: undefined,
731
+ onNavigateOption: undefined,
732
+ onQueryChange: undefined
733
+ } satisfies Required<WidgetProps> &
734
+ Required<Pick<OmniBoxWidgetProps, 'placeholder' | 'minQueryLength' | 'placement'>> &
735
+ OmniBoxWidgetProps;
736
+
737
+ placement: WidgetPlacement = OmniBoxWidget.defaultProps.placement;
738
+ className = 'deck-widget-omni-box';
739
+
740
+ #rootElement: HTMLElement | null = null;
741
+ #hasLayoutListeners = false;
742
+
743
+ #handleWindowLayoutChange = () => {
744
+ this.#updateRootLayout();
745
+ };
746
+
747
+ constructor(props: OmniBoxWidgetProps = {}) {
748
+ super({...OmniBoxWidget.defaultProps, ...props});
749
+ if (props.placement !== undefined) {
750
+ this.placement = props.placement;
751
+ }
752
+ }
753
+
754
+ override setProps(props: Partial<OmniBoxWidgetProps>): void {
755
+ if (props.placement !== undefined) {
756
+ this.placement = props.placement;
757
+ }
758
+ super.setProps(props);
759
+ }
760
+
761
+ override onRenderHTML(rootElement: HTMLElement): void {
762
+ this.#rootElement = rootElement;
763
+
764
+ rootElement.className = ['deck-widget', this.className, this.props.className]
765
+ .filter(Boolean)
766
+ .join(' ');
767
+
768
+ Object.assign(rootElement.style, ROOT_STYLE);
769
+ this.#attachLayoutListeners();
770
+ this.#updateRootLayout();
771
+
772
+ render(
773
+ <OmniBoxWidgetView
774
+ placeholder={this.props.placeholder ?? OmniBoxWidget.defaultProps.placeholder}
775
+ minQueryLength={this.props.minQueryLength ?? OmniBoxWidget.defaultProps.minQueryLength}
776
+ defaultOpen={this.props.defaultOpen ?? OmniBoxWidget.defaultProps.defaultOpen}
777
+ getOptions={this.props.getOptions ?? OmniBoxWidget.defaultProps.getOptions}
778
+ renderOption={this.props.renderOption}
779
+ onSelectOption={this.props.onSelectOption}
780
+ onActiveOptionChange={this.props.onActiveOptionChange}
781
+ onNavigateOption={this.props.onNavigateOption}
782
+ onQueryChange={this.props.onQueryChange}
783
+ />,
784
+ rootElement
785
+ );
786
+ }
787
+
788
+ override onViewportChange(_viewport: Viewport): void {
789
+ this.#updateRootLayout();
790
+ }
791
+
792
+ override onRemove(): void {
793
+ this.#detachLayoutListeners();
794
+ if (this.#rootElement) {
795
+ render(null, this.#rootElement);
796
+ }
797
+ }
798
+
799
+ #attachLayoutListeners(): void {
800
+ if (this.#hasLayoutListeners || typeof window === 'undefined') {
801
+ return;
802
+ }
803
+
804
+ window.addEventListener('resize', this.#handleWindowLayoutChange);
805
+ window.addEventListener('scroll', this.#handleWindowLayoutChange, true);
806
+ this.#hasLayoutListeners = true;
807
+ }
808
+
809
+ #detachLayoutListeners(): void {
810
+ if (!this.#hasLayoutListeners || typeof window === 'undefined') {
811
+ return;
812
+ }
813
+
814
+ window.removeEventListener('resize', this.#handleWindowLayoutChange);
815
+ window.removeEventListener('scroll', this.#handleWindowLayoutChange, true);
816
+ this.#hasLayoutListeners = false;
817
+ }
818
+
819
+ #updateRootLayout(): void {
820
+ if (!this.#rootElement || typeof window === 'undefined') {
821
+ return;
822
+ }
823
+
824
+ const fallbackTopOffsetPx = getWidgetMarginPx(this.#rootElement);
825
+ const configuredTopOffsetPx = this.props.topOffsetPx;
826
+ const topOffsetPx =
827
+ configuredTopOffsetPx !== undefined && Number.isFinite(configuredTopOffsetPx)
828
+ ? configuredTopOffsetPx
829
+ : fallbackTopOffsetPx;
830
+ const canvasRect = getDeckCanvasRect(this.deck);
831
+
832
+ if (canvasRect) {
833
+ const availableWidthPx = Math.max(0, canvasRect.width - OMNIBOX_HORIZONTAL_MARGIN_PX * 2);
834
+ const resolvedWidthPx =
835
+ availableWidthPx > 0
836
+ ? Math.min(OMNIBOX_MAX_WIDTH_PX, availableWidthPx)
837
+ : OMNIBOX_MAX_WIDTH_PX;
838
+
839
+ this.#rootElement.style.left = `${canvasRect.left + canvasRect.width / 2}px`;
840
+ this.#rootElement.style.top = `${canvasRect.top + topOffsetPx}px`;
841
+ this.#rootElement.style.width = `${resolvedWidthPx}px`;
842
+ return;
843
+ }
844
+
845
+ this.#rootElement.style.left = '50%';
846
+ this.#rootElement.style.top = `${topOffsetPx}px`;
847
+ this.#rootElement.style.width = `min(${OMNIBOX_MAX_WIDTH_PX}px, calc(100vw - ${OMNIBOX_HORIZONTAL_MARGIN_PX * 2}px))`;
848
+ }
849
+ }