@digithings-ai/ui 0.0.0-stage → 0.1.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 (424) hide show
  1. package/README.md +48 -2
  2. package/package.json +143 -4
  3. package/src/components/NavShell.tsx +776 -0
  4. package/src/components/SocialRow.tsx +87 -0
  5. package/src/components/Spinner.tsx +27 -0
  6. package/src/components/StackLogo.tsx +40 -0
  7. package/src/components/Terminal.tsx +110 -0
  8. package/src/components/TerminalManifest.tsx +212 -0
  9. package/src/components/ThemeProvider.tsx +271 -0
  10. package/src/components/account/AuthCard.tsx +309 -0
  11. package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
  12. package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
  13. package/src/components/brand/BrandWordmark.tsx +32 -0
  14. package/src/components/changelog/ReleaseRail.tsx +81 -0
  15. package/src/components/changelog/index.ts +1 -0
  16. package/src/components/chat/ChatCodeBlock.tsx +67 -0
  17. package/src/components/chat/ChatMarkdown.tsx +66 -0
  18. package/src/components/chat/ChatMarkdownSource.tsx +203 -0
  19. package/src/components/chat/ChatMermaidBlock.tsx +202 -0
  20. package/src/components/chat/ChatMessage.tsx +77 -0
  21. package/src/components/chat/ChatPlayback.tsx +422 -0
  22. package/src/components/chat/ChatStreamCursor.tsx +25 -0
  23. package/src/components/chat/ChatThinking.tsx +113 -0
  24. package/src/components/chat/ChatToolCall.tsx +190 -0
  25. package/src/components/chat/ChatTranscript.tsx +60 -0
  26. package/src/components/chat/ChatWidgetFrame.tsx +107 -0
  27. package/src/components/chat/DigichatBootLoader.tsx +683 -0
  28. package/src/components/chat/DigichatLauncher.tsx +361 -0
  29. package/src/components/chat/DigichatThread.tsx +118 -0
  30. package/src/components/chat/DigichatThreadList.tsx +54 -0
  31. package/src/components/chat/DigichatWordmark.tsx +24 -0
  32. package/src/components/chat/DotMatrix.tsx +650 -0
  33. package/src/components/chat/boot-lab.tsx +766 -0
  34. package/src/components/chat/boot-signal.ts +144 -0
  35. package/src/components/chat/chat-surfaces.ts +52 -0
  36. package/src/components/chat/connection-status.ts +40 -0
  37. package/src/components/chat/digichat-wordmark.ts +355 -0
  38. package/src/components/chat/disclosure-chevron.tsx +49 -0
  39. package/src/components/chat/followable-href.ts +9 -0
  40. package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
  41. package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
  42. package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
  43. package/src/components/chat/gallery-thread/cn.ts +1 -0
  44. package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
  45. package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
  46. package/src/components/chat/gallery-thread/css.d.ts +6 -0
  47. package/src/components/chat/gallery-thread/file.tsx +238 -0
  48. package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
  49. package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
  50. package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
  51. package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
  52. package/src/components/chat/gallery-thread/image.tsx +519 -0
  53. package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
  54. package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
  55. package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
  56. package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
  57. package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
  58. package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
  59. package/src/components/chat/gallery-thread/source.tsx +76 -0
  60. package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
  61. package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
  62. package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
  63. package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
  64. package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
  65. package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
  66. package/src/components/chat/gallery-thread/work-status.ts +66 -0
  67. package/src/components/chat/overlay-scrollbar.tsx +86 -0
  68. package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
  69. package/src/components/chat/skins/SOURCE.md +36 -0
  70. package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
  71. package/src/components/chat/skins/base/aui-default/README.md +45 -0
  72. package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
  73. package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
  74. package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
  75. package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
  76. package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
  77. package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
  78. package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
  79. package/src/components/chat/skins/base/docs-model-options.ts +6 -0
  80. package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
  81. package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
  82. package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
  83. package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
  84. package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
  85. package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
  86. package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
  87. package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
  88. package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
  89. package/src/components/chat/skins/base/markdown-text.tsx +294 -0
  90. package/src/components/chat/skins/base/model.ts +1 -0
  91. package/src/components/chat/skins/base/thread.tsx +1050 -0
  92. package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
  93. package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
  94. package/src/components/chat/skins/base/ui/badge.tsx +51 -0
  95. package/src/components/chat/skins/base/ui/button.tsx +67 -0
  96. package/src/components/chat/skins/base/ui/command.tsx +185 -0
  97. package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
  98. package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
  99. package/src/components/chat/skins/base/ui/popover.tsx +97 -0
  100. package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
  101. package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
  102. package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
  103. package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
  104. package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
  105. package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
  106. package/src/components/chat/skins/chatgpt.tsx +448 -0
  107. package/src/components/chat/skins/claude.tsx +380 -0
  108. package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
  109. package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
  110. package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
  111. package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
  112. package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
  113. package/src/components/chat/skins/digichat.tsx +144 -0
  114. package/src/components/chat/skins/expo-react-native.tsx +29 -0
  115. package/src/components/chat/skins/gemini.tsx +322 -0
  116. package/src/components/chat/skins/grok-icon.tsx +34 -0
  117. package/src/components/chat/skins/grok.tsx +369 -0
  118. package/src/components/chat/skins/index.ts +46 -0
  119. package/src/components/chat/skins/perplexity.tsx +462 -0
  120. package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
  121. package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
  122. package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
  123. package/src/components/chat/skins/react-ink.tsx +97 -0
  124. package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
  125. package/src/components/chat/skins/thread-skin-view.tsx +156 -0
  126. package/src/components/chat/skins/thread-skins.ts +143 -0
  127. package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
  128. package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
  129. package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
  130. package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
  131. package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
  132. package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
  133. package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
  134. package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
  135. package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
  136. package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
  137. package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
  138. package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
  139. package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
  140. package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
  141. package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
  142. package/src/components/chat/stock/SOURCE.md +90 -0
  143. package/src/components/chat/stock/artifacts-panel.tsx +305 -0
  144. package/src/components/chat/stock/attachment.aui.tsx +280 -0
  145. package/src/components/chat/stock/chat-notice.tsx +143 -0
  146. package/src/components/chat/stock/chat-private-mode.tsx +64 -0
  147. package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
  148. package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
  149. package/src/components/chat/stock/file.tsx +265 -0
  150. package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
  151. package/src/components/chat/stock/image.tsx +531 -0
  152. package/src/components/chat/stock/index.ts +129 -0
  153. package/src/components/chat/stock/json-view.tsx +140 -0
  154. package/src/components/chat/stock/markdown-text.tsx +319 -0
  155. package/src/components/chat/stock/message-error.aui.tsx +96 -0
  156. package/src/components/chat/stock/message-export-menu.tsx +156 -0
  157. package/src/components/chat/stock/palette-row-preview.tsx +26 -0
  158. package/src/components/chat/stock/reasoning.aui.tsx +120 -0
  159. package/src/components/chat/stock/reasoning.tsx +335 -0
  160. package/src/components/chat/stock/skin-chrome.tsx +81 -0
  161. package/src/components/chat/stock/skin-credit.tsx +63 -0
  162. package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
  163. package/src/components/chat/stock/skin-palette-context.ts +16 -0
  164. package/src/components/chat/stock/skin-runtime.tsx +52 -0
  165. package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
  166. package/src/components/chat/stock/skin-types.ts +217 -0
  167. package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
  168. package/src/components/chat/stock/stock-send-gate.tsx +94 -0
  169. package/src/components/chat/stock/stock-shape.tsx +39 -0
  170. package/src/components/chat/stock/thread.aui.tsx +645 -0
  171. package/src/components/chat/stock/tool-display.ts +6 -0
  172. package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
  173. package/src/components/chat/stock/tool-group.aui.tsx +244 -0
  174. package/src/components/chat/stock/tool-surface.ts +120 -0
  175. package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
  176. package/src/components/chat/stock/ui/avatar.tsx +108 -0
  177. package/src/components/chat/stock/ui/button.tsx +81 -0
  178. package/src/components/chat/stock/ui/collapsible.tsx +21 -0
  179. package/src/components/chat/stock/ui/dialog.tsx +163 -0
  180. package/src/components/chat/stock/ui/skeleton.tsx +13 -0
  181. package/src/components/chat/stock/ui/textarea.tsx +20 -0
  182. package/src/components/chat/stock/ui/tooltip.tsx +68 -0
  183. package/src/components/chat/stock/use-attachment-src.ts +42 -0
  184. package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
  185. package/src/components/chat/strip-model-boilerplate.ts +42 -0
  186. package/src/components/chat/suggestion-cycle.tsx +230 -0
  187. package/src/components/chat/suggestion-scroll-row.tsx +81 -0
  188. package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
  189. package/src/components/chat/trace-grouping/chain-state.ts +141 -0
  190. package/src/components/chat/trace-grouping/index.ts +32 -0
  191. package/src/components/chat/trace-grouping/tool-row.ts +74 -0
  192. package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
  193. package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
  194. package/src/components/chat/trace-i18n/core.ts +48 -0
  195. package/src/components/chat/trace-i18n/index.tsx +23 -0
  196. package/src/components/chat/trace-i18n/locales/en.ts +24 -0
  197. package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
  198. package/src/components/chat/trace-i18n/types.ts +35 -0
  199. package/src/components/chat/trace-spacing.ts +249 -0
  200. package/src/components/chat/transcript/activity-view.ts +375 -0
  201. package/src/components/chat/transcript/chat-export.ts +133 -0
  202. package/src/components/chat/transcript/index.ts +10 -0
  203. package/src/components/chat/transcript/save-file.ts +209 -0
  204. package/src/components/chat/transcript/transcript-markdown.ts +428 -0
  205. package/src/components/chat/transcript/types.ts +81 -0
  206. package/src/components/chat/turn-outcome.ts +52 -0
  207. package/src/components/chat/use-warmup-phase.ts +18 -0
  208. package/src/components/chat/work-status-lead.tsx +49 -0
  209. package/src/components/chrome.tsx +264 -0
  210. package/src/components/command-palette/CommandPalette.tsx +404 -0
  211. package/src/components/command-palette/index.ts +6 -0
  212. package/src/components/contact/ContactMailto.tsx +112 -0
  213. package/src/components/contact/index.ts +5 -0
  214. package/src/components/conviction/ConvictionMeter.tsx +40 -0
  215. package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
  216. package/src/components/conviction/index.ts +7 -0
  217. package/src/components/data-layout/BentoGrid.tsx +52 -0
  218. package/src/components/data-layout/CardRail.tsx +150 -0
  219. package/src/components/data-layout/DotMatrixStat.tsx +65 -0
  220. package/src/components/data-layout/FeatureCell.tsx +80 -0
  221. package/src/components/data-layout/MediaFrame.tsx +125 -0
  222. package/src/components/data-layout/ModuleGrid.tsx +735 -0
  223. package/src/components/data-layout/Odometer.tsx +149 -0
  224. package/src/components/data-layout/ProductFrame.tsx +79 -0
  225. package/src/components/data-layout/TestimonialWall.tsx +139 -0
  226. package/src/components/data-layout/index.ts +19 -0
  227. package/src/components/data-layout/treemap.ts +368 -0
  228. package/src/components/deck/DeckStack.tsx +243 -0
  229. package/src/components/desk/DataTable.tsx +373 -0
  230. package/src/components/desk/KpiGrid.tsx +80 -0
  231. package/src/components/desk/Sparkline.tsx +63 -0
  232. package/src/components/desk/StateBlock.tsx +132 -0
  233. package/src/components/desk/index.ts +18 -0
  234. package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
  235. package/src/components/diagrams/ArchitectureTour.tsx +912 -0
  236. package/src/components/diagrams/TerminalSchematic.tsx +192 -0
  237. package/src/components/diagrams/architecture-svg.tsx +384 -0
  238. package/src/components/diagrams/index.ts +30 -0
  239. package/src/components/diagrams/mermaid-theme.ts +115 -0
  240. package/src/components/diagrams/tour-camera.ts +171 -0
  241. package/src/components/docs/CodeTabs.tsx +90 -0
  242. package/src/components/docs/CopyCommand.tsx +151 -0
  243. package/src/components/docs/DocsLayout.tsx +197 -0
  244. package/src/components/docs/DocsSearch.tsx +97 -0
  245. package/src/components/docs/Endpoint.tsx +135 -0
  246. package/src/components/document/DocumentFrame.tsx +43 -0
  247. package/src/components/document/GlyphList.tsx +51 -0
  248. package/src/components/document/PageTitle.tsx +44 -0
  249. package/src/components/document/Prose.tsx +38 -0
  250. package/src/components/document/Section.tsx +55 -0
  251. package/src/components/document/index.ts +13 -0
  252. package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
  253. package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
  254. package/src/components/effects-chrome/Pipeline.tsx +226 -0
  255. package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
  256. package/src/components/effects-chrome/StackingPanels.tsx +101 -0
  257. package/src/components/effects-chrome/TabStrip.tsx +232 -0
  258. package/src/components/effects-chrome/ToastStack.tsx +201 -0
  259. package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
  260. package/src/components/effects-chrome/index.ts +35 -0
  261. package/src/components/emblems.tsx +188 -0
  262. package/src/components/faviconTheme.ts +23 -0
  263. package/src/components/figure/Figure.tsx +38 -0
  264. package/src/components/figure/index.ts +2 -0
  265. package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
  266. package/src/components/finance-charts/EquityCurve.tsx +71 -0
  267. package/src/components/finance-charts/PriceChart.tsx +119 -0
  268. package/src/components/finance-charts/chart-host.tsx +236 -0
  269. package/src/components/finance-charts/chart-scale.ts +93 -0
  270. package/src/components/finance-charts/demo-data.ts +88 -0
  271. package/src/components/finance-charts/index.ts +46 -0
  272. package/src/components/finance-charts/lw-chart.tsx +292 -0
  273. package/src/components/finance-composites/MarketBar.tsx +243 -0
  274. package/src/components/finance-composites/OrderBook.tsx +133 -0
  275. package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
  276. package/src/components/finance-composites/SortableTable.tsx +144 -0
  277. package/src/components/finance-composites/StockTicker.tsx +65 -0
  278. package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
  279. package/src/components/finance-composites/index.ts +28 -0
  280. package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
  281. package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
  282. package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
  283. package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
  284. package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
  285. package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
  286. package/src/components/finance-tearsheet/charts.tsx +2624 -0
  287. package/src/components/finance-tearsheet/demo-data.ts +177 -0
  288. package/src/components/finance-tearsheet/format.ts +41 -0
  289. package/src/components/finance-tearsheet/index.ts +120 -0
  290. package/src/components/finance-tearsheet/print.ts +52 -0
  291. package/src/components/finance-tearsheet/risk-bands.ts +48 -0
  292. package/src/components/finance-tearsheet/stats.ts +35 -0
  293. package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
  294. package/src/components/finance-tearsheet/types.ts +58 -0
  295. package/src/components/footer/FooterCells.tsx +109 -0
  296. package/src/components/footer/index.ts +6 -0
  297. package/src/components/graph.tsx +158 -0
  298. package/src/components/icons.tsx +98 -0
  299. package/src/components/landing/PixelWordmark.tsx +36 -0
  300. package/src/components/logos.ts +39 -0
  301. package/src/components/marquee/Marquee.tsx +204 -0
  302. package/src/components/metrics/PerfMetrics.tsx +68 -0
  303. package/src/components/metrics/StatCounter.tsx +104 -0
  304. package/src/components/page-geometry.tsx +177 -0
  305. package/src/components/pricing/Pricing.tsx +174 -0
  306. package/src/components/pricing/PricingMatrix.tsx +219 -0
  307. package/src/components/prose/index.ts +2 -0
  308. package/src/components/prose/prose.tsx +86 -0
  309. package/src/components/repo-activity/RepoActivity.tsx +599 -0
  310. package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
  311. package/src/components/repo-activity/demo.ts +201 -0
  312. package/src/components/repo-activity/fetch.ts +211 -0
  313. package/src/components/repo-activity/heatmap.ts +123 -0
  314. package/src/components/repo-activity/index.ts +28 -0
  315. package/src/components/repo-activity/types.ts +99 -0
  316. package/src/components/stages/NumberedStages.tsx +116 -0
  317. package/src/components/symbols/marks.tsx +116 -0
  318. package/src/components/symbols/terminal-lockup.tsx +136 -0
  319. package/src/components/symbols/terminal-marks.tsx +226 -0
  320. package/src/components/terminal/ChatResponseLoader.tsx +67 -0
  321. package/src/components/terminal/ContainerBootLoader.tsx +82 -0
  322. package/src/components/terminal/TerminalStepCaret.tsx +202 -0
  323. package/src/components/themeInit.ts +24 -0
  324. package/src/components/typography/MotionReveal.tsx +108 -0
  325. package/src/components/typography/TypewriterReveal.tsx +101 -0
  326. package/src/components/typography/WordReveal.tsx +143 -0
  327. package/src/data/modules.ts +378 -0
  328. package/src/data/subsystems.ts +101 -0
  329. package/src/index.ts +587 -0
  330. package/src/lib/base-path.ts +60 -0
  331. package/src/lib/cx.ts +25 -0
  332. package/src/lib/gloomberb.ts +69 -0
  333. package/src/lib/record.ts +19 -0
  334. package/src/lib/stub-payload.ts +183 -0
  335. package/src/lib/useBodyScrollLock.ts +44 -0
  336. package/src/lib/utils.ts +6 -0
  337. package/src/motion/primitives.tsx +105 -0
  338. package/src/motion/scroll-glide.ts +62 -0
  339. package/src/motion/scrolly-core.ts +38 -0
  340. package/src/motion/scrolly.tsx +95 -0
  341. package/src/navigation/HashScrollManager.tsx +167 -0
  342. package/src/navigation/hashScroll.ts +78 -0
  343. package/src/replay/browser.ts +280 -0
  344. package/src/replay/chat-export-run.ts +121 -0
  345. package/src/replay/export-format.ts +25 -0
  346. package/src/replay/export-mount.ts +32 -0
  347. package/src/replay/filename.ts +54 -0
  348. package/src/replay/index.ts +21 -0
  349. package/src/replay/redact-dom.ts +178 -0
  350. package/src/replay/redact.ts +113 -0
  351. package/src/replay/snapshot.ts +659 -0
  352. package/src/styles/account-auth.css +339 -0
  353. package/src/styles/brand-wordmark.css +11 -0
  354. package/src/styles/card-rail.css +120 -0
  355. package/src/styles/chat-aui.css +1143 -0
  356. package/src/styles/chat-core.css +469 -0
  357. package/src/styles/chat-digichat.css +2531 -0
  358. package/src/styles/chat-math.css +70 -0
  359. package/src/styles/chat-skin-palette.css +138 -0
  360. package/src/styles/chat-stock-menu.css +37 -0
  361. package/src/styles/chat-widgets.css +190 -0
  362. package/src/styles/command-palette.css +372 -0
  363. package/src/styles/controls-core.css +2261 -0
  364. package/src/styles/controls-overlay.css +603 -0
  365. package/src/styles/data-layout.css +288 -0
  366. package/src/styles/deck.css +141 -0
  367. package/src/styles/diagrams.css +1860 -0
  368. package/src/styles/digichat-app-theme.css +250 -0
  369. package/src/styles/digichat-boot-loader.css +487 -0
  370. package/src/styles/digichat-launcher.css +377 -0
  371. package/src/styles/docs.css +165 -0
  372. package/src/styles/effects-chrome.css +888 -0
  373. package/src/styles/finance-composites.css +214 -0
  374. package/src/styles/finance-tearsheet-overlay.css +90 -0
  375. package/src/styles/finance-tearsheet.css +945 -0
  376. package/src/styles/marquee.css +168 -0
  377. package/src/styles/metrics.css +82 -0
  378. package/src/styles/nav-shell.css +551 -0
  379. package/src/styles/pricing.css +79 -0
  380. package/src/styles/repo-activity.css +575 -0
  381. package/src/styles/stages.css +69 -0
  382. package/src/styles/terminal-loaders.css +122 -0
  383. package/src/styles/terminal-manifest.css +228 -0
  384. package/src/styles/web-theme.css +1007 -0
  385. package/src/styles/word-reveal.css +204 -0
  386. package/src/theme/apply-theme.ts +214 -0
  387. package/src/theme/index.ts +27 -0
  388. package/src/theme/palette-registry.ts +855 -0
  389. package/src/theme/tui-prefs.ts +64 -0
  390. package/src/ui/alert.tsx +78 -0
  391. package/src/ui/avatar.tsx +104 -0
  392. package/src/ui/badge.tsx +75 -0
  393. package/src/ui/breadcrumb.tsx +61 -0
  394. package/src/ui/button.tsx +67 -0
  395. package/src/ui/card.tsx +139 -0
  396. package/src/ui/checkbox.tsx +27 -0
  397. package/src/ui/collapsible.tsx +32 -0
  398. package/src/ui/date-pager.tsx +325 -0
  399. package/src/ui/dialog.tsx +157 -0
  400. package/src/ui/dropdown-menu.tsx +267 -0
  401. package/src/ui/empty-state.tsx +165 -0
  402. package/src/ui/field.tsx +104 -0
  403. package/src/ui/form.tsx +36 -0
  404. package/src/ui/icon-button.tsx +36 -0
  405. package/src/ui/index.ts +34 -0
  406. package/src/ui/input.tsx +33 -0
  407. package/src/ui/kbd.tsx +30 -0
  408. package/src/ui/label.tsx +32 -0
  409. package/src/ui/pager.tsx +119 -0
  410. package/src/ui/pagination.tsx +126 -0
  411. package/src/ui/radio-group.tsx +51 -0
  412. package/src/ui/search-bar.tsx +85 -0
  413. package/src/ui/segmented-control.tsx +76 -0
  414. package/src/ui/select.tsx +257 -0
  415. package/src/ui/separator.tsx +24 -0
  416. package/src/ui/sheet.tsx +141 -0
  417. package/src/ui/skeleton.tsx +76 -0
  418. package/src/ui/slider.tsx +76 -0
  419. package/src/ui/switch.tsx +31 -0
  420. package/src/ui/table.tsx +179 -0
  421. package/src/ui/tabs.tsx +89 -0
  422. package/src/ui/tags-input.tsx +168 -0
  423. package/src/ui/textarea.tsx +17 -0
  424. package/src/ui/tooltip.tsx +71 -0
@@ -0,0 +1,439 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, type CSSProperties, type RefObject } from "react";
4
+
5
+ /**
6
+ * Block caret for the gallery-thread composer.
7
+ *
8
+ * `caret-shape: block` is Chromium-only, so on engines without it the native
9
+ * caret is made transparent (see the `@supports not (caret-shape: block)`
10
+ * rules in the reference chatbot.css) and this painted block tracks it. The
11
+ * digichat embed likewise promotes the painted block ONLY on engines without a
12
+ * native block caret (inside the same `@supports not (caret-shape: block)`
13
+ * guard in product-chrome.css), so the tracking must be runtime-robust.
14
+ *
15
+ * Positioning is measured in pixels from the live textarea: the current
16
+ * logical line is sliced from the value + selection offset, its width is
17
+ * measured with a canvas 2d context using the textarea's own computed font,
18
+ * and the block is placed at padding + measured width (minus scroll). The old
19
+ * `col * 1ch` math drifted because the overlay's font-size (0.88rem) never
20
+ * matched the input's (`text-sm`/`text-base`), so the error grew per
21
+ * keystroke — behind on some skins, ahead on others. Soft-wrapped visual
22
+ * lines are followed with a hidden mirror div (see mirrorCaretPx); the
23
+ * logical-line canvas math is only the no-layout fallback.
24
+ *
25
+ * The textarea can mount after this overlay (the composer renders it from its
26
+ * own lifecycle), so listeners live on the document and the textarea is
27
+ * re-resolved per event; the initial paint retries briefly until it exists.
28
+ * Syncs run on the next animation frame so React-controlled value /
29
+ * selection updates have landed before we measure.
30
+ */
31
+
32
+ export function caretRowCol(text: string, offset: number): { row: number; col: number } {
33
+ const safe = Math.max(0, Math.min(Math.trunc(offset), text.length));
34
+ const before = text.slice(0, safe);
35
+ const row = (before.match(/\n/g) ?? []).length;
36
+ return { row, col: safe - before.lastIndexOf("\n") - 1 };
37
+ }
38
+
39
+ const CARET_EVENTS = [
40
+ "select",
41
+ "input",
42
+ "beforeinput",
43
+ "click",
44
+ "keydown",
45
+ "keyup",
46
+ "compositionupdate",
47
+ "focusin",
48
+ "scroll",
49
+ ] as const;
50
+ const SYNC_RETRY_MS = 50;
51
+ const SYNC_RETRY_LIMIT = 40;
52
+
53
+ let measureCanvas: HTMLCanvasElement | null = null;
54
+
55
+ function measureTextWidth(text: string, font: string): number | null {
56
+ if (!text) return 0;
57
+ try {
58
+ if (!measureCanvas && typeof document !== "undefined") {
59
+ measureCanvas = document.createElement("canvas");
60
+ }
61
+ const ctx = measureCanvas?.getContext("2d");
62
+ if (!ctx) return null;
63
+ ctx.font = font;
64
+ return ctx.measureText(text).width;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * The ink height of a capital/digit: how far the glyphs rise above the
72
+ * baseline. Measured from the font, not guessed, because the caret is placed
73
+ * against it.
74
+ */
75
+ function fontInkAscent(font: string, fontSize: number): number {
76
+ const fallback = fontSize * 0.72;
77
+ try {
78
+ if (!measureCanvas && typeof document !== "undefined") {
79
+ measureCanvas = document.createElement("canvas");
80
+ }
81
+ const ctx = measureCanvas?.getContext("2d");
82
+ if (!ctx) return fallback;
83
+ ctx.font = font;
84
+ const digit = ctx.measureText("0").actualBoundingBoxAscent;
85
+ const cap = ctx.measureText("H").actualBoundingBoxAscent;
86
+ const ink = Math.max(
87
+ Number.isFinite(digit) ? digit : 0,
88
+ Number.isFinite(cap) ? cap : 0,
89
+ );
90
+ return ink > 0 ? ink : fallback;
91
+ } catch {
92
+ return fallback;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * The font's own vertical metrics: the ascent and descent the browser uses to
98
+ * place the baseline inside a line box. Measured, not guessed — the block
99
+ * caret has to sit on the same baseline the text does, and a hardcoded
100
+ * ascent ratio lands off the glyphs on other families.
101
+ */
102
+ function fontVerticalMetrics(
103
+ font: string,
104
+ fontSize: number,
105
+ ): { ascent: number; descent: number } {
106
+ const fallback = { ascent: fontSize * 0.8, descent: fontSize * 0.2 };
107
+ try {
108
+ if (!measureCanvas && typeof document !== "undefined") {
109
+ measureCanvas = document.createElement("canvas");
110
+ }
111
+ const ctx = measureCanvas?.getContext("2d");
112
+ if (!ctx) return fallback;
113
+ ctx.font = font;
114
+ const m = ctx.measureText("Hxg0");
115
+ const ascent = m.fontBoundingBoxAscent;
116
+ const descent = m.fontBoundingBoxDescent;
117
+ return {
118
+ ascent: Number.isFinite(ascent) && ascent > 0 ? ascent : fallback.ascent,
119
+ descent: Number.isFinite(descent) && descent >= 0 ? descent : fallback.descent,
120
+ };
121
+ } catch {
122
+ return fallback;
123
+ }
124
+ }
125
+
126
+ function px(value: string | null | undefined, fallback: number): number {
127
+ if (!value) return fallback;
128
+ const n = Number.parseFloat(value);
129
+ return Number.isFinite(n) ? n : fallback;
130
+ }
131
+
132
+ /**
133
+ * Canvas 2d font string for the textarea. Built from the individual longhands
134
+ * — NEVER from the `font` shorthand: computed `font` includes `/line-height`
135
+ * (`400 14px/22px …`), which `ctx.font` rejects, silently leaving the canvas
136
+ * at default 10px sans-serif. Every glyph then measures wrong and the error
137
+ * compounds per character (block drifts further the longer the line).
138
+ */
139
+ export function canvasFont(cs: CSSStyleDeclaration | null, fontSize: number): string {
140
+ const style = cs?.fontStyle && cs.fontStyle !== "" ? cs.fontStyle : "normal";
141
+ const variant = cs?.fontVariant && cs.fontVariant !== "" ? cs.fontVariant : "normal";
142
+ const weight = cs?.fontWeight && cs.fontWeight !== "" ? cs.fontWeight : "400";
143
+ const family = cs?.fontFamily && cs.fontFamily !== "" ? cs.fontFamily : "monospace";
144
+ return `${style} ${variant} ${weight} ${fontSize}px ${family}`;
145
+ }
146
+
147
+ /** Expand tabs the way a textarea lays them out (advance to next tab stop). */
148
+ export function expandTabs(line: string, tabSize: number): string {
149
+ if (!line.includes("\t")) return line;
150
+ const stops = Math.max(1, Math.trunc(tabSize) || 8);
151
+ let out = "";
152
+ let col = 0;
153
+ for (const ch of line) {
154
+ if (ch === "\t") {
155
+ const next = col + (stops - (col % stops));
156
+ out += " ".repeat(next - col);
157
+ col = next;
158
+ } else {
159
+ out += ch;
160
+ col += 1;
161
+ }
162
+ }
163
+ return out;
164
+ }
165
+
166
+ const MIRROR_PROPS = [
167
+ "boxSizing",
168
+ "width",
169
+ "borderTopWidth",
170
+ "borderRightWidth",
171
+ "borderBottomWidth",
172
+ "borderLeftWidth",
173
+ "paddingTop",
174
+ "paddingRight",
175
+ "paddingBottom",
176
+ "paddingLeft",
177
+ "fontStyle",
178
+ "fontVariant",
179
+ "fontWeight",
180
+ "fontStretch",
181
+ "fontSize",
182
+ "fontFamily",
183
+ "lineHeight",
184
+ "letterSpacing",
185
+ "wordSpacing",
186
+ "textTransform",
187
+ "textIndent",
188
+ "tabSize",
189
+ ] as const;
190
+
191
+ let mirrorDiv: HTMLDivElement | null = null;
192
+
193
+ /**
194
+ * Wrapped caret position via a hidden mirror of the textarea (same width,
195
+ * padding and font, `pre-wrap`), so soft-wrapped lines are followed: the old
196
+ * logical-line math kept growing `left` past the right edge on a long single
197
+ * line instead of dropping to the next visual row. Returns null when the
198
+ * textarea has no layout (jsdom, display:none) so the canvas path still runs.
199
+ *
200
+ * The returned baseline is the text's own baseline, read with a zero-height
201
+ * inline-block probe (its bottom edge sits on the baseline). Measuring it
202
+ * beats modelling it: the browser's line layout and the canvas metrics
203
+ * disagree by a pixel or two, and a caret that misses the baseline by that
204
+ * much reads as "sitting below the letters".
205
+ */
206
+ function mirrorCaretPx(
207
+ area: HTMLTextAreaElement,
208
+ cs: CSSStyleDeclaration | null,
209
+ ): { left: number; baseline: number; lineHeight: number } | null {
210
+ if (!cs || typeof document === "undefined" || !area.clientWidth) return null;
211
+ try {
212
+ if (!mirrorDiv) {
213
+ mirrorDiv = document.createElement("div");
214
+ mirrorDiv.setAttribute("aria-hidden", "true");
215
+ document.body.appendChild(mirrorDiv);
216
+ }
217
+ const style = mirrorDiv.style;
218
+ for (const prop of MIRROR_PROPS) {
219
+ (style as unknown as Record<string, string>)[prop] = cs[prop] as string;
220
+ }
221
+ style.position = "absolute";
222
+ style.visibility = "hidden";
223
+ style.top = "0";
224
+ style.left = "-9999px";
225
+ style.whiteSpace = "pre-wrap";
226
+ style.overflowWrap = "break-word";
227
+ style.wordBreak = cs.wordBreak || "normal";
228
+ style.overflow = "hidden";
229
+ style.height = "auto";
230
+ // The textarea's width includes a vertical scrollbar once it scrolls.
231
+ style.width = `${area.offsetWidth}px`;
232
+ const offset = area.selectionStart ?? area.value.length;
233
+ mirrorDiv.textContent = area.value.slice(0, offset);
234
+ const marker = document.createElement("span");
235
+ // A zero-width glyph keeps the marker on the line the next char would use.
236
+ marker.textContent = area.value.slice(offset) ? area.value.slice(offset, offset + 1) : "\u200b";
237
+ mirrorDiv.appendChild(marker);
238
+ // Zero-height inline-block: its bottom margin edge sits on the baseline.
239
+ const probe = document.createElement("span");
240
+ probe.style.display = "inline-block";
241
+ probe.style.width = "0";
242
+ probe.style.height = "0";
243
+ mirrorDiv.appendChild(probe);
244
+ const fontSize = px(cs.fontSize, 14);
245
+ const lh = cs.lineHeight === "normal" || !cs.lineHeight ? fontSize * 1.5 : px(cs.lineHeight, fontSize * 1.5);
246
+ return {
247
+ left: marker.offsetLeft,
248
+ baseline: probe.offsetTop,
249
+ lineHeight: lh,
250
+ };
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+
256
+ /** Pixel position of the caret relative to the container. Falls back to null when unmeasurable. */
257
+ export function caretPx(
258
+ area: HTMLTextAreaElement,
259
+ container: HTMLElement | null,
260
+ ): { left: number; top: number; height: number; width: number } | null {
261
+ const { row, col } = caretRowCol(area.value, area.selectionStart ?? area.value.length);
262
+ const lines = area.value.split("\n");
263
+ const lineText = (lines[row] ?? "").slice(0, col);
264
+
265
+ let cs: CSSStyleDeclaration | null = null;
266
+ try {
267
+ cs = window.getComputedStyle(area);
268
+ } catch {
269
+ cs = null;
270
+ }
271
+ const fontSize = px(cs?.fontSize, 14);
272
+ const rawLineHeight = cs?.lineHeight ?? "";
273
+ const lineHeight = rawLineHeight === "normal" || !rawLineHeight ? fontSize * 1.5 : px(rawLineHeight, fontSize * 1.5);
274
+ const font = canvasFont(cs, fontSize);
275
+
276
+ const tabSizeRaw = cs?.tabSize ?? "";
277
+ const tabSize = tabSizeRaw.endsWith("px") ? px(tabSizeRaw, 8) / Math.max(1, fontSize * 0.6) : px(tabSizeRaw, 8);
278
+ const lineExpanded = expandTabs(lineText, tabSize);
279
+ const measured = measureTextWidth(lineExpanded, font);
280
+ const chWidth = measureTextWidth("0", font) ?? fontSize * 0.6;
281
+ // Canvas measureText ignores CSS letter/word-spacing — add them per unit
282
+ // or the block drifts a fraction of a pixel per character/space.
283
+ const letterSpacing = px(cs?.letterSpacing, 0);
284
+ const wordSpacing = px(cs?.wordSpacing, 0);
285
+ const spaces = (lineExpanded.match(/ /g) ?? []).length;
286
+ const textWidth =
287
+ (measured ?? [...lineExpanded].length * chWidth) + [...lineExpanded].length * letterSpacing + spaces * wordSpacing;
288
+
289
+ const paddingLeft = px(cs?.paddingLeft, 0);
290
+ const paddingTop = px(cs?.paddingTop, 0);
291
+ const borderLeft = px(cs?.borderLeftWidth, 0);
292
+ const borderTop = px(cs?.borderTopWidth, 0);
293
+
294
+ // Overlay is absolutely positioned in the relatively-positioned wrapper;
295
+ // offsetLeft/Top pin the textarea's border box inside that wrapper.
296
+ const baseLeft = (area.offsetLeft || 0) + borderLeft + paddingLeft;
297
+ const baseTop = (area.offsetTop || 0) + borderTop + paddingTop;
298
+ void container;
299
+
300
+ // Native-cursor geometry: the caret is as tall as the font's ascent +
301
+ // descent (what a real text cursor spans), and it is centred on the
302
+ // letters' ink box — half way between standing on the baseline and
303
+ // aligning with the top of the glyphs, so it neither hangs below the text
304
+ // nor floats above it.
305
+ const { ascent, descent } = fontVerticalMetrics(font, fontSize);
306
+ const contentHeight = ascent + descent;
307
+ const caretHeight = Math.max(8, Math.round(contentHeight));
308
+ const inkAscent = fontInkAscent(font, fontSize);
309
+ // Distance from the baseline down to the caret's top edge.
310
+ const caretDrop = inkAscent / 2 + caretHeight / 2;
311
+
312
+ const mirrored = mirrorCaretPx(area, cs);
313
+ if (mirrored) {
314
+ return {
315
+ left: (area.offsetLeft || 0) + borderLeft + mirrored.left - (area.scrollLeft || 0),
316
+ // The mirror's baseline is measured from the textarea's padding edge, so
317
+ // the block hangs from it: bottom edge on the baseline, height upward.
318
+ top:
319
+ (area.offsetTop || 0) +
320
+ borderTop +
321
+ mirrored.baseline -
322
+ caretDrop -
323
+ (area.scrollTop || 0),
324
+ height: caretHeight,
325
+ width: Math.max(1, chWidth),
326
+ };
327
+ }
328
+
329
+ // No layout to measure (jsdom, display:none): fall back to the font model.
330
+ const halfLeading = (lineHeight - contentHeight) / 2;
331
+ const baseline = baseTop + row * lineHeight + halfLeading + ascent;
332
+ return {
333
+ left: baseLeft + textWidth - (area.scrollLeft || 0),
334
+ top: baseline - caretDrop - (area.scrollTop || 0),
335
+ height: caretHeight,
336
+ width: Math.max(1, chWidth),
337
+ };
338
+ }
339
+
340
+ export function ComposerBlockCaret({
341
+ containerRef,
342
+ }: {
343
+ /** Ref of the wrapper holding the composer textarea. */
344
+ containerRef: RefObject<HTMLDivElement | null>;
345
+ }) {
346
+ const [pos, setPos] = useState({ row: 0, col: 0, left: 0, top: 0, height: 22, width: 8 });
347
+
348
+ useEffect(() => {
349
+ let retries = 0;
350
+ let timer: ReturnType<typeof setTimeout> | null = null;
351
+ let raf = 0;
352
+ let queued = false;
353
+
354
+ const textarea = () => containerRef.current?.querySelector("textarea") ?? null;
355
+
356
+ const sync = () => {
357
+ const area = textarea();
358
+ if (!area) return false;
359
+ const { row, col } = caretRowCol(area.value, area.selectionStart ?? area.value.length);
360
+ const measured = caretPx(area, containerRef.current);
361
+ setPos(
362
+ measured
363
+ ? { row, col, left: measured.left, top: measured.top, height: measured.height, width: measured.width }
364
+ : { row, col, left: col * 8, top: row * 22, height: 22, width: 8 },
365
+ );
366
+ return true;
367
+ };
368
+
369
+ const schedule = () => {
370
+ if (queued) return;
371
+ queued = true;
372
+ const run = () => {
373
+ queued = false;
374
+ sync();
375
+ };
376
+ if (typeof requestAnimationFrame === "function") {
377
+ raf = requestAnimationFrame(run);
378
+ } else {
379
+ run();
380
+ }
381
+ };
382
+
383
+ const onEvent = (event: Event) => {
384
+ const area = textarea();
385
+ if (!area) return;
386
+ if (event.target === area) {
387
+ // Immediate sync keeps unit-test determinism (assert right after
388
+ // dispatch); the rAF pass then re-measures after React-controlled
389
+ // value/selection updates land, which is the live-typing fix.
390
+ sync();
391
+ schedule();
392
+ } else if (event.type === "scroll") {
393
+ schedule();
394
+ }
395
+ };
396
+
397
+ if (!sync()) {
398
+ const retry = () => {
399
+ if (sync() || retries >= SYNC_RETRY_LIMIT) return;
400
+ retries += 1;
401
+ timer = setTimeout(retry, SYNC_RETRY_MS);
402
+ };
403
+ timer = setTimeout(retry, SYNC_RETRY_MS);
404
+ }
405
+
406
+ for (const name of CARET_EVENTS) {
407
+ document.addEventListener(name, onEvent, true);
408
+ }
409
+ window.addEventListener("resize", schedule);
410
+
411
+ return () => {
412
+ if (timer) clearTimeout(timer);
413
+ if (raf) cancelAnimationFrame(raf);
414
+ queued = false;
415
+ for (const name of CARET_EVENTS) {
416
+ document.removeEventListener(name, onEvent, true);
417
+ }
418
+ window.removeEventListener("resize", schedule);
419
+ };
420
+ }, [containerRef]);
421
+
422
+ return (
423
+ <span
424
+ aria-hidden="true"
425
+ data-slot="aui_block-caret"
426
+ className="aui-block-caret"
427
+ style={
428
+ {
429
+ "--caret-col": pos.col,
430
+ "--caret-row": pos.row,
431
+ left: `${pos.left}px`,
432
+ top: `${pos.top}px`,
433
+ height: `${pos.height}px`,
434
+ width: `${pos.width}px`,
435
+ } as CSSProperties
436
+ }
437
+ />
438
+ );
439
+ }
@@ -0,0 +1 @@
1
+ export { cn } from "../../../lib/utils";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Composer keyboard dismissal for phones.
3
+ *
4
+ * On a phone the software keyboard covers most of the viewport, so sending a
5
+ * message leaves the visitor looking at the first lines of the answer with the
6
+ * keyboard still up. Dropping focus from the textarea retracts the keyboard and
7
+ * gives the streaming answer the whole screen.
8
+ *
9
+ * Scoped to a coarse pointer on purpose: on a desktop the caret belongs in the
10
+ * composer for the follow-up turn, and losing focus there would be a
11
+ * regression.
12
+ */
13
+
14
+ type MatchMedia = ((query: string) => MediaQueryList) | undefined;
15
+
16
+ function defaultMatchMedia(): MatchMedia {
17
+ return typeof window === "undefined" ? undefined : window.matchMedia;
18
+ }
19
+
20
+ /** True on a phone or tablet, where the pointer is a finger. */
21
+ export function isCoarsePointer(matchMedia: MatchMedia = defaultMatchMedia()): boolean {
22
+ if (typeof matchMedia !== "function") return false;
23
+ try {
24
+ return matchMedia("(pointer: coarse)").matches;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * True when the composer holds something the user meant to send.
32
+ *
33
+ * Read *before* the submit is handed to the runtime: a successful send clears
34
+ * the textarea, so asking afterwards would answer "empty" for every real send.
35
+ * Enter on an empty composer also submits through the same form, and that one
36
+ * must not steal focus.
37
+ */
38
+ export function composerHasText(root: HTMLElement | null): boolean {
39
+ if (!root) return false;
40
+ const area = root.querySelector<HTMLTextAreaElement>("textarea");
41
+ return area ? area.value.trim().length > 0 : false;
42
+ }
43
+
44
+ /**
45
+ * Blur the composer's textarea so the on-screen keyboard retracts.
46
+ *
47
+ * Returns true when the textarea had focus and was blurred, so a caller (or a
48
+ * test) can tell a dismissal from a no-op. Never throws: a missing textarea on
49
+ * a skin that does not use one is a valid state.
50
+ */
51
+ export function dismissComposerKeyboard(
52
+ root: HTMLElement | null,
53
+ options: { coarse?: boolean } = {},
54
+ ): boolean {
55
+ const coarse = options.coarse ?? isCoarsePointer();
56
+ if (!coarse || !root) return false;
57
+ const area = root.querySelector<HTMLTextAreaElement>("textarea");
58
+ if (!area) return false;
59
+ const wasFocused = area.ownerDocument.activeElement === area;
60
+ area.blur();
61
+ return wasFocused;
62
+ }
63
+
64
+ const TEXT_INPUT_TYPES = new Set([
65
+ "text",
66
+ "search",
67
+ "email",
68
+ "url",
69
+ "tel",
70
+ "password",
71
+ "number",
72
+ ]);
73
+
74
+ /** True when focusing this element is what opens the software keyboard. */
75
+ function isTextEntry(element: Element | null): boolean {
76
+ if (!element) return false;
77
+ if (element.tagName === "TEXTAREA") return true;
78
+ if (element.tagName === "INPUT") {
79
+ const type = (element as HTMLInputElement).type || "text";
80
+ return TEXT_INPUT_TYPES.has(type);
81
+ }
82
+ return (element as HTMLElement).isContentEditable === true;
83
+ }
84
+
85
+ /**
86
+ * Blur whatever text field currently holds focus, so the keyboard retracts.
87
+ *
88
+ * The composer is not the only way to send: an example question, a redo, a
89
+ * retry and an edit all start a turn without going through the composer's
90
+ * submit, and each one leaves the keyboard up over the answer. This is the
91
+ * entry-point-shaped dismissal those paths call, and it deliberately works
92
+ * from the document's active element rather than from a composer ref.
93
+ *
94
+ * Only a text field is blurred. A focused button (an example question the
95
+ * visitor tapped) is left alone: it does not open a keyboard, and blurring it
96
+ * would drop the focus ring for a switch or keyboard user on a hybrid device.
97
+ */
98
+ export function dismissKeyboardIfCoarse(options: { coarse?: boolean } = {}): boolean {
99
+ if (typeof document === "undefined") return false;
100
+ const coarse = options.coarse ?? isCoarsePointer();
101
+ if (!coarse) return false;
102
+ const active = document.activeElement;
103
+ if (!isTextEntry(active)) return false;
104
+ (active as HTMLElement).blur();
105
+ return true;
106
+ }