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