@adecore/agents-react 0.39.0 → 0.40.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 (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +26 -26
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -22,6 +22,7 @@ const ChecklistContext = createContext<ChecklistState>({ values: [], enabled: fa
22
22
  /* The rows of a checklist as one named group. */
23
23
  function ChecklistGroup({ pending, children }: { pending: boolean; children: ReactNode }) {
24
24
  const { t } = useTranslation('agent-chat');
25
+
25
26
  return (
26
27
  <div role="group" aria-label={t('blocks.checklist')} className={clsx('chat-ui-nodes flex flex-col gap-1.5', pending && PENDING)}>
27
28
  {children}
@@ -32,9 +33,11 @@ function ChecklistGroup({ pending, children }: { pending: boolean; children: Rea
32
33
  export function ChecklistRenderer({ node, children, context }: UiRendererProps<UiProps<'Checklist'>>) {
33
34
  const binding = bindingOf(node, context);
34
35
  const values = binding?.value ?? node.props.value;
36
+
35
37
  const toggle = (value: Scalar): void => {
36
38
  binding?.onValueChange(values.includes(value) ? values.filter((entry) => entry !== value) : [...values, value]);
37
39
  };
40
+
38
41
  return (
39
42
  <ChecklistContext value={{ values, enabled: binding !== null, toggle }}>
40
43
  <ChecklistGroup pending={!node.complete}>{children}</ChecklistGroup>
@@ -47,11 +50,13 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
47
50
  const list = useContext(ChecklistContext);
48
51
  const { value } = node.props;
49
52
  const checked = list.values.includes(value);
53
+
50
54
  const toggleRow = (event: MouseEvent<HTMLDivElement>): void => {
51
55
  if (list.enabled && !(event.target as Element).closest('button, a')) {
52
56
  list.toggle(value);
53
57
  }
54
58
  };
59
+
55
60
  return (
56
61
  <div
57
62
  className={clsx(
@@ -69,6 +74,7 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
69
74
 
70
75
  export function SwitchRenderer({ node, children, context }: UiRendererProps<UiProps<'Switch'>>) {
71
76
  const binding = bindingOf(node, context);
77
+
72
78
  return (
73
79
  <div
74
80
  className={clsx('chat-ui-flat flex min-h-11 items-center justify-between gap-3 rounded-xl bg-ui-tile px-3 py-2 text-xs', !node.complete && PENDING)}
@@ -88,6 +94,7 @@ export function SwitchRenderer({ node, children, context }: UiRendererProps<UiPr
88
94
  export function SliderRenderer({ node, context }: UiRendererProps<UiProps<'Slider'>>) {
89
95
  const binding = bindingOf(node, context);
90
96
  const { value, min, max, step, unit } = node.props;
97
+
91
98
  return (
92
99
  <Slider
93
100
  value={binding?.value ?? value}
@@ -114,6 +121,7 @@ export function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Se
114
121
  const current = binding?.value ?? node.props.value;
115
122
  const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
116
123
  const picked = options.find((option) => option.value === current)?.id ?? '';
124
+
117
125
  return (
118
126
  <div className={clsx(!node.complete && PENDING)}>
119
127
  <Segmented
@@ -124,6 +132,7 @@ export function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Se
124
132
  disabled={binding === null}
125
133
  onValueChange={(id) => {
126
134
  const option = options[Number(id)];
135
+
127
136
  if (option !== undefined) {
128
137
  binding?.onValueChange(option.value);
129
138
  }
@@ -151,6 +160,7 @@ export function TextFieldRenderer({ node, children, context }: UiRendererProps<U
151
160
  const value = binding?.value ?? node.props.value;
152
161
  const countId = `${id}-count`;
153
162
  const counting = value.length >= maxLength * COUNT_FROM;
163
+
154
164
  return (
155
165
  <div className={clsx('flex flex-col gap-1.5', !node.complete && PENDING)}>
156
166
  <label htmlFor={id} className="px-0.5 text-2xs text-text-muted">
@@ -186,6 +196,7 @@ export function SelectRenderer({ node, context }: UiRendererProps<UiProps<'Selec
186
196
  const current = binding?.value ?? node.props.value;
187
197
  const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
188
198
  const label = uiOwnLabel(node);
199
+
189
200
  return (
190
201
  <div className={clsx('flex flex-col items-start gap-1.5', !node.complete && PENDING)}>
191
202
  {label !== '' && <span className="px-0.5 text-2xs text-text-muted">{label}</span>}
@@ -196,6 +207,7 @@ export function SelectRenderer({ node, context }: UiRendererProps<UiProps<'Selec
196
207
  disabled={binding === null || options.length === 0}
197
208
  onValueChange={(id) => {
198
209
  const option = options[Number(id)];
210
+
199
211
  if (option !== undefined) {
200
212
  binding?.onValueChange(option.value);
201
213
  }
@@ -210,6 +222,7 @@ export function ButtonRenderer({ node, children, context }: UiRendererProps<UiPr
210
222
  const enabled = node.onAction !== undefined && node.complete && context.answer === null && node.props.disabled !== true;
211
223
  const label = uiNodeLabel(node);
212
224
  const [measure, truncated] = useTruncated<HTMLSpanElement>();
225
+
213
226
  return (
214
227
  <div className={clsx(!node.complete && PENDING)}>
215
228
  <Tooltip label={label} disabled={!truncated}>
@@ -15,6 +15,7 @@ const UI_CHIP = 'bg-ui-inset font-mono text-text shadow-[inset_0_0_0_1px_var(--b
15
15
 
16
16
  function baseName(path: string): string {
17
17
  const trimmed = path.replace(/\/+$/, '');
18
+
18
19
  return trimmed.slice(trimmed.lastIndexOf('/') + 1) || path;
19
20
  }
20
21
 
@@ -85,18 +86,23 @@ function chipFace(target: UiLinkTarget, link: UiLink): { face: ReactNode; hint:
85
86
  export function UiLinkChip({ target, context }: { target: UiLinkTarget; context: UiRenderContext }) {
86
87
  const { t } = useTranslation('agent-chat');
87
88
  const link = context.link?.(target) ?? PLAIN;
89
+
88
90
  if (link.state === 'plain') {
89
91
  const reason = uiReasonText(t, link.code, link.reason) ?? (context.link === undefined ? null : t('blocks.outsideProject'));
92
+
90
93
  // Focusable only with a reason, so a keyboard reaches the tooltip.
91
94
  const text = (
92
95
  <span tabIndex={reason === null ? undefined : 0} className="font-mono text-code break-all text-text-muted">
93
96
  {plainText(target, link)}
94
97
  </span>
95
98
  );
99
+
96
100
  return reason === null ? text : <Tooltip label={reason}>{text}</Tooltip>;
97
101
  }
102
+
98
103
  const { face, hint } = chipFace(target, link);
99
104
  const { openLink } = context;
105
+
100
106
  const chip =
101
107
  openLink === undefined ? (
102
108
  <span className={clsx(CHIP_IN_MESSAGE, UI_CHIP, 'transition-opacity duration-120 starting:opacity-0')}>{face}</span>
@@ -109,6 +115,7 @@ export function UiLinkChip({ target, context }: { target: UiLinkTarget; context:
109
115
  {face}
110
116
  </button>
111
117
  );
118
+
112
119
  return hint === null ? chip : <Tooltip label={hint}>{chip}</Tooltip>;
113
120
  }
114
121
 
@@ -117,6 +124,7 @@ function LinkRow({ target, context, described, children }: { target: UiLinkTarge
117
124
  if (!described) {
118
125
  return <UiLinkChip target={target} context={context} />;
119
126
  }
127
+
120
128
  return (
121
129
  <div className="flex min-w-0 flex-wrap items-baseline gap-x-2 text-xs">
122
130
  <span className="max-w-full min-w-0">
@@ -129,6 +137,7 @@ function LinkRow({ target, context, described, children }: { target: UiLinkTarge
129
137
 
130
138
  export function FileRenderer({ node, children, context }: UiRendererProps<UiProps<'File'>>) {
131
139
  const { path, line } = node.props;
140
+
132
141
  return (
133
142
  <LinkRow target={{ type: 'File', path, line }} context={context} described={node.children.length > 0}>
134
143
  {children}
@@ -28,6 +28,7 @@ export class MapSlots {
28
28
  if (this.held.includes(id) || this.queue.includes(id)) {
29
29
  return;
30
30
  }
31
+
31
32
  (this.held.length < this.capacity ? this.held : this.queue).push(id);
32
33
  this.changed();
33
34
  }
@@ -35,14 +36,18 @@ export class MapSlots {
35
36
  release(id: string): void {
36
37
  const wasHeld = this.held.includes(id);
37
38
  const wasQueued = this.queue.includes(id);
39
+
38
40
  if (!wasHeld && !wasQueued) {
39
41
  return;
40
42
  }
43
+
41
44
  this.held.splice(0, this.held.length, ...this.held.filter((entry) => entry !== id));
42
45
  this.queue.splice(0, this.queue.length, ...this.queue.filter((entry) => entry !== id));
46
+
43
47
  while (this.held.length < this.capacity && this.queue.length > 0) {
44
48
  this.held.push(this.queue.shift()!);
45
49
  }
50
+
46
51
  this.changed();
47
52
  }
48
53
 
@@ -50,13 +55,17 @@ export class MapSlots {
50
55
  if (this.held.includes(id)) {
51
56
  return;
52
57
  }
58
+
53
59
  const at = this.queue.indexOf(id);
60
+
54
61
  if (at >= 0) {
55
62
  this.queue.splice(at, 1);
56
63
  }
64
+
57
65
  if (this.held.length >= this.capacity) {
58
66
  this.queue.unshift(this.held.shift()!);
59
67
  }
68
+
60
69
  this.held.push(id);
61
70
  this.changed();
62
71
  }
@@ -67,6 +76,7 @@ export class MapSlots {
67
76
 
68
77
  subscribe = (listener: () => void): (() => void) => {
69
78
  this.listeners.add(listener);
79
+
70
80
  return () => {
71
81
  this.listeners.delete(listener);
72
82
  };
@@ -84,6 +94,7 @@ let page: MapSlots | null = null;
84
94
  /* The one budget of the page, made on first use. */
85
95
  export function pageMapSlots(): MapSlots {
86
96
  page ??= new MapSlots(MAX_LIVE_MAPS);
97
+
87
98
  return page;
88
99
  }
89
100
 
@@ -94,12 +105,16 @@ export function useMapSlot(id: string, wanted: boolean, slots: MapSlots = pageMa
94
105
  () => slots.stateOf(id),
95
106
  () => 'idle' as const
96
107
  );
108
+
97
109
  useEffect(() => {
98
110
  if (!wanted) {
99
111
  return;
100
112
  }
113
+
101
114
  slots.request(id);
115
+
102
116
  return () => slots.release(id);
103
117
  }, [slots, id, wanted]);
118
+
104
119
  return { state, take: () => slots.take(id) };
105
120
  }
@@ -67,13 +67,16 @@ const SPREAD_STEPS: readonly (readonly [number, number])[] = [
67
67
  export function spreadOverlaps(points: readonly ScreenPoint[]): Map<string, [number, number]> {
68
68
  const offsets = new Map<string, [number, number]>();
69
69
  const placed: { x: number; y: number }[] = [];
70
+
70
71
  for (const point of points) {
71
72
  const free = ([dx, dy]: readonly [number, number]): boolean =>
72
73
  placed.every((other) => Math.hypot(other.x - (point.x + dx), other.y - (point.y + dy)) >= MAP_MARKER_SIZE);
74
+
73
75
  const step = [[0, 0] as const, ...SPREAD_STEPS].find(free) ?? ([0, 0] as const);
74
76
  offsets.set(point.id, [step[0], step[1]]);
75
77
  placed.push({ x: point.x + step[0], y: point.y + step[1] });
76
78
  }
79
+
77
80
  return offsets;
78
81
  }
79
82
 
@@ -95,19 +98,24 @@ const TOKEN = /\{([^{}]+)\}/g;
95
98
  function tokenExpression(text: string): Expression | string {
96
99
  const parts: (Expression | string)[] = [];
97
100
  let at = 0;
101
+
98
102
  for (const match of text.matchAll(TOKEN)) {
99
103
  if (match.index > at) {
100
104
  parts.push(text.slice(at, match.index));
101
105
  }
106
+
102
107
  parts.push(['to-string', ['coalesce', ['get', match[1]], '']]);
103
108
  at = match.index + match[0].length;
104
109
  }
110
+
105
111
  if (parts.length === 0) {
106
112
  return text;
107
113
  }
114
+
108
115
  if (at < text.length) {
109
116
  parts.push(text.slice(at));
110
117
  }
118
+
111
119
  return parts.length === 1 && typeof parts[0] !== 'string' ? parts[0] : ['concat', ...parts];
112
120
  }
113
121
 
@@ -117,9 +125,11 @@ function tokenExpression(text: string): Expression | string {
117
125
  */
118
126
  export function localizedTextField(original: unknown, field: string): unknown {
119
127
  const fallback = typeof original === 'string' ? tokenExpression(original) : original;
128
+
120
129
  if (typeof fallback !== 'string' && !Array.isArray(fallback)) {
121
130
  return original;
122
131
  }
132
+
123
133
  return ['coalesce', ['get', field], fallback];
124
134
  }
125
135
 
@@ -132,19 +142,24 @@ export function boundsOf(points: readonly MapPoint[]): LngLatBoundsLike | null {
132
142
  if (points.length === 0) {
133
143
  return null;
134
144
  }
145
+
135
146
  const lons = points.map((point) => point.lon).sort((a, b) => a - b);
136
147
  let widest = lons[0]! + 360 - lons[lons.length - 1]!;
137
148
  let west = lons[0]!;
138
149
  let east = lons[lons.length - 1]!;
150
+
139
151
  for (let i = 1; i < lons.length; i++) {
140
152
  const gap = lons[i]! - lons[i - 1]!;
153
+
141
154
  if (gap > widest) {
142
155
  widest = gap;
143
156
  west = lons[i]!;
144
157
  east = lons[i - 1]! + 360;
145
158
  }
146
159
  }
160
+
147
161
  const lats = points.map((point) => point.lat);
162
+
148
163
  return [
149
164
  [west, Math.min(...lats)],
150
165
  [east, Math.max(...lats)]
@@ -44,6 +44,7 @@ let workerUrlSet: string | null = null;
44
44
  */
45
45
  function ensureBundledWorker(hosted: string | undefined): void {
46
46
  const url = hosted ?? new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url).href;
47
+
47
48
  if (url !== workerUrlSet) {
48
49
  setWorkerUrl(url);
49
50
  workerUrlSet = url;
@@ -53,6 +54,7 @@ function ensureBundledWorker(hosted: string | undefined): void {
53
54
  /* The grounds of the built-in map are theme tokens, which the page resolves; the style needs plain colors. */
54
55
  function readColors(element: HTMLElement): MapColors {
55
56
  const style = getComputedStyle(element);
57
+
56
58
  return { land: style.getPropertyValue('--map-land').trim() || 'transparent', water: style.getPropertyValue('--map-water').trim() || 'transparent' };
57
59
  }
58
60
 
@@ -70,25 +72,31 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
70
72
  const [map, setMap] = useState<MapLibreMap | null>(null);
71
73
  const [outline, setOutline] = useState(false);
72
74
  const latest = useRef({ source, onStatus, onPick });
75
+
73
76
  useEffect(() => {
74
77
  latest.current = { source, onStatus, onPick };
75
78
  });
79
+
76
80
  const language = i18n.language.split('-')[0] ?? 'en';
77
81
  const styleKey = `${mode}:${source.state === 'tiles' ? source.styleUrl(mode) : 'outline'}`;
78
82
  const pointsKey = JSON.stringify(markers.map((marker) => [marker.id, marker.lat, marker.lon]));
79
83
 
80
84
  useEffect(() => {
81
85
  const container = element.current;
86
+
82
87
  if (container === null) {
83
88
  return;
84
89
  }
90
+
85
91
  const current = latest.current.source;
86
92
  let colors = readColors(container);
87
93
  const offline = current.state === 'tiles' && typeof navigator !== 'undefined' && navigator.onLine === false;
88
94
  const tiles = current.state === 'tiles' && !offline;
89
95
  let created: MapLibreMap;
96
+
90
97
  try {
91
98
  ensureBundledWorker(workerUrl);
99
+
92
100
  created = new MapLibreMap({
93
101
  container,
94
102
  style: current.state === 'tiles' && tiles ? current.styleUrl(mode) : baseMapStyle(colors),
@@ -106,13 +114,16 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
106
114
  },
107
115
  transformRequest: (url) => {
108
116
  const now = latest.current.source;
117
+
109
118
  return now.state === 'tiles' && now.transformRequest ? now.transformRequest(url) : { url };
110
119
  }
111
120
  });
112
121
  } catch {
113
122
  latest.current.onStatus('failed');
123
+
114
124
  return;
115
125
  }
126
+
116
127
  created.touchZoomRotate.disableRotation();
117
128
  const canvas = created.getCanvas();
118
129
  canvas.tabIndex = -1;
@@ -130,16 +141,19 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
130
141
  if (drawing === 'outline' || !wantsTiles) {
131
142
  return;
132
143
  }
144
+
133
145
  drawing = 'outline';
134
146
  reason = why;
135
147
  setOutline(true);
136
148
  created.setStyle(baseMapStyle(colors));
137
149
  latest.current.onStatus(why);
138
150
  };
151
+
139
152
  const drawTiles = (): void => {
140
153
  if (current.state !== 'tiles') {
141
154
  return;
142
155
  }
156
+
143
157
  drawing = 'tiles';
144
158
  reason = null;
145
159
  styleLoaded = false;
@@ -149,74 +163,97 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
149
163
  created.setStyle(current.styleUrl(mode));
150
164
  latest.current.onStatus('loading');
151
165
  };
166
+
152
167
  const unreachable = (): 'offline' | 'unavailable' => (typeof navigator !== 'undefined' && navigator.onLine === false ? 'offline' : 'unavailable');
153
168
 
154
169
  created.on('style.load', () => {
155
170
  if (drawing !== 'tiles') {
156
171
  return;
157
172
  }
173
+
158
174
  styleLoaded = true;
175
+
159
176
  if (restoring) {
160
177
  restoring = false;
161
178
  latest.current.onStatus('tiles');
162
179
  }
180
+
163
181
  const field = current.state === 'tiles' ? current.labelField?.(language) : undefined;
182
+
164
183
  if (field === undefined) {
165
184
  return;
166
185
  }
186
+
167
187
  for (const layer of created.getStyle().layers) {
168
188
  const textField = layer.type === 'symbol' ? layer.layout?.['text-field'] : undefined;
189
+
169
190
  if (textField !== undefined) {
170
191
  created.setLayoutProperty(layer.id, 'text-field', localizedTextField(textField, field) as never);
171
192
  }
172
193
  }
173
194
  });
195
+
174
196
  created.on('load', () => {
175
197
  if (drawing === 'tiles') {
176
198
  latest.current.onStatus('tiles');
177
199
  }
178
200
  });
201
+
179
202
  created.on('sourcedata', (event) => {
180
203
  if ('tile' in event) {
181
204
  tileErrors = 0;
182
205
  }
183
206
  });
207
+
184
208
  created.on('error', (event) => {
185
209
  if (drawing !== 'tiles') {
186
210
  return;
187
211
  }
212
+
188
213
  // Before the style is here there is nothing to draw. After it, a tile the host refuses (a ticket
189
214
  // that ran out) or several in a row leave an empty map; a single one the map retries itself.
190
215
  const status = (event.error as { status?: number } | undefined)?.status;
216
+
191
217
  if (!styleLoaded || status === 401 || status === 403 || ++tileErrors >= TILE_ERRORS_IN_A_ROW) {
192
218
  drawOutline(unreachable());
193
219
  }
194
220
  });
221
+
195
222
  const goOffline = (): void => drawOutline('offline');
223
+
196
224
  const goOnline = (): void => {
197
225
  if (reason === 'offline') {
198
226
  drawTiles();
199
227
  }
200
228
  };
229
+
201
230
  const contextLost = (): void => latest.current.onStatus('lost');
231
+
202
232
  if (wantsTiles) {
203
233
  window.addEventListener('offline', goOffline);
204
234
  window.addEventListener('online', goOnline);
205
235
  }
236
+
206
237
  canvas.addEventListener('webglcontextlost', contextLost);
238
+
207
239
  // Colors that change within the side repaint the outline in place: a style that differs only in its paint is
208
240
  // applied as a diff, so the map, its camera and its markers stay. The tiles draw colors of their own.
209
241
  const stopWatching = watchTheme(container.ownerDocument, () => {
210
242
  const next = readColors(container);
243
+
211
244
  if (next.land === colors.land && next.water === colors.water) {
212
245
  return;
213
246
  }
247
+
214
248
  colors = next;
249
+
215
250
  if (drawing === 'outline') {
216
251
  created.setStyle(baseMapStyle(colors));
217
252
  }
218
253
  });
254
+
219
255
  setMap(created);
256
+
220
257
  return () => {
221
258
  stopWatching();
222
259
  window.removeEventListener('offline', goOffline);
@@ -231,19 +268,23 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
231
268
 
232
269
  useEffect(() => {
233
270
  const bounds = boundsOf(markers);
271
+
234
272
  if (map === null || bounds === null) {
235
273
  return;
236
274
  }
275
+
237
276
  map.fitBounds(bounds, { ...mapFitOptions(outline), duration: 0 });
238
277
  // The points are compared by their coordinates, so a render that rebuilt the same list does not move the map.
239
278
  // oxlint-disable-next-line react-hooks/exhaustive-deps
240
279
  }, [map, pointsKey, outline]);
241
280
 
242
281
  const pickable = onPick !== null;
282
+
243
283
  useEffect(() => {
244
284
  if (map === null) {
245
285
  return;
246
286
  }
287
+
247
288
  const drawn = markers.map((marker) => {
248
289
  const node = document.createElement('div');
249
290
  node.setAttribute('aria-hidden', 'true');
@@ -251,18 +292,24 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
251
292
  node.className = `${MARKER} ${marker.id === selected ? PICKED : ''} ${pickable ? 'cursor-pointer' : ''}`;
252
293
  node.textContent = String(marker.number);
253
294
  node.addEventListener('click', () => latest.current.onPick?.(marker.id));
295
+
254
296
  return { marker, drawing: new Marker({ element: node }).setLngLat([marker.lon, marker.lat]).addTo(map) };
255
297
  });
298
+
256
299
  const spread = (): void => {
257
300
  const offsets = spreadOverlaps(drawn.map(({ marker }) => ({ id: marker.id, ...map.project([marker.lon, marker.lat]) })));
301
+
258
302
  for (const { marker, drawing } of drawn) {
259
303
  drawing.setOffset(offsets.get(marker.id) ?? [0, 0]);
260
304
  }
261
305
  };
306
+
262
307
  spread();
263
308
  map.on('zoomend', spread);
309
+
264
310
  return () => {
265
311
  map.off('zoomend', spread);
312
+
266
313
  for (const { drawing } of drawn) {
267
314
  drawing.remove();
268
315
  }
@@ -272,6 +319,7 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
272
319
 
273
320
  const showAll = (): void => {
274
321
  const bounds = boundsOf(markers);
322
+
275
323
  if (map !== null && bounds !== null) {
276
324
  map.fitBounds(bounds, mapFitOptions(outline));
277
325
  }
@@ -47,6 +47,7 @@ function NumberChip({ number, selected }: { number: number; selected: boolean })
47
47
  function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; number: number; selected: boolean; onPick: (() => void) | null }) {
48
48
  const { t } = useTranslation('agent-chat');
49
49
  const { detail, address } = listed.node.props;
50
+
50
51
  const body = (
51
52
  <>
52
53
  <NumberChip number={number} selected={selected} />
@@ -59,7 +60,9 @@ function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; n
59
60
  </span>
60
61
  </>
61
62
  );
63
+
62
64
  const row = 'flex h-full w-full min-h-12 items-start gap-2.5 rounded-xl bg-ui-tile px-3 py-2 text-left';
65
+
63
66
  return (
64
67
  <li>
65
68
  {onPick === null ? (
@@ -83,12 +86,15 @@ function noteFor(t: TFunction<'agent-chat'>, source: UiMapSource | null, status:
83
86
  if (status === 'offline') {
84
87
  return t('blocks.map.offline');
85
88
  }
89
+
86
90
  if (status === 'unavailable' || status === 'failed' || status === 'lost') {
87
91
  return t('blocks.map.unavailable');
88
92
  }
93
+
89
94
  if (source?.state === 'off') {
90
95
  return t(source.reason === 'disabled' ? 'blocks.map.disabled' : source.reason === 'signed-out' ? 'blocks.map.signedOut' : 'blocks.map.unavailable');
91
96
  }
97
+
92
98
  return source?.state === 'tiles' && status === 'tiles' ? (source.attribution ?? null) : null;
93
99
  }
94
100
 
@@ -117,6 +123,7 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
117
123
  const drawn = slot.state === 'granted' && !broken;
118
124
  const waiting = useLater(drawn && status === 'loading', LOADING_SPINNER_MS);
119
125
  const note = noteFor(t, source, status);
126
+
120
127
  const quiet = (text: string, action?: { label: string; run(): void }) => (
121
128
  <div className="grid h-full place-items-center content-center justify-items-center gap-2 p-3 text-center">
122
129
  <p className="text-xs text-text-faint">{text}</p>
@@ -127,11 +134,14 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
127
134
  )}
128
135
  </div>
129
136
  );
137
+
130
138
  const retry = () => {
131
139
  setStatus('loading');
132
140
  setAttempt((previous) => previous + 1);
133
141
  };
142
+
134
143
  let body: ReactNode = null;
144
+
135
145
  if (status === 'failed') {
136
146
  body = quiet(t('blocks.map.unavailable'));
137
147
  } else if (status === 'lost') {
@@ -154,6 +164,7 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
154
164
  </DiffFallbackBoundary>
155
165
  );
156
166
  }
167
+
157
168
  return (
158
169
  <>
159
170
  <div ref={setElement} className="relative h-50 overflow-hidden rounded-xl bg-ui-tile @min-[420px]:h-60">
@@ -184,33 +195,42 @@ export function MapRenderer({ node, children, context }: UiRendererProps<UiProps
184
195
  const binding = bindingOf(node, context);
185
196
  const current = binding?.value ?? node.props.value;
186
197
  const maps = useChatHost().maps;
198
+
187
199
  const places: ListedPlace[] = uiChildrenOf(node, 'Place').map((place) => ({
188
200
  node: place,
189
201
  name: uiNodeLabel(place),
190
202
  point: placePoint(place.props, place.id, context)
191
203
  }));
204
+
192
205
  const markers = places.flatMap((listed, index) =>
193
206
  listed.point ? [{ id: listed.node.id, number: index + 1, lat: listed.point.lat, lon: listed.point.lon }] : []
194
207
  );
208
+
195
209
  const selectedId = places.find((listed) => listed.node.props.value !== undefined && listed.node.props.value === current)?.node.id ?? null;
210
+
196
211
  const pick = (id: string): void => {
197
212
  const value = places.find((listed) => listed.node.id === id)?.node.props.value;
213
+
198
214
  if (value !== undefined) {
199
215
  binding?.onValueChange(value);
200
216
  }
201
217
  };
218
+
202
219
  const onPick = binding !== null ? pick : null;
220
+
203
221
  const frame = maps ? (
204
222
  <HostedFrame maps={maps} scopeId={context.scopeId} markers={markers} selected={selectedId} onPick={onPick} />
205
223
  ) : (
206
224
  <MapFrame markers={markers} source={null} selected={selectedId} onPick={onPick} />
207
225
  );
226
+
208
227
  return (
209
228
  <div className={clsx('@container flex flex-col gap-1.5', !node.complete && PENDING)}>
210
229
  {frame}
211
230
  <ol aria-label={t('blocks.map.label', { count: places.length })} className="grid gap-1.5 @min-[420px]:grid-cols-2">
212
231
  {places.map((listed, index) => {
213
232
  const { value } = listed.node.props;
233
+
214
234
  return (
215
235
  <PlaceRow
216
236
  key={listed.node.id}
@@ -8,9 +8,12 @@ import type { UiPlacePoint, UiRenderContext } from '../render-context';
8
8
  export function placePoint(props: UiProps<'Place'>, nodeId: string, context: Pick<UiRenderContext, 'place'>): UiPlacePoint | null | undefined {
9
9
  const { lat, lon, address } = props;
10
10
  const written = lat !== undefined && lon !== undefined ? { lat, lon } : undefined;
11
+
11
12
  if (address === undefined || address.trim() === '') {
12
13
  return written ?? null;
13
14
  }
15
+
14
16
  const found = context.place === undefined ? null : context.place(nodeId, address);
17
+
15
18
  return found ?? written ?? found;
16
19
  }