@burdenoff/microfe-bigconsole 2026.805.2 → 2026.812.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 (253) hide show
  1. package/dist/bigconsole/components/DrilldownDashboardRenderer.js +118 -106
  2. package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
  3. package/dist/bigconsole/components/DrilldownModal.js +54 -50
  4. package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
  5. package/dist/bigconsole/components/builder/BuilderCanvas.js +2 -2
  6. package/dist/bigconsole/components/builder/BuilderCanvas.js.map +1 -1
  7. package/dist/bigconsole/components/builder/BuilderTopbar.js +3 -3
  8. package/dist/bigconsole/components/builder/BuilderTopbar.js.map +1 -1
  9. package/dist/bigconsole/components/builder/inspector/AdvancedSection.js +24 -25
  10. package/dist/bigconsole/components/builder/inspector/AdvancedSection.js.map +1 -1
  11. package/dist/bigconsole/components/builder/inspector/AppearanceSection.js +40 -29
  12. package/dist/bigconsole/components/builder/inspector/AppearanceSection.js.map +1 -1
  13. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +145 -111
  14. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
  15. package/dist/bigconsole/components/builder/inspector/GeneralSection.js +58 -44
  16. package/dist/bigconsole/components/builder/inspector/GeneralSection.js.map +1 -1
  17. package/dist/bigconsole/components/common/Section.js +70 -0
  18. package/dist/bigconsole/components/common/Section.js.map +1 -0
  19. package/dist/bigconsole/components/common/index.js +2 -0
  20. package/dist/bigconsole/components/common/sectionStyles.js +42 -0
  21. package/dist/bigconsole/components/common/sectionStyles.js.map +1 -0
  22. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +269 -251
  23. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
  24. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +214 -223
  25. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  26. package/dist/bigconsole/components/dashboard/DashboardGrid.js +12 -18
  27. package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
  28. package/dist/bigconsole/components/dashboard/DashboardToolbar.js +110 -102
  29. package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
  30. package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +81 -68
  31. package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
  32. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +125 -159
  33. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  34. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js +147 -144
  35. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js.map +1 -1
  36. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +154 -153
  37. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
  38. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js +82 -79
  39. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js.map +1 -1
  40. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +171 -284
  41. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
  42. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +250 -273
  43. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
  44. package/dist/bigconsole/components/dashboard/LiveBoardBanner.js +45 -53
  45. package/dist/bigconsole/components/dashboard/LiveBoardBanner.js.map +1 -1
  46. package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js +157 -148
  47. package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js.map +1 -1
  48. package/dist/bigconsole/components/dashboard/PublishToStoreDialog.js +75 -83
  49. package/dist/bigconsole/components/dashboard/PublishToStoreDialog.js.map +1 -1
  50. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +167 -161
  51. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
  52. package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +181 -190
  53. package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js.map +1 -1
  54. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +101 -95
  55. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  56. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +20 -18
  57. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
  58. package/dist/bigconsole/components/dashboard/TemplatePicker/TemplatePicker.js +1 -0
  59. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +26 -58
  60. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -1
  61. package/dist/bigconsole/components/dashboard/WidgetPalette.js +307 -229
  62. package/dist/bigconsole/components/dashboard/WidgetPalette.js.map +1 -1
  63. package/dist/bigconsole/components/datasink/CacheTTLSelector.js +128 -129
  64. package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
  65. package/dist/bigconsole/components/datasink/DataSinkCard.js +78 -79
  66. package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
  67. package/dist/bigconsole/components/datasink/DataSinkDataViewer.js +33 -34
  68. package/dist/bigconsole/components/datasink/DataSinkDataViewer.js.map +1 -1
  69. package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js +79 -61
  70. package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js.map +1 -1
  71. package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js +75 -64
  72. package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js.map +1 -1
  73. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js +1 -1
  74. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js.map +1 -1
  75. package/dist/bigconsole/components/datasink/DataSinkTableViewer.js +20 -19
  76. package/dist/bigconsole/components/datasink/DataSinkTableViewer.js.map +1 -1
  77. package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js +127 -171
  78. package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js.map +1 -1
  79. package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js +113 -117
  80. package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js.map +1 -1
  81. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +312 -307
  82. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
  83. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +65 -66
  84. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
  85. package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js +121 -127
  86. package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js.map +1 -1
  87. package/dist/bigconsole/components/datasink/RefreshTypeSelector.js +194 -189
  88. package/dist/bigconsole/components/datasink/RefreshTypeSelector.js.map +1 -1
  89. package/dist/bigconsole/components/datasink/SetupStateBadge.js +3 -3
  90. package/dist/bigconsole/components/datasink/SetupStateBadge.js.map +1 -1
  91. package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js +95 -95
  92. package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js.map +1 -1
  93. package/dist/bigconsole/components/parser/DataSinkSelector.js +163 -131
  94. package/dist/bigconsole/components/parser/DataSinkSelector.js.map +1 -1
  95. package/dist/bigconsole/components/parser/ExpressionEditor.js +3 -3
  96. package/dist/bigconsole/components/parser/ExpressionEditor.js.map +1 -1
  97. package/dist/bigconsole/components/parser/ImportParserDialog.js +205 -260
  98. package/dist/bigconsole/components/parser/ImportParserDialog.js.map +1 -1
  99. package/dist/bigconsole/components/parser/ParserCard.js +133 -114
  100. package/dist/bigconsole/components/parser/ParserCard.js.map +1 -1
  101. package/dist/bigconsole/components/parser/ParserSelector.js +193 -174
  102. package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
  103. package/dist/bigconsole/components/parser/ParserStatusBadge.js +2 -2
  104. package/dist/bigconsole/components/parser/ParserStatusBadge.js.map +1 -1
  105. package/dist/bigconsole/components/parser/ParserTypeBadge.js +4 -4
  106. package/dist/bigconsole/components/parser/ParserTypeBadge.js.map +1 -1
  107. package/dist/bigconsole/components/pipeline/PipelineCard.js +134 -89
  108. package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
  109. package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js +77 -64
  110. package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js.map +1 -1
  111. package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js +7 -10
  112. package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js.map +1 -1
  113. package/dist/bigconsole/components/preview/AiPreviewContent.js +31 -32
  114. package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
  115. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +152 -103
  116. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
  117. package/dist/bigconsole/components/preview/AiPreviewPanel.js +193 -148
  118. package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
  119. package/dist/bigconsole/components/preview/previewShared.js +41 -23
  120. package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
  121. package/dist/bigconsole/components/widgets/ActionConfigDialog.js +405 -364
  122. package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
  123. package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +265 -237
  124. package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
  125. package/dist/bigconsole/components/widgets/PropertiesPanel.js +764 -821
  126. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  127. package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js +7 -5
  128. package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js.map +1 -1
  129. package/dist/bigconsole/components/widgets/WidgetHeader.js +7 -8
  130. package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
  131. package/dist/bigconsole/components/widgets/WidgetWrapper.js +8 -7
  132. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  133. package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js +1 -1
  134. package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js.map +1 -1
  135. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +46 -49
  136. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js.map +1 -1
  137. package/dist/bigconsole/components/widgets/chart/ChartConfig.js +154 -147
  138. package/dist/bigconsole/components/widgets/chart/ChartConfig.js.map +1 -1
  139. package/dist/bigconsole/components/widgets/comments/WidgetComments.js +118 -101
  140. package/dist/bigconsole/components/widgets/comments/WidgetComments.js.map +1 -1
  141. package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js +244 -191
  142. package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js.map +1 -1
  143. package/dist/bigconsole/components/widgets/form/FormWidget.js +167 -159
  144. package/dist/bigconsole/components/widgets/form/FormWidget.js.map +1 -1
  145. package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +92 -76
  146. package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js.map +1 -1
  147. package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js +1 -1
  148. package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js.map +1 -1
  149. package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js +89 -86
  150. package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js.map +1 -1
  151. package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js +2 -2
  152. package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js.map +1 -1
  153. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +64 -63
  154. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js.map +1 -1
  155. package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js +2 -2
  156. package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js.map +1 -1
  157. package/dist/bigconsole/components/widgets/list/ListConfig.js +65 -58
  158. package/dist/bigconsole/components/widgets/list/ListConfig.js.map +1 -1
  159. package/dist/bigconsole/components/widgets/list/ListItem.js +1 -1
  160. package/dist/bigconsole/components/widgets/list/ListItem.js.map +1 -1
  161. package/dist/bigconsole/components/widgets/map-widget/MapWidget.js +1 -1
  162. package/dist/bigconsole/components/widgets/map-widget/MapWidget.js.map +1 -1
  163. package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +79 -72
  164. package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js.map +1 -1
  165. package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +35 -34
  166. package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js.map +1 -1
  167. package/dist/bigconsole/components/widgets/metric-card/TrendIndicator.js +20 -19
  168. package/dist/bigconsole/components/widgets/metric-card/TrendIndicator.js.map +1 -1
  169. package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js +181 -180
  170. package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js.map +1 -1
  171. package/dist/bigconsole/components/widgets/progress/ProgressConfig.js +100 -90
  172. package/dist/bigconsole/components/widgets/progress/ProgressConfig.js.map +1 -1
  173. package/dist/bigconsole/components/widgets/retention/RetentionWidget.js +3 -3
  174. package/dist/bigconsole/components/widgets/retention/RetentionWidget.js.map +1 -1
  175. package/dist/bigconsole/components/widgets/states/EmptyState.js +6 -4
  176. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  177. package/dist/bigconsole/components/widgets/states/ErrorState.js +7 -5
  178. package/dist/bigconsole/components/widgets/states/ErrorState.js.map +1 -1
  179. package/dist/bigconsole/components/widgets/states/LoadingState.js +5 -4
  180. package/dist/bigconsole/components/widgets/states/LoadingState.js.map +1 -1
  181. package/dist/bigconsole/components/widgets/table/TableConfig.js +193 -172
  182. package/dist/bigconsole/components/widgets/table/TableConfig.js.map +1 -1
  183. package/dist/bigconsole/components/widgets/table/TableHeader.js +1 -1
  184. package/dist/bigconsole/components/widgets/table/TableHeader.js.map +1 -1
  185. package/dist/bigconsole/components/widgets/table/TableWidget.js +1 -1
  186. package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
  187. package/dist/bigconsole/components/widgets/text-widget/TextWidget.js +1 -1
  188. package/dist/bigconsole/components/widgets/text-widget/TextWidget.js.map +1 -1
  189. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js +3 -3
  190. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js.map +1 -1
  191. package/dist/bigconsole/components/widgets/utils/conditionalFormat.js +2 -2
  192. package/dist/bigconsole/components/widgets/utils/conditionalFormat.js.map +1 -1
  193. package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js +283 -249
  194. package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js.map +1 -1
  195. package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js +28 -20
  196. package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js.map +1 -1
  197. package/dist/bigconsole/components/workflow/WorkflowCard.js +92 -77
  198. package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
  199. package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js +70 -51
  200. package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js.map +1 -1
  201. package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js +16 -17
  202. package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js.map +1 -1
  203. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +46 -42
  204. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
  205. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js +372 -348
  206. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js.map +1 -1
  207. package/dist/bigconsole/hooks/useModalFocusTrap.js +53 -0
  208. package/dist/bigconsole/hooks/useModalFocusTrap.js.map +1 -0
  209. package/dist/bigconsole/pages/DashboardBuilderPage.js +153 -142
  210. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  211. package/dist/bigconsole/pages/DashboardViewPage.js +230 -220
  212. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  213. package/dist/bigconsole/pages/DashboardsPage.js +832 -790
  214. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  215. package/dist/bigconsole/pages/DataParsersPage.js +249 -304
  216. package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
  217. package/dist/bigconsole/pages/DataSinkBuilderPage.js +324 -349
  218. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  219. package/dist/bigconsole/pages/DataSinkViewPage.js +336 -323
  220. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  221. package/dist/bigconsole/pages/DataSinksPage.js +356 -458
  222. package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
  223. package/dist/bigconsole/pages/DataSourcesPage.js +14 -8
  224. package/dist/bigconsole/pages/DataSourcesPage.js.map +1 -1
  225. package/dist/bigconsole/pages/HomePage.js +186 -193
  226. package/dist/bigconsole/pages/HomePage.js.map +1 -1
  227. package/dist/bigconsole/pages/InsightsPage.js +901 -724
  228. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  229. package/dist/bigconsole/pages/ParserBuilderPage.js +769 -675
  230. package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
  231. package/dist/bigconsole/pages/ParserViewPage.js +382 -354
  232. package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
  233. package/dist/bigconsole/pages/PipelineBuilderPage.js +540 -420
  234. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  235. package/dist/bigconsole/pages/PipelineViewPage.js +416 -384
  236. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  237. package/dist/bigconsole/pages/PipelinesPage.js +260 -309
  238. package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
  239. package/dist/bigconsole/pages/SettingsPage.js +177 -172
  240. package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
  241. package/dist/bigconsole/pages/WidgetDemoPage.js +278 -142
  242. package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
  243. package/dist/bigconsole/pages/WorkflowViewPage.js +382 -366
  244. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  245. package/dist/bigconsole/pages/WorkflowsPage.js +202 -316
  246. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  247. package/dist/bigconsole/renderers/adapters/AdaptiveCardRenderer.js +111 -109
  248. package/dist/bigconsole/renderers/adapters/AdaptiveCardRenderer.js.map +1 -1
  249. package/dist/bigconsole/renderers/adapters/BlockKitRenderer.js +127 -126
  250. package/dist/bigconsole/renderers/adapters/BlockKitRenderer.js.map +1 -1
  251. package/dist/bigconsole/renderers/adapters/CustomRenderer.js +103 -103
  252. package/dist/bigconsole/renderers/adapters/CustomRenderer.js.map +1 -1
  253. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ListConfig.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/list/ListConfig.tsx"],"sourcesContent":["/**\n * ListConfig Component\n *\n * Configuration panel for List widgets.\n */\n\nimport { type FC, memo, useCallback } from 'react';\nimport type { ListConfig as ConfigType } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ListConfigProps {\n /** Current configuration */\n config: ConfigType;\n /** Callback when config changes */\n onChange: (config: ConfigType) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ListConfig: FC<ListConfigProps> = memo(function ListConfig({ config, onChange }) {\n const handleChange = useCallback(\n <K extends keyof ConfigType>(key: K, value: ConfigType[K]) => {\n onChange({ ...config, [key]: value });\n },\n [config, onChange]\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Max Items */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Maximum Items to Display</label>\n <input\n type=\"number\"\n value={config.maxItems ?? ''}\n onChange={(e) => handleChange('maxItems', e.target.value ? Number(e.target.value) : undefined)}\n min={1}\n placeholder=\"No limit\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <p className=\"text-xs text-text-secondary mt-1\">Leave empty to show all items</p>\n </div>\n\n {/* Title Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Title Field</label>\n <input\n type=\"text\"\n value={config.titleField || ''}\n onChange={(e) => handleChange('titleField', e.target.value)}\n placeholder=\"e.g., name, title\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Subtitle Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Subtitle Field</label>\n <input\n type=\"text\"\n value={config.subtitleField || ''}\n onChange={(e) => handleChange('subtitleField', e.target.value)}\n placeholder=\"e.g., description, email\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Value Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Value Field (right side)</label>\n <input\n type=\"text\"\n value={config.valueField || ''}\n onChange={(e) => handleChange('valueField', e.target.value)}\n placeholder=\"e.g., amount, count\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Badge Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Badge Field</label>\n <input\n type=\"text\"\n value={config.badgeField || ''}\n onChange={(e) => handleChange('badgeField', e.target.value)}\n placeholder=\"e.g., status, type\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Toggle Options */}\n <div className=\"space-y-3\">\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showCount !== false}\n onChange={(e) => handleChange('showCount', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show item count</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showSearch === true}\n onChange={(e) => handleChange('showSearch', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show search box</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showViewAll !== false}\n onChange={(e) => handleChange('showViewAll', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show &quot;View All&quot; link when truncated</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showIcon === true}\n onChange={(e) => handleChange('showIcon', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show item icons</span>\n </label>\n </div>\n </div>\n );\n});\n\nexport default ListConfig;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ListConfig.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/list/ListConfig.tsx"],"sourcesContent":["/**\n * ListConfig Component\n *\n * Configuration panel for List widgets.\n */\n\nimport { type FC, memo, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { ListConfig as ConfigType } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ListConfigProps {\n /** Current configuration */\n config: ConfigType;\n /** Callback when config changes */\n onChange: (config: ConfigType) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ListConfig: FC<ListConfigProps> = memo(function ListConfig({ config, onChange }) {\n const { t } = useI18n();\n const handleChange = useCallback(\n <K extends keyof ConfigType>(key: K, value: ConfigType[K]) => {\n onChange({ ...config, [key]: value });\n },\n [config, onChange]\n );\n\n return (\n <div className=\"min-w-0 space-y-form-gap\">\n {/* Max Items */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.list.maxItems.label', 'Maximum Items to Display')}\n </label>\n <input\n type=\"number\"\n value={config.maxItems ?? ''}\n onChange={(e) => handleChange('maxItems', e.target.value ? Number(e.target.value) : undefined)}\n min={1}\n placeholder={t('bigconsole.widget.config.list.maxItems.placeholder', 'No limit')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n {t('bigconsole.widget.config.list.maxItems.help', 'Leave empty to show all items')}\n </p>\n </div>\n\n {/* Title Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.list.titleField.label', 'Title Field')}\n </label>\n <input\n type=\"text\"\n value={config.titleField || ''}\n onChange={(e) => handleChange('titleField', e.target.value)}\n placeholder={t('bigconsole.widget.config.list.titleField.placeholder', 'e.g., name, title')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Subtitle Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.list.subtitleField.label', 'Subtitle Field')}\n </label>\n <input\n type=\"text\"\n value={config.subtitleField || ''}\n onChange={(e) => handleChange('subtitleField', e.target.value)}\n placeholder={t('bigconsole.widget.config.list.subtitleField.placeholder', 'e.g., description, email')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Value Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.list.valueField.label', 'Value Field (right side)')}\n </label>\n <input\n type=\"text\"\n value={config.valueField || ''}\n onChange={(e) => handleChange('valueField', e.target.value)}\n placeholder={t('bigconsole.widget.config.list.valueField.placeholder', 'e.g., amount, count')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Badge Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.list.badgeField.label', 'Badge Field')}\n </label>\n <input\n type=\"text\"\n value={config.badgeField || ''}\n onChange={(e) => handleChange('badgeField', e.target.value)}\n placeholder={t('bigconsole.widget.config.list.badgeField.placeholder', 'e.g., status, type')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Toggle Options */}\n <div\n role=\"group\"\n aria-label=\"Display options\"\n className=\"space-y-3 border-t border-[var(--color-border-subtle)] pt-form-gap\"\n >\n {/* Toggles are a different KIND of control from the field stack above,\n so they get their own labelled group instead of being visually\n identical to it (BOFF-5636). */}\n <p className=\"text-xs font-bold uppercase tracking-wider text-text-secondary\">Display options</p>\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showCount !== false}\n onChange={(e) => handleChange('showCount', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.list.showCount', 'Show item count')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showSearch === true}\n onChange={(e) => handleChange('showSearch', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.list.showSearch', 'Show search box')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showViewAll !== false}\n onChange={(e) => handleChange('showViewAll', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.list.showViewAll', 'Show \"View All\" link when truncated')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showIcon === true}\n onChange={(e) => handleChange('showIcon', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.list.showIcon', 'Show item icons')}\n </span>\n </label>\n </div>\n </div>\n );\n});\n\nexport default ListConfig;\n"],"mappings":";;;;AAyBA,IAAa,IAAkC,EAAK,SAAoB,EAAE,WAAQ,eAAY;CAC5F,IAAM,EAAE,SAAM,GAAS,EACjB,IAAe,GACU,GAAQ,MAAyB;AAC5D,IAAS;GAAE,GAAG;IAAS,IAAM;GAAO,CAAC;IAEvC,CAAC,GAAQ,EAAS,CACnB;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KAAO,WAAU;eACd,EAAE,gDAAgD,2BAA2B;KACxE,CAAA;IACR,kBAAC,SAAD;KACE,MAAK;KACL,OAAO,EAAO,YAAY;KAC1B,WAAW,MAAM,EAAa,YAAY,EAAE,OAAO,QAAQ,OAAO,EAAE,OAAO,MAAM,GAAG,KAAA,EAAU;KAC9F,KAAK;KACL,aAAa,EAAE,sDAAsD,WAAW;KAChF,WAAU;KAQV,CAAA;IACF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,+CAA+C,gCAAgC;KAChF,CAAA;IACA,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cACd,EAAE,kDAAkD,cAAc;IAC7D,CAAA,EACR,kBAAC,SAAD;IACE,MAAK;IACL,OAAO,EAAO,cAAc;IAC5B,WAAW,MAAM,EAAa,cAAc,EAAE,OAAO,MAAM;IAC3D,aAAa,EAAE,wDAAwD,oBAAoB;IAC3F,WAAU;IAQV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cACd,EAAE,qDAAqD,iBAAiB;IACnE,CAAA,EACR,kBAAC,SAAD;IACE,MAAK;IACL,OAAO,EAAO,iBAAiB;IAC/B,WAAW,MAAM,EAAa,iBAAiB,EAAE,OAAO,MAAM;IAC9D,aAAa,EAAE,2DAA2D,2BAA2B;IACrG,WAAU;IAQV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cACd,EAAE,kDAAkD,2BAA2B;IAC1E,CAAA,EACR,kBAAC,SAAD;IACE,MAAK;IACL,OAAO,EAAO,cAAc;IAC5B,WAAW,MAAM,EAAa,cAAc,EAAE,OAAO,MAAM;IAC3D,aAAa,EAAE,wDAAwD,sBAAsB;IAC7F,WAAU;IAQV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cACd,EAAE,kDAAkD,cAAc;IAC7D,CAAA,EACR,kBAAC,SAAD;IACE,MAAK;IACL,OAAO,EAAO,cAAc;IAC5B,WAAW,MAAM,EAAa,cAAc,EAAE,OAAO,MAAM;IAC3D,aAAa,EAAE,wDAAwD,qBAAqB;IAC5F,WAAU;IAQV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IACE,MAAK;IACL,cAAW;IACX,WAAU;cAHZ;KAQE,kBAAC,KAAD;MAAG,WAAU;gBAAiE;MAAmB,CAAA;KACjG,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAO,cAAc;OAC9B,WAAW,MAAM,EAAa,aAAa,EAAE,OAAO,QAAQ;OAC5D,WAAU;OAQV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,2CAA2C,kBAAkB;OAC3D,CAAA,CACD;;KAER,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAO,eAAe;OAC/B,WAAW,MAAM,EAAa,cAAc,EAAE,OAAO,QAAQ;OAC7D,WAAU;OAQV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,4CAA4C,kBAAkB;OAC5D,CAAA,CACD;;KAER,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAO,gBAAgB;OAChC,WAAW,MAAM,EAAa,eAAe,EAAE,OAAO,QAAQ;OAC9D,WAAU;OAQV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,6CAA6C,wCAAsC;OACjF,CAAA,CACD;;KAER,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAO,aAAa;OAC7B,WAAW,MAAM,EAAa,YAAY,EAAE,OAAO,QAAQ;OAC3D,WAAU;OAQV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,0CAA0C,kBAAkB;OAC1D,CAAA,CACD;;KACJ;;GACF;;EAER"}
@@ -4,7 +4,7 @@ import { jsx as t, jsxs as n } from "react/jsx-runtime";
4
4
  function r(e) {
5
5
  switch (e?.color) {
6
6
  case "success": return "bg-status-success-bg/10 text-status-success-text";
7
- case "warning": return "bg-status-warning-bg/10 text-status-warning-text";
7
+ case "warning": return "bg-status-warning-bg/10 text-[var(--color-status-warning-text)]";
8
8
  case "error": return "bg-status-error-bg/10 text-status-error-text";
9
9
  case "info": return "bg-action-primary-bg/10 text-action-primary-bg";
10
10
  default: return "bg-bg-sunken text-text-secondary";
@@ -1 +1 @@
1
- {"version":3,"file":"ListItem.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/list/ListItem.tsx"],"sourcesContent":["/**\n * ListItem Component\n *\n * Individual item in a list widget.\n */\n\nimport { type FC, memo, type ReactNode } from 'react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ListItemData {\n /** Unique identifier */\n id: string;\n /** Primary text */\n title: string;\n /** Secondary text */\n subtitle?: string;\n /** Additional metadata lines */\n metadata?: Array<{\n label: string;\n value: string | number;\n }>;\n /** Badge/tag to display */\n badge?: {\n text: string;\n color?: 'default' | 'success' | 'warning' | 'error' | 'info';\n };\n /** Icon or avatar */\n icon?: ReactNode;\n /** Value to display on the right */\n value?: string | number;\n /** Whether item is disabled */\n disabled?: boolean;\n}\n\nexport interface ListItemProps {\n /** Item data */\n item: ListItemData;\n /** Whether the item is clickable */\n clickable?: boolean;\n /** Click handler */\n onClick?: () => void;\n /** Whether to show divider */\n showDivider?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getBadgeColor(badge: ListItemData['badge']): string {\n const color = badge?.color;\n switch (color) {\n case 'success':\n return 'bg-status-success-bg/10 text-status-success-text';\n case 'warning':\n return 'bg-status-warning-bg/10 text-status-warning-text';\n case 'error':\n return 'bg-status-error-bg/10 text-status-error-text';\n case 'info':\n return 'bg-action-primary-bg/10 text-action-primary-bg';\n case 'default':\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ListItem: FC<ListItemProps> = memo(function ListItem({\n item,\n clickable = false,\n onClick,\n showDivider = true,\n}) {\n const isClickable = clickable && !item.disabled;\n\n return (\n <div\n className={`\n flex items-start gap-3 p-3\n ${showDivider ? 'border-b border-border-default' : ''}\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n ${item.disabled ? 'opacity-50' : ''}\n `}\n onClick={() => {\n if (isClickable && onClick) {\n onClick();\n }\n }}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n }}\n >\n {/* Icon/Avatar */}\n {item.icon && (\n <div className=\"flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n {item.icon}\n </div>\n )}\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Title row */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-sm font-medium text-text-primary truncate\">{item.title}</h4>\n {item.subtitle && <p className=\"text-xs text-text-secondary mt-0.5 line-clamp-2\">{item.subtitle}</p>}\n </div>\n\n {/* Value */}\n {item.value !== undefined && (\n <span className=\"flex-shrink-0 text-sm font-medium text-text-primary\">\n {typeof item.value === 'number' ? new Intl.NumberFormat('en-US').format(item.value) : item.value}\n </span>\n )}\n </div>\n\n {/* Metadata */}\n {item.metadata && item.metadata.length > 0 && (\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 mt-2\">\n {item.metadata.map((meta, index) => (\n <span key={index} className=\"text-xs text-text-secondary\">\n <span className=\"text-text-secondary/70\">{meta.label}: </span>\n <span className=\"font-medium\">{meta.value}</span>\n </span>\n ))}\n </div>\n )}\n\n {/* Badge */}\n {item.badge && (\n <div className=\"mt-2\">\n <span\n className={`\n inline-flex items-center\n px-2 py-0.5\n text-xs font-medium\n rounded-full\n ${getBadgeColor(item.badge)}\n `}\n >\n {item.badge.text}\n </span>\n </div>\n )}\n </div>\n\n {/* Chevron for clickable items */}\n {isClickable && (\n <div className=\"flex-shrink-0 self-center\">\n <svg className=\"w-4 h-4 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </div>\n )}\n </div>\n );\n});\n\nexport default ListItem;\n"],"mappings":";;;AAoDA,SAAS,EAAc,GAAsC;AAE3D,SADc,GAAO,OACrB;EACE,KAAK,UACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,OACH,QAAO;EAET,QACE,QAAO;;;AAQb,IAAa,IAA8B,EAAK,SAAkB,EAChE,SACA,eAAY,IACZ,YACA,iBAAc,MACb;CACD,IAAM,IAAc,KAAa,CAAC,EAAK;AAEvC,QACE,kBAAC,OAAD;EACE,WAAW;;UAEP,IAAc,mCAAmC,GAAG;UACpD,IAAc,2DAA2D,GAAG;UAC5E,EAAK,WAAW,eAAe,GAAG;;EAEtC,eAAe;AACb,GAAI,KAAe,KACjB,GAAS;;EAGb,MAAM,IAAc,WAAW,KAAA;EAC/B,UAAU,IAAc,IAAI,KAAA;EAC5B,YAAY,MAAM;AAChB,GAAI,KAAe,MAAY,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC5D,EAAE,gBAAgB,EAClB,GAAS;;YAjBf;GAsBG,EAAK,QACJ,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK;IACF,CAAA;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAkD,EAAK;QAAW,CAAA,EAC/E,EAAK,YAAY,kBAAC,KAAD;QAAG,WAAU;kBAAmD,EAAK;QAAa,CAAA,CAChG;UAGL,EAAK,UAAU,KAAA,KACd,kBAAC,QAAD;OAAM,WAAU;iBACb,OAAO,EAAK,SAAU,WAAW,IAAI,KAAK,aAAa,QAAQ,CAAC,OAAO,EAAK,MAAM,GAAG,EAAK;OACtF,CAAA,CAEL;;KAGL,EAAK,YAAY,EAAK,SAAS,SAAS,KACvC,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAK,SAAS,KAAK,GAAM,MACxB,kBAAC,QAAD;OAAkB,WAAU;iBAA5B,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA0C,EAAK,OAAM,KAAS;WAC9D,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAK;QAAa,CAAA,CAC5C;SAHI,EAGJ,CACP;MACE,CAAA;KAIP,EAAK,SACJ,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,QAAD;OACE,WAAW;;;;;kBAKP,EAAc,EAAK,MAAM,CAAC;;iBAG7B,EAAK,MAAM;OACP,CAAA;MACH,CAAA;KAEJ;;GAGL,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;KAA8B,MAAK;KAAO,QAAO;KAAe,SAAQ;eACrF,kBAAC,QAAD;MAAM,eAAc;MAAQ,gBAAe;MAAQ,aAAa;MAAG,GAAE;MAAiB,CAAA;KAClF,CAAA;IACF,CAAA;GAEJ;;EAER"}
1
+ {"version":3,"file":"ListItem.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/list/ListItem.tsx"],"sourcesContent":["/**\n * ListItem Component\n *\n * Individual item in a list widget.\n */\n\nimport { type FC, memo, type ReactNode } from 'react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ListItemData {\n /** Unique identifier */\n id: string;\n /** Primary text */\n title: string;\n /** Secondary text */\n subtitle?: string;\n /** Additional metadata lines */\n metadata?: Array<{\n label: string;\n value: string | number;\n }>;\n /** Badge/tag to display */\n badge?: {\n text: string;\n color?: 'default' | 'success' | 'warning' | 'error' | 'info';\n };\n /** Icon or avatar */\n icon?: ReactNode;\n /** Value to display on the right */\n value?: string | number;\n /** Whether item is disabled */\n disabled?: boolean;\n}\n\nexport interface ListItemProps {\n /** Item data */\n item: ListItemData;\n /** Whether the item is clickable */\n clickable?: boolean;\n /** Click handler */\n onClick?: () => void;\n /** Whether to show divider */\n showDivider?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getBadgeColor(badge: ListItemData['badge']): string {\n const color = badge?.color;\n switch (color) {\n case 'success':\n return 'bg-status-success-bg/10 text-status-success-text';\n case 'warning':\n return 'bg-status-warning-bg/10 text-[var(--color-status-warning-text)]';\n case 'error':\n return 'bg-status-error-bg/10 text-status-error-text';\n case 'info':\n return 'bg-action-primary-bg/10 text-action-primary-bg';\n case 'default':\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ListItem: FC<ListItemProps> = memo(function ListItem({\n item,\n clickable = false,\n onClick,\n showDivider = true,\n}) {\n const isClickable = clickable && !item.disabled;\n\n return (\n <div\n className={`\n flex items-start gap-3 p-3\n ${showDivider ? 'border-b border-border-default' : ''}\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n ${item.disabled ? 'opacity-50' : ''}\n `}\n onClick={() => {\n if (isClickable && onClick) {\n onClick();\n }\n }}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n }}\n >\n {/* Icon/Avatar */}\n {item.icon && (\n <div className=\"flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n {item.icon}\n </div>\n )}\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Title row */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-sm font-medium text-text-primary truncate\">{item.title}</h4>\n {item.subtitle && <p className=\"text-xs text-text-secondary mt-0.5 line-clamp-2\">{item.subtitle}</p>}\n </div>\n\n {/* Value */}\n {item.value !== undefined && (\n <span className=\"flex-shrink-0 text-sm font-medium text-text-primary\">\n {typeof item.value === 'number' ? new Intl.NumberFormat('en-US').format(item.value) : item.value}\n </span>\n )}\n </div>\n\n {/* Metadata */}\n {item.metadata && item.metadata.length > 0 && (\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 mt-2\">\n {item.metadata.map((meta, index) => (\n <span key={index} className=\"text-xs text-text-secondary\">\n <span className=\"text-text-secondary/70\">{meta.label}: </span>\n <span className=\"font-medium\">{meta.value}</span>\n </span>\n ))}\n </div>\n )}\n\n {/* Badge */}\n {item.badge && (\n <div className=\"mt-2\">\n <span\n className={`\n inline-flex items-center\n px-2 py-0.5\n text-xs font-medium\n rounded-full\n ${getBadgeColor(item.badge)}\n `}\n >\n {item.badge.text}\n </span>\n </div>\n )}\n </div>\n\n {/* Chevron for clickable items */}\n {isClickable && (\n <div className=\"flex-shrink-0 self-center\">\n <svg className=\"w-4 h-4 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </div>\n )}\n </div>\n );\n});\n\nexport default ListItem;\n"],"mappings":";;;AAoDA,SAAS,EAAc,GAAsC;AAE3D,SADc,GAAO,OACrB;EACE,KAAK,UACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,OACH,QAAO;EAET,QACE,QAAO;;;AAQb,IAAa,IAA8B,EAAK,SAAkB,EAChE,SACA,eAAY,IACZ,YACA,iBAAc,MACb;CACD,IAAM,IAAc,KAAa,CAAC,EAAK;AAEvC,QACE,kBAAC,OAAD;EACE,WAAW;;UAEP,IAAc,mCAAmC,GAAG;UACpD,IAAc,2DAA2D,GAAG;UAC5E,EAAK,WAAW,eAAe,GAAG;;EAEtC,eAAe;AACb,GAAI,KAAe,KACjB,GAAS;;EAGb,MAAM,IAAc,WAAW,KAAA;EAC/B,UAAU,IAAc,IAAI,KAAA;EAC5B,YAAY,MAAM;AAChB,GAAI,KAAe,MAAY,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC5D,EAAE,gBAAgB,EAClB,GAAS;;YAjBf;GAsBG,EAAK,QACJ,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK;IACF,CAAA;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAkD,EAAK;QAAW,CAAA,EAC/E,EAAK,YAAY,kBAAC,KAAD;QAAG,WAAU;kBAAmD,EAAK;QAAa,CAAA,CAChG;UAGL,EAAK,UAAU,KAAA,KACd,kBAAC,QAAD;OAAM,WAAU;iBACb,OAAO,EAAK,SAAU,WAAW,IAAI,KAAK,aAAa,QAAQ,CAAC,OAAO,EAAK,MAAM,GAAG,EAAK;OACtF,CAAA,CAEL;;KAGL,EAAK,YAAY,EAAK,SAAS,SAAS,KACvC,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAK,SAAS,KAAK,GAAM,MACxB,kBAAC,QAAD;OAAkB,WAAU;iBAA5B,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA0C,EAAK,OAAM,KAAS;WAC9D,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAK;QAAa,CAAA,CAC5C;SAHI,EAGJ,CACP;MACE,CAAA;KAIP,EAAK,SACJ,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,QAAD;OACE,WAAW;;;;;kBAKP,EAAc,EAAK,MAAM,CAAC;;iBAG7B,EAAK,MAAM;OACP,CAAA;MACH,CAAA;KAEJ;;GAGL,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;KAA8B,MAAK;KAAO,QAAO;KAAe,SAAQ;eACrF,kBAAC,QAAD;MAAM,eAAc;MAAQ,gBAAe;MAAQ,aAAa;MAAG,GAAE;MAAiB,CAAA;KAClF,CAAA;IACF,CAAA;GAEJ;;EAER"}
@@ -210,7 +210,7 @@ var y = e(function({ widget: e, data: c, onClick: l, onDrilldown: u }) {
210
210
  children: /* @__PURE__ */ o("div", {
211
211
  className: "text-center",
212
212
  children: [/* @__PURE__ */ o("svg", {
213
- className: "mx-auto mb-2 h-12 w-12 text-text-tertiary",
213
+ className: "mx-auto mb-2 h-12 w-12 text-text-muted",
214
214
  fill: "none",
215
215
  stroke: "currentColor",
216
216
  viewBox: "0 0 24 24",
@@ -1 +1 @@
1
- {"version":3,"file":"MapWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/map-widget/MapWidget.tsx"],"sourcesContent":["/**\n * MapWidget Component\n *\n * Interactive geographic map (Leaflet + OpenStreetMap tiles) with data-bound\n * markers. Leaflet's stylesheet is loaded from a CDN at runtime (with SRI) because\n * the build-time MFE library bundle does not ship a component's CSS to the host\n * shell — consistent with the map being an inherently online feature (tiles).\n */\n\nimport { type FC, memo, useEffect, useMemo, useRef, useState } from 'react';\nimport L from 'leaflet';\nimport type { Widget } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MapWidgetProps {\n widget: Widget;\n data?: MapData;\n onClick?: (marker: MapMarker) => void;\n /** Drilldown handler - receives selected marker data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MapMarker {\n name: string;\n lat: number;\n lng: number;\n value?: number;\n popup?: string;\n color?: string;\n}\n\n/** Minimal GeoJSON FeatureCollection shape (we only read geometry + properties). */\nexport interface GeoJsonFeatureCollection {\n type: 'FeatureCollection';\n features: Array<{\n type: 'Feature';\n id?: string | number;\n properties?: Record<string, unknown> | null;\n geometry: unknown;\n }>;\n}\n\n/** A region value used to shade a choropleth (joined to a GeoJSON feature). */\nexport interface MapRegionValue {\n /** Join key — matched against the feature's id field. */\n id: string;\n /** Numeric value driving the fill intensity. */\n value: number;\n /** Optional display name for the popup. */\n name?: string;\n}\n\nexport interface MapData {\n center?: { lat: number; lng: number };\n zoom?: number;\n markers?: MapMarker[];\n /** GeoJSON regions for choropleth mode. */\n geoJson?: GeoJsonFeatureCollection;\n /** Per-region values joined to GeoJSON features for choropleth shading. */\n regions?: MapRegionValue[];\n}\n\n// ============================================================================\n// Leaflet CSS (runtime CDN injection)\n// ============================================================================\n\nconst LEAFLET_CSS = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';\n// SRI for leaflet@1.9.4/dist/leaflet.css (immutable versioned URL) — guards against a CDN compromise.\nconst LEAFLET_CSS_SRI = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';\n\nlet leafletCssRequested = false;\n\nfunction ensureLeafletCss(): void {\n // Module-level guard in addition to the DOM check so two MapWidgets mounting in\n // the same tick can't both append the <link> before either lands in the DOM.\n if (leafletCssRequested || document.getElementById('leaflet-css')) return;\n leafletCssRequested = true;\n const link = document.createElement('link');\n link.id = 'leaflet-css';\n link.rel = 'stylesheet';\n link.href = LEAFLET_CSS;\n link.integrity = LEAFLET_CSS_SRI;\n link.crossOrigin = 'anonymous';\n // Don't leak the current path (workspace/dashboard IDs) to the CDN.\n link.referrerPolicy = 'no-referrer';\n // Leaflet needs its stylesheet for correct tile/marker positioning. If the CDN\n // is unreachable or the SRI check fails the load is otherwise silent, so warn and\n // clear the guard/element to allow a later mount to retry.\n link.onerror = () => {\n console.warn('[bigconsole] Leaflet CSS failed to load; map styling may be broken.');\n leafletCssRequested = false;\n link.remove();\n };\n document.head.appendChild(link);\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst DEFAULT_MARKER_COLOR = 'var(--color-chart-1, #3b82f6)';\n\n/**\n * Marker colours come from widget data and are interpolated into an inline\n * `style` attribute, so restrict them to a safe character set (hex / rgb() /\n * hsl() / css var() / named colours). Anything outside that — or containing\n * style-breaking characters — falls back to the default token colour.\n */\nfunction safeColor(color: unknown): string {\n // Marker data is untrusted: a parser can emit a non-string colour (e.g. a numeric\n // status code) that slips past the lat/lng filter, so guard the type before trim().\n if (typeof color !== 'string') return DEFAULT_MARKER_COLOR;\n const trimmed = color.trim();\n if (trimmed.length > 64) return DEFAULT_MARKER_COLOR;\n if (/[;{}<>\"'\\\\]/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Block `url()` so a marker colour can't smuggle an exfiltration beacon\n // (e.g. `url(//evil.com/pixel)`) through the otherwise-permissive char set.\n if (/\\burl\\s*\\(/i.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Allow `_` (custom-property names) and `/` (modern `rgb(r g b / a)` syntax);\n // the dangerous-char guard above still blocks anything that could break out of\n // the inline style.\n if (!/^[#a-zA-Z0-9(),.%/_\\s-]+$/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n return trimmed;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\n/**\n * Theme-safe cold→hot ramp for the heat-point layer: normalized intensity\n * (0–1) → info → warning → error status tokens, returned as `var()` refs.\n *\n * We deliberately pass the `var()` reference (not a resolved color): the map\n * uses Leaflet's default SVG renderer, where `fill` participates in the CSS\n * cascade, so the tokens resolve correctly AND stay live across theme toggles\n * (a resolved concrete color would go stale until the next data refresh). This\n * also matches the choropleth layer and avoids any per-marker `getComputedStyle`\n * lookup. (If the map ever opts into `preferCanvas`, pre-resolve these instead.)\n */\nfunction heatColor(norm: number): string {\n if (norm >= 0.66) return 'var(--color-status-error)';\n if (norm >= 0.33) return 'var(--color-status-warning)';\n return 'var(--color-status-info)';\n}\n\n/**\n * Build a marker/region popup as DOM (a bold title + optional formatted value).\n * Uses textContent — never an HTML string — so marker-supplied text can't be\n * interpreted as markup. Shared by the pin and heat-point branches.\n */\nfunction buildMarkerPopup(marker: MapMarker): HTMLDivElement {\n const popupEl = document.createElement('div');\n const title = document.createElement('div');\n title.style.fontWeight = '600';\n title.textContent = marker.popup || marker.name || 'Location';\n popupEl.appendChild(title);\n if (isFiniteNumber(marker.value)) {\n const val = document.createElement('div');\n val.textContent = marker.value.toLocaleString();\n popupEl.appendChild(val);\n }\n return popupEl;\n}\n\n/**\n * Normalize the bound widget data into markers + optional center/zoom. Accepts\n * the documented `{ markers, center, zoom }` shape, or a bare array of markers\n * (parser output), and drops any entry without valid coordinates.\n */\ninterface NormalizedMapData {\n markers: MapMarker[];\n /** Center explicitly supplied by the data payload — wins over markers. */\n center?: { lat: number; lng: number };\n /** Zoom explicitly supplied by the data payload. */\n zoom?: number;\n /** Center from widget config — a static fallback used only when there are no markers. */\n fallbackCenter?: { lat: number; lng: number };\n /** Zoom from widget config — fallback for the no-marker case. */\n fallbackZoom?: number;\n /** GeoJSON for choropleth mode. */\n geoJson?: GeoJsonFeatureCollection;\n /** region id → { value, name } map for choropleth shading. */\n regionValues: Map<string, { value: number; name?: string }>;\n}\n\nfunction asLatLng(v: unknown): { lat: number; lng: number } | undefined {\n const c = v as { lat?: unknown; lng?: unknown } | undefined;\n return c && isFiniteNumber(c.lat) && isFiniteNumber(c.lng) ? { lat: c.lat, lng: c.lng } : undefined;\n}\n\nfunction normalizeMapData(data: MapData | MapMarker[] | undefined, config: Record<string, unknown>): NormalizedMapData {\n // The widget may be handed either the documented { markers, center, zoom } shape\n // or a bare marker array (parser output); narrow once so the rest is type-safe.\n const payload: MapData | undefined = Array.isArray(data) ? undefined : data;\n const rawMarkers: unknown = Array.isArray(data) ? data : data?.markers;\n const markers: MapMarker[] = Array.isArray(rawMarkers)\n ? (rawMarkers as MapMarker[]).filter((m) => m && isFiniteNumber(m.lat) && isFiniteNumber(m.lng))\n : [];\n\n // Choropleth inputs: GeoJSON (from data payload or config) + a region→value join.\n const geoJson = (payload?.geoJson ?? (config.geoJson as GeoJsonFeatureCollection | undefined)) || undefined;\n const regionIdField = (config.regionIdField as string) || 'id';\n const regionValues = new Map<string, { value: number; name?: string }>();\n const rawRegions: unknown = payload?.regions ?? config.regions;\n if (Array.isArray(rawRegions)) {\n for (const r of rawRegions as Array<Record<string, unknown>>) {\n if (!r) continue;\n const id = r[regionIdField] ?? r.id ?? r.region ?? r.code;\n const value = r.value ?? r.count ?? r.total;\n if (id != null && isFiniteNumber(value)) {\n regionValues.set(String(id), { value, name: typeof r.name === 'string' ? r.name : undefined });\n }\n }\n }\n\n // Data-payload center/zoom are an explicit intent and win over markers. Config\n // center/zoom (e.g. the WidgetRegistry default of {0,0}/2) are only a static\n // fallback for marker-less maps — otherwise that default would pin every marker\n // dataset to the world view instead of framing the markers.\n return {\n markers,\n center: asLatLng(payload?.center),\n zoom: isFiniteNumber(payload?.zoom) ? payload?.zoom : undefined,\n fallbackCenter: asLatLng(config.center),\n fallbackZoom: isFiniteNumber(config.zoom) ? (config.zoom as number) : undefined,\n geoJson: geoJson && Array.isArray(geoJson.features) ? geoJson : undefined,\n regionValues,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MapWidget: FC<MapWidgetProps> = memo(function MapWidget({ widget, data, onClick, onDrilldown }) {\n const containerRef = useRef<HTMLDivElement>(null);\n const mapRef = useRef<L.Map | null>(null);\n // Signature of the last view we auto-framed to; lets us re-render markers on\n // every data change without snapping the viewport back when the framing\n // inputs (coords/center/zoom) are unchanged — e.g. a refetch of identical data.\n const lastFrameSigRef = useRef<string | null>(null);\n const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading');\n\n const config = useMemo(() => (widget?.config ?? {}) as Record<string, unknown>, [widget?.config]);\n const showMarkers = config.showMarkers !== false;\n // Heat-point density mode: render weighted, translucent circles whose overlap\n // accumulates into a heat-like surface (dependency-free — no leaflet.heat).\n const heatMode = config.mapMode === 'heat';\n const regionIdField = (config.regionIdField as string) || 'id';\n const { markers, center, zoom, fallbackCenter, fallbackZoom, geoJson, regionValues } = useMemo(\n () => normalizeMapData(data, config),\n [data, config]\n );\n const hasChoropleth = Boolean(geoJson && regionValues.size > 0);\n\n // Keep the latest click handler in a ref so marker bindings don't need rebinding.\n // Assign in an effect (not during render) per the React 19 useRef guidance.\n const clickRef = useRef<(marker: MapMarker) => void>(() => undefined);\n useEffect(() => {\n clickRef.current = (marker: MapMarker) => {\n if (onDrilldown) onDrilldown(marker as unknown as Record<string, unknown>);\n else if (onClick) onClick(marker);\n };\n }, [onClick, onDrilldown]);\n\n // Initialise the map once.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return undefined;\n ensureLeafletCss();\n\n let map: L.Map;\n try {\n map = L.map(el, { center: [20, 0], zoom: 2, scrollWheelZoom: true, attributionControl: true });\n const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n attribution: '© OpenStreetMap contributors',\n maxZoom: 18,\n });\n // Leaflet reports tile failures asynchronously, so addTo() won't throw when\n // tiles are blocked/offline. Surface the error overlay only if a tile errors\n // before any tile has loaded (true offline/blocked), tolerating the odd\n // transient miss once the base map is up.\n let anyTileLoaded = false;\n tiles.on('tileload', () => {\n anyTileLoaded = true;\n // Recover if an early tile miss had shown the offline overlay — a flaky\n // connection that later succeeds shouldn't stay hidden behind it.\n setStatus((s) => (s === 'error' ? 'ready' : s));\n });\n tiles.on('tileerror', () => {\n if (!anyTileLoaded) setStatus('error');\n });\n tiles.addTo(map);\n mapRef.current = map;\n // Fresh map ⇒ force the next marker effect to (re)frame it. Without this the\n // frame-signature ref would survive a remount (e.g. React StrictMode's\n // double-invoke) and the new map would stay stuck at this init view.\n lastFrameSigRef.current = null;\n setStatus('ready');\n } catch {\n setStatus('error');\n return undefined;\n }\n\n // Widgets live in a resizable grid; keep tiles correct as the box changes.\n const ro =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => mapRef.current?.invalidateSize()) : undefined;\n ro?.observe(el);\n const t = window.setTimeout(() => map.invalidateSize(), 80);\n\n return () => {\n window.clearTimeout(t);\n ro?.disconnect();\n map.remove();\n mapRef.current = null;\n };\n }, []);\n\n // Render markers + frame the view whenever the data changes.\n useEffect(() => {\n const map = mapRef.current;\n if (!map) return undefined;\n\n const layer = L.layerGroup();\n\n // Choropleth: shade GeoJSON regions by a single-hue intensity ramp (the\n // semantic `info` token at varying opacity) joined on the region id field.\n let geoLayer: L.GeoJSON | null = null;\n if (geoJson && regionValues.size > 0) {\n const vals = Array.from(regionValues.values()).map((v) => v.value);\n const min = Math.min(...vals);\n const max = Math.max(...vals);\n const span = max - min || 1;\n const FILL = 'var(--color-status-info, #3b82f6)';\n\n const lookup = (feature: { id?: string | number; properties?: Record<string, unknown> | null }) => {\n const props = feature.properties ?? {};\n const joinId = feature.id ?? props[regionIdField] ?? props.id ?? props.code;\n return joinId != null ? regionValues.get(String(joinId)) : undefined;\n };\n\n geoLayer = L.geoJSON(geoJson as unknown as Parameters<typeof L.geoJSON>[0], {\n style: (feature) => {\n const hit = feature ? lookup(feature) : undefined;\n const norm = hit ? (hit.value - min) / span : 0;\n return {\n fillColor: FILL,\n fillOpacity: hit ? 0.2 + norm * 0.65 : 0.05,\n color: 'var(--color-border-default, #cbd5e1)',\n weight: 1,\n };\n },\n onEachFeature: (feature, lyr) => {\n const hit = lookup(feature);\n const name =\n hit?.name ??\n (typeof feature.properties?.name === 'string' ? (feature.properties.name as string) : undefined) ??\n String(feature.id ?? '');\n const popupEl = document.createElement('div');\n const title = document.createElement('div');\n title.style.fontWeight = '600';\n title.textContent = name || 'Region';\n popupEl.appendChild(title);\n if (hit) {\n const val = document.createElement('div');\n val.textContent = hit.value.toLocaleString();\n popupEl.appendChild(val);\n }\n lyr.bindPopup(popupEl);\n lyr.on('click', () => clickRef.current({ name, lat: NaN, lng: NaN, value: hit?.value } as MapMarker));\n },\n });\n geoLayer.addTo(layer);\n }\n\n if (showMarkers && heatMode) {\n // Heat-point layer: translucent weighted circles, sized + colored by value.\n const weights = markers.map((mk) => (isFiniteNumber(mk.value) ? mk.value : 1));\n // reduce, not Math.min(...spread) — spreading a large array as args can\n // blow the call-stack (RangeError) for big datasets.\n const minW = weights.reduce((a, b) => Math.min(a, b), Infinity);\n const maxW = weights.reduce((a, b) => Math.max(a, b), -Infinity);\n const spanW = maxW - minW || 1;\n markers.forEach((marker, i) => {\n const norm = (weights[i] - minW) / spanW;\n const circle = L.circleMarker([marker.lat, marker.lng], {\n radius: 10 + norm * 22,\n fillColor: heatColor(norm),\n // Both radius and opacity ramp with intensity for a truer density look.\n fillOpacity: 0.25 + norm * 0.35,\n stroke: false,\n }).addTo(layer);\n circle.bindPopup(buildMarkerPopup(marker));\n circle.on('click', () => clickRef.current(marker));\n });\n } else if (showMarkers) {\n for (const marker of markers) {\n // Build the pin as a DOM element (not an HTML string) so the marker colour\n // is assigned via the style API and can never be interpreted as markup —\n // defense-in-depth alongside safeColor().\n const pin = document.createElement('div');\n Object.assign(pin.style, {\n width: '14px',\n height: '14px',\n borderRadius: '50% 50% 50% 0',\n transform: 'rotate(-45deg)',\n background: safeColor(marker.color),\n border: '2px solid #fff',\n boxShadow: '0 1px 4px rgba(0,0,0,.4)',\n });\n const icon = L.divIcon({\n className: 'bc-map-marker',\n html: pin,\n iconSize: [14, 14],\n iconAnchor: [7, 14],\n });\n const m = L.marker([marker.lat, marker.lng], { icon }).addTo(layer);\n m.bindPopup(buildMarkerPopup(marker));\n m.on('click', () => clickRef.current(marker));\n }\n }\n layer.addTo(map);\n\n // Only (re)frame the view when the framing inputs actually change — otherwise\n // a data-prop reference change (e.g. a refetch returning identical markers)\n // would discard the user's current pan/zoom.\n const frameSig = JSON.stringify({\n m: markers.map((mk) => [mk.lat, mk.lng]),\n c: center ? [center.lat, center.lng] : null,\n z: zoom ?? null,\n fc: fallbackCenter ? [fallbackCenter.lat, fallbackCenter.lng] : null,\n fz: fallbackZoom ?? null,\n g: geoJson ? regionValues.size : 0,\n });\n if (frameSig !== lastFrameSigRef.current) {\n lastFrameSigRef.current = frameSig;\n // Framing priority: data-payload center → frame the markers → frame the\n // choropleth regions → config fallback center → zoom only → world view.\n // Markers/regions win over the config default so a palette-created map frames\n // its data instead of {0,0}.\n if (center) {\n map.setView([center.lat, center.lng], zoom ?? map.getZoom());\n } else if (!markers.length && geoLayer) {\n const b = geoLayer.getBounds();\n if (b.isValid()) map.fitBounds(b, { padding: [24, 24], maxZoom: 12 });\n } else if (markers.length === 1) {\n map.setView([markers[0].lat, markers[0].lng], zoom ?? 8);\n } else if (markers.length > 1) {\n const bounds = L.latLngBounds(markers.map((m) => [m.lat, m.lng] as [number, number]));\n map.fitBounds(bounds, { padding: [32, 32], maxZoom: 12 });\n } else if (fallbackCenter) {\n map.setView([fallbackCenter.lat, fallbackCenter.lng], zoom ?? fallbackZoom ?? map.getZoom());\n } else if (isFiniteNumber(zoom ?? fallbackZoom)) {\n map.setZoom((zoom ?? fallbackZoom) as number);\n }\n }\n\n const resize = window.setTimeout(() => map.invalidateSize(), 0);\n\n return () => {\n window.clearTimeout(resize);\n layer.remove();\n };\n }, [\n markers,\n center,\n zoom,\n fallbackCenter,\n fallbackZoom,\n showMarkers,\n heatMode,\n geoJson,\n regionValues,\n regionIdField,\n ]);\n\n return (\n <div className=\"relative h-full w-full overflow-hidden rounded-lg bg-bg-sunken\">\n <div ref={containerRef} className=\"h-full w-full\" />\n\n {status !== 'error' && markers.length === 0 && !hasChoropleth && (\n <div className=\"pointer-events-none absolute inset-0 z-[500] flex items-center justify-center bg-bg-sunken/70 text-sm text-text-secondary\">\n <div className=\"text-center\">\n <svg\n className=\"mx-auto mb-2 h-12 w-12 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <p>No map data</p>\n </div>\n </div>\n )}\n\n {status === 'error' && (\n <div className=\"absolute inset-0 z-[500] flex items-center justify-center bg-bg-sunken px-4 text-center text-sm text-text-secondary\">\n The map needs an internet connection and couldn’t load.\n </div>\n )}\n </div>\n );\n});\n\nexport default MapWidget;\n"],"mappings":";;;;AAqEA,IAAM,IAAc,oDAEd,IAAkB,uDAEpB,IAAsB;AAE1B,SAAS,IAAyB;AAGhC,KAAI,KAAuB,SAAS,eAAe,cAAc,CAAE;AACnE,KAAsB;CACtB,IAAM,IAAO,SAAS,cAAc,OAAO;AAgB3C,CAfA,EAAK,KAAK,eACV,EAAK,MAAM,cACX,EAAK,OAAO,GACZ,EAAK,YAAY,GACjB,EAAK,cAAc,aAEnB,EAAK,iBAAiB,eAItB,EAAK,gBAAgB;AAGnB,EAFA,QAAQ,KAAK,sEAAsE,EACnF,IAAsB,IACtB,EAAK,QAAQ;IAEf,SAAS,KAAK,YAAY,EAAK;;AAOjC,IAAM,IAAuB;AAQ7B,SAAS,EAAU,GAAwB;AAGzC,KAAI,OAAO,KAAU,SAAU,QAAO;CACtC,IAAM,IAAU,EAAM,MAAM;AAU5B,QATI,EAAQ,SAAS,MACjB,cAAc,KAAK,EAAQ,IAG3B,cAAc,KAAK,EAAQ,IAI3B,CAAC,4BAA4B,KAAK,EAAQ,GAAS,IAChD;;AAGT,SAAS,EAAe,GAAyB;AAC/C,QAAO,OAAO,KAAM,YAAY,OAAO,SAAS,EAAE;;AAcpD,SAAS,EAAU,GAAsB;AAGvC,QAFI,KAAQ,MAAa,8BACrB,KAAQ,MAAa,gCAClB;;AAQT,SAAS,EAAiB,GAAmC;CAC3D,IAAM,IAAU,SAAS,cAAc,MAAM,EACvC,IAAQ,SAAS,cAAc,MAAM;AAI3C,KAHA,EAAM,MAAM,aAAa,OACzB,EAAM,cAAc,EAAO,SAAS,EAAO,QAAQ,YACnD,EAAQ,YAAY,EAAM,EACtB,EAAe,EAAO,MAAM,EAAE;EAChC,IAAM,IAAM,SAAS,cAAc,MAAM;AAEzC,EADA,EAAI,cAAc,EAAO,MAAM,gBAAgB,EAC/C,EAAQ,YAAY,EAAI;;AAE1B,QAAO;;AAwBT,SAAS,EAAS,GAAsD;CACtE,IAAM,IAAI;AACV,QAAO,KAAK,EAAe,EAAE,IAAI,IAAI,EAAe,EAAE,IAAI,GAAG;EAAE,KAAK,EAAE;EAAK,KAAK,EAAE;EAAK,GAAG,KAAA;;AAG5F,SAAS,EAAiB,GAAyC,GAAoD;CAGrH,IAAM,IAA+B,MAAM,QAAQ,EAAK,GAAG,KAAA,IAAY,GACjE,IAAsB,MAAM,QAAQ,EAAK,GAAG,IAAO,GAAM,SACzD,IAAuB,MAAM,QAAQ,EAAW,GACjD,EAA2B,QAAQ,MAAM,KAAK,EAAe,EAAE,IAAI,IAAI,EAAe,EAAE,IAAI,CAAC,GAC9F,EAAE,EAGA,KAAW,GAAS,WAAY,EAAO,YAAqD,KAAA,GAC5F,IAAiB,EAAO,iBAA4B,MACpD,oBAAe,IAAI,KAA+C,EAClE,IAAsB,GAAS,WAAW,EAAO;AACvD,KAAI,MAAM,QAAQ,EAAW,CAC3B,MAAK,IAAM,KAAK,GAA8C;AAC5D,MAAI,CAAC,EAAG;EACR,IAAM,IAAK,EAAE,MAAkB,EAAE,MAAM,EAAE,UAAU,EAAE,MAC/C,IAAQ,EAAE,SAAS,EAAE,SAAS,EAAE;AACtC,EAAI,KAAM,QAAQ,EAAe,EAAM,IACrC,EAAa,IAAI,OAAO,EAAG,EAAE;GAAE;GAAO,MAAM,OAAO,EAAE,QAAS,WAAW,EAAE,OAAO,KAAA;GAAW,CAAC;;AASpG,QAAO;EACL;EACA,QAAQ,EAAS,GAAS,OAAO;EACjC,MAAM,EAAe,GAAS,KAAK,GAAG,GAAS,OAAO,KAAA;EACtD,gBAAgB,EAAS,EAAO,OAAO;EACvC,cAAc,EAAe,EAAO,KAAK,GAAI,EAAO,OAAkB,KAAA;EACtE,SAAS,KAAW,MAAM,QAAQ,EAAQ,SAAS,GAAG,IAAU,KAAA;EAChE;EACD;;AAOH,IAAa,IAAgC,EAAK,SAAmB,EAAE,WAAQ,SAAM,YAAS,kBAAe;CAC3G,IAAM,IAAe,EAAuB,KAAK,EAC3C,IAAS,EAAqB,KAAK,EAInC,IAAkB,EAAsB,KAAK,EAC7C,CAAC,GAAQ,KAAa,EAAwC,UAAU,EAExE,IAAS,QAAe,GAAQ,UAAU,EAAE,EAA8B,CAAC,GAAQ,OAAO,CAAC,EAC3F,IAAc,EAAO,gBAAgB,IAGrC,IAAW,EAAO,YAAY,QAC9B,IAAiB,EAAO,iBAA4B,MACpD,EAAE,YAAS,WAAQ,SAAM,mBAAgB,iBAAc,YAAS,oBAAiB,QAC/E,EAAiB,GAAM,EAAO,EACpC,CAAC,GAAM,EAAO,CACf,EACK,IAAgB,GAAQ,KAAW,EAAa,OAAO,IAIvD,IAAW,QAA0C,KAAA,EAAU;AA2NrE,QA1NA,QAAgB;AACd,IAAS,WAAW,MAAsB;AACxC,GAAI,IAAa,EAAY,EAA6C,GACjE,KAAS,EAAQ,EAAO;;IAElC,CAAC,GAAS,EAAY,CAAC,EAG1B,QAAgB;EACd,IAAM,IAAK,EAAa;AACxB,MAAI,CAAC,EAAI;AACT,KAAkB;EAElB,IAAI;AACJ,MAAI;AACF,OAAM,EAAE,IAAI,GAAI;IAAE,QAAQ,CAAC,IAAI,EAAE;IAAE,MAAM;IAAG,iBAAiB;IAAM,oBAAoB;IAAM,CAAC;GAC9F,IAAM,IAAQ,EAAE,UAAU,sDAAsD;IAC9E,aAAa;IACb,SAAS;IACV,CAAC,EAKE,IAAgB;AAgBpB,GAfA,EAAM,GAAG,kBAAkB;AAIzB,IAHA,IAAgB,IAGhB,GAAW,MAAO,MAAM,UAAU,UAAU,EAAG;KAC/C,EACF,EAAM,GAAG,mBAAmB;AAC1B,IAAK,KAAe,EAAU,QAAQ;KACtC,EACF,EAAM,MAAM,EAAI,EAChB,EAAO,UAAU,GAIjB,EAAgB,UAAU,MAC1B,EAAU,QAAQ;UACZ;AACN,KAAU,QAAQ;AAClB;;EAIF,IAAM,IACJ,OAAO,iBAAmB,MAAc,IAAI,qBAAqB,EAAO,SAAS,gBAAgB,CAAC,GAAG,KAAA;AACvG,KAAI,QAAQ,EAAG;EACf,IAAM,IAAI,OAAO,iBAAiB,EAAI,gBAAgB,EAAE,GAAG;AAE3D,eAAa;AAIX,GAHA,OAAO,aAAa,EAAE,EACtB,GAAI,YAAY,EAChB,EAAI,QAAQ,EACZ,EAAO,UAAU;;IAElB,EAAE,CAAC,EAGN,QAAgB;EACd,IAAM,IAAM,EAAO;AACnB,MAAI,CAAC,EAAK;EAEV,IAAM,IAAQ,EAAE,YAAY,EAIxB,IAA6B;AACjC,MAAI,KAAW,EAAa,OAAO,GAAG;GACpC,IAAM,IAAO,MAAM,KAAK,EAAa,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,MAAM,EAC5D,IAAM,KAAK,IAAI,GAAG,EAAK,EAEvB,IADM,KAAK,IAAI,GAAG,EAAK,GACV,KAAO,GAGpB,KAAU,MAAmF;IACjG,IAAM,IAAQ,EAAQ,cAAc,EAAE,EAChC,IAAS,EAAQ,MAAM,EAAM,MAAkB,EAAM,MAAM,EAAM;AACvE,WAAO,KAAU,OAA0C,KAAA,IAAnC,EAAa,IAAI,OAAO,EAAO,CAAC;;AAkC1D,GA/BA,IAAW,EAAE,QAAQ,GAAuD;IAC1E,QAAQ,MAAY;KAClB,IAAM,IAAM,IAAU,EAAO,EAAQ,GAAG,KAAA,GAClC,IAAO,KAAO,EAAI,QAAQ,KAAO,IAAO;AAC9C,YAAO;MACL,WAAW;MACX,aAAa,IAAM,KAAM,IAAO,MAAO;MACvC,OAAO;MACP,QAAQ;MACT;;IAEH,gBAAgB,GAAS,MAAQ;KAC/B,IAAM,IAAM,EAAO,EAAQ,EACrB,IACJ,GAAK,SACJ,OAAO,EAAQ,YAAY,QAAS,WAAY,EAAQ,WAAW,OAAkB,KAAA,MACtF,OAAO,EAAQ,MAAM,GAAG,EACpB,IAAU,SAAS,cAAc,MAAM,EACvC,IAAQ,SAAS,cAAc,MAAM;AAI3C,SAHA,EAAM,MAAM,aAAa,OACzB,EAAM,cAAc,KAAQ,UAC5B,EAAQ,YAAY,EAAM,EACtB,GAAK;MACP,IAAM,IAAM,SAAS,cAAc,MAAM;AAEzC,MADA,EAAI,cAAc,EAAI,MAAM,gBAAgB,EAC5C,EAAQ,YAAY,EAAI;;AAG1B,KADA,EAAI,UAAU,EAAQ,EACtB,EAAI,GAAG,eAAe,EAAS,QAAQ;MAAE;MAAM,KAAK;MAAK,KAAK;MAAK,OAAO,GAAK;MAAO,CAAc,CAAC;;IAExG,CAAC,EACF,EAAS,MAAM,EAAM;;AAGvB,MAAI,KAAe,GAAU;GAE3B,IAAM,IAAU,EAAQ,KAAK,MAAQ,EAAe,EAAG,MAAM,GAAG,EAAG,QAAQ,EAAG,EAGxE,IAAO,EAAQ,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,EAAE,SAAS,EAEzD,IADO,EAAQ,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,EAAE,UAAU,GAC3C,KAAQ;AAC7B,KAAQ,SAAS,GAAQ,MAAM;IAC7B,IAAM,KAAQ,EAAQ,KAAK,KAAQ,GAC7B,IAAS,EAAE,aAAa,CAAC,EAAO,KAAK,EAAO,IAAI,EAAE;KACtD,QAAQ,KAAK,IAAO;KACpB,WAAW,EAAU,EAAK;KAE1B,aAAa,MAAO,IAAO;KAC3B,QAAQ;KACT,CAAC,CAAC,MAAM,EAAM;AAEf,IADA,EAAO,UAAU,EAAiB,EAAO,CAAC,EAC1C,EAAO,GAAG,eAAe,EAAS,QAAQ,EAAO,CAAC;KAClD;aACO,EACT,MAAK,IAAM,KAAU,GAAS;GAI5B,IAAM,IAAM,SAAS,cAAc,MAAM;AACzC,UAAO,OAAO,EAAI,OAAO;IACvB,OAAO;IACP,QAAQ;IACR,cAAc;IACd,WAAW;IACX,YAAY,EAAU,EAAO,MAAM;IACnC,QAAQ;IACR,WAAW;IACZ,CAAC;GACF,IAAM,IAAO,EAAE,QAAQ;IACrB,WAAW;IACX,MAAM;IACN,UAAU,CAAC,IAAI,GAAG;IAClB,YAAY,CAAC,GAAG,GAAG;IACpB,CAAC,EACI,IAAI,EAAE,OAAO,CAAC,EAAO,KAAK,EAAO,IAAI,EAAE,EAAE,SAAM,CAAC,CAAC,MAAM,EAAM;AAEnE,GADA,EAAE,UAAU,EAAiB,EAAO,CAAC,EACrC,EAAE,GAAG,eAAe,EAAS,QAAQ,EAAO,CAAC;;AAGjD,IAAM,MAAM,EAAI;EAKhB,IAAM,IAAW,KAAK,UAAU;GAC9B,GAAG,EAAQ,KAAK,MAAO,CAAC,EAAG,KAAK,EAAG,IAAI,CAAC;GACxC,GAAG,IAAS,CAAC,EAAO,KAAK,EAAO,IAAI,GAAG;GACvC,GAAG,KAAQ;GACX,IAAI,IAAiB,CAAC,EAAe,KAAK,EAAe,IAAI,GAAG;GAChE,IAAI,KAAgB;GACpB,GAAG,IAAU,EAAa,OAAO;GAClC,CAAC;AACF,MAAI,MAAa,EAAgB,QAM/B,KALA,EAAgB,UAAU,GAKtB,EACF,GAAI,QAAQ,CAAC,EAAO,KAAK,EAAO,IAAI,EAAE,KAAQ,EAAI,SAAS,CAAC;WACnD,CAAC,EAAQ,UAAU,GAAU;GACtC,IAAM,IAAI,EAAS,WAAW;AAC9B,GAAI,EAAE,SAAS,IAAE,EAAI,UAAU,GAAG;IAAE,SAAS,CAAC,IAAI,GAAG;IAAE,SAAS;IAAI,CAAC;aAC5D,EAAQ,WAAW,EAC5B,GAAI,QAAQ,CAAC,EAAQ,GAAG,KAAK,EAAQ,GAAG,IAAI,EAAE,KAAQ,EAAE;WAC/C,EAAQ,SAAS,GAAG;GAC7B,IAAM,IAAS,EAAE,aAAa,EAAQ,KAAK,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAqB,CAAC;AACrF,KAAI,UAAU,GAAQ;IAAE,SAAS,CAAC,IAAI,GAAG;IAAE,SAAS;IAAI,CAAC;SAChD,IACT,EAAI,QAAQ,CAAC,EAAe,KAAK,EAAe,IAAI,EAAE,KAAQ,KAAgB,EAAI,SAAS,CAAC,GACnF,EAAe,KAAQ,EAAa,IAC7C,EAAI,QAAS,KAAQ,EAAwB;EAIjD,IAAM,IAAS,OAAO,iBAAiB,EAAI,gBAAgB,EAAE,EAAE;AAE/D,eAAa;AAEX,GADA,OAAO,aAAa,EAAO,EAC3B,EAAM,QAAQ;;IAEf;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAGA,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,KAAK;IAAc,WAAU;IAAkB,CAAA;GAEnD,MAAW,WAAW,EAAQ,WAAW,KAAK,CAAC,KAC9C,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,QAAO;MACP,SAAQ;gBAJV,CAME,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA,EACF,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA,CACE;SACN,kBAAC,KAAD,EAAA,UAAG,eAAe,CAAA,CACd;;IACF,CAAA;GAGP,MAAW,WACV,kBAAC,OAAD;IAAK,WAAU;cAAsH;IAE/H,CAAA;GAEJ;;EAER"}
1
+ {"version":3,"file":"MapWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/map-widget/MapWidget.tsx"],"sourcesContent":["/**\n * MapWidget Component\n *\n * Interactive geographic map (Leaflet + OpenStreetMap tiles) with data-bound\n * markers. Leaflet's stylesheet is loaded from a CDN at runtime (with SRI) because\n * the build-time MFE library bundle does not ship a component's CSS to the host\n * shell — consistent with the map being an inherently online feature (tiles).\n */\n\nimport { type FC, memo, useEffect, useMemo, useRef, useState } from 'react';\nimport L from 'leaflet';\nimport type { Widget } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MapWidgetProps {\n widget: Widget;\n data?: MapData;\n onClick?: (marker: MapMarker) => void;\n /** Drilldown handler - receives selected marker data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MapMarker {\n name: string;\n lat: number;\n lng: number;\n value?: number;\n popup?: string;\n color?: string;\n}\n\n/** Minimal GeoJSON FeatureCollection shape (we only read geometry + properties). */\nexport interface GeoJsonFeatureCollection {\n type: 'FeatureCollection';\n features: Array<{\n type: 'Feature';\n id?: string | number;\n properties?: Record<string, unknown> | null;\n geometry: unknown;\n }>;\n}\n\n/** A region value used to shade a choropleth (joined to a GeoJSON feature). */\nexport interface MapRegionValue {\n /** Join key — matched against the feature's id field. */\n id: string;\n /** Numeric value driving the fill intensity. */\n value: number;\n /** Optional display name for the popup. */\n name?: string;\n}\n\nexport interface MapData {\n center?: { lat: number; lng: number };\n zoom?: number;\n markers?: MapMarker[];\n /** GeoJSON regions for choropleth mode. */\n geoJson?: GeoJsonFeatureCollection;\n /** Per-region values joined to GeoJSON features for choropleth shading. */\n regions?: MapRegionValue[];\n}\n\n// ============================================================================\n// Leaflet CSS (runtime CDN injection)\n// ============================================================================\n\nconst LEAFLET_CSS = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';\n// SRI for leaflet@1.9.4/dist/leaflet.css (immutable versioned URL) — guards against a CDN compromise.\nconst LEAFLET_CSS_SRI = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';\n\nlet leafletCssRequested = false;\n\nfunction ensureLeafletCss(): void {\n // Module-level guard in addition to the DOM check so two MapWidgets mounting in\n // the same tick can't both append the <link> before either lands in the DOM.\n if (leafletCssRequested || document.getElementById('leaflet-css')) return;\n leafletCssRequested = true;\n const link = document.createElement('link');\n link.id = 'leaflet-css';\n link.rel = 'stylesheet';\n link.href = LEAFLET_CSS;\n link.integrity = LEAFLET_CSS_SRI;\n link.crossOrigin = 'anonymous';\n // Don't leak the current path (workspace/dashboard IDs) to the CDN.\n link.referrerPolicy = 'no-referrer';\n // Leaflet needs its stylesheet for correct tile/marker positioning. If the CDN\n // is unreachable or the SRI check fails the load is otherwise silent, so warn and\n // clear the guard/element to allow a later mount to retry.\n link.onerror = () => {\n console.warn('[bigconsole] Leaflet CSS failed to load; map styling may be broken.');\n leafletCssRequested = false;\n link.remove();\n };\n document.head.appendChild(link);\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst DEFAULT_MARKER_COLOR = 'var(--color-chart-1, #3b82f6)';\n\n/**\n * Marker colours come from widget data and are interpolated into an inline\n * `style` attribute, so restrict them to a safe character set (hex / rgb() /\n * hsl() / css var() / named colours). Anything outside that — or containing\n * style-breaking characters — falls back to the default token colour.\n */\nfunction safeColor(color: unknown): string {\n // Marker data is untrusted: a parser can emit a non-string colour (e.g. a numeric\n // status code) that slips past the lat/lng filter, so guard the type before trim().\n if (typeof color !== 'string') return DEFAULT_MARKER_COLOR;\n const trimmed = color.trim();\n if (trimmed.length > 64) return DEFAULT_MARKER_COLOR;\n if (/[;{}<>\"'\\\\]/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Block `url()` so a marker colour can't smuggle an exfiltration beacon\n // (e.g. `url(//evil.com/pixel)`) through the otherwise-permissive char set.\n if (/\\burl\\s*\\(/i.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Allow `_` (custom-property names) and `/` (modern `rgb(r g b / a)` syntax);\n // the dangerous-char guard above still blocks anything that could break out of\n // the inline style.\n if (!/^[#a-zA-Z0-9(),.%/_\\s-]+$/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n return trimmed;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\n/**\n * Theme-safe cold→hot ramp for the heat-point layer: normalized intensity\n * (0–1) → info → warning → error status tokens, returned as `var()` refs.\n *\n * We deliberately pass the `var()` reference (not a resolved color): the map\n * uses Leaflet's default SVG renderer, where `fill` participates in the CSS\n * cascade, so the tokens resolve correctly AND stay live across theme toggles\n * (a resolved concrete color would go stale until the next data refresh). This\n * also matches the choropleth layer and avoids any per-marker `getComputedStyle`\n * lookup. (If the map ever opts into `preferCanvas`, pre-resolve these instead.)\n */\nfunction heatColor(norm: number): string {\n if (norm >= 0.66) return 'var(--color-status-error)';\n if (norm >= 0.33) return 'var(--color-status-warning)';\n return 'var(--color-status-info)';\n}\n\n/**\n * Build a marker/region popup as DOM (a bold title + optional formatted value).\n * Uses textContent — never an HTML string — so marker-supplied text can't be\n * interpreted as markup. Shared by the pin and heat-point branches.\n */\nfunction buildMarkerPopup(marker: MapMarker): HTMLDivElement {\n const popupEl = document.createElement('div');\n const title = document.createElement('div');\n title.style.fontWeight = '600';\n title.textContent = marker.popup || marker.name || 'Location';\n popupEl.appendChild(title);\n if (isFiniteNumber(marker.value)) {\n const val = document.createElement('div');\n val.textContent = marker.value.toLocaleString();\n popupEl.appendChild(val);\n }\n return popupEl;\n}\n\n/**\n * Normalize the bound widget data into markers + optional center/zoom. Accepts\n * the documented `{ markers, center, zoom }` shape, or a bare array of markers\n * (parser output), and drops any entry without valid coordinates.\n */\ninterface NormalizedMapData {\n markers: MapMarker[];\n /** Center explicitly supplied by the data payload — wins over markers. */\n center?: { lat: number; lng: number };\n /** Zoom explicitly supplied by the data payload. */\n zoom?: number;\n /** Center from widget config — a static fallback used only when there are no markers. */\n fallbackCenter?: { lat: number; lng: number };\n /** Zoom from widget config — fallback for the no-marker case. */\n fallbackZoom?: number;\n /** GeoJSON for choropleth mode. */\n geoJson?: GeoJsonFeatureCollection;\n /** region id → { value, name } map for choropleth shading. */\n regionValues: Map<string, { value: number; name?: string }>;\n}\n\nfunction asLatLng(v: unknown): { lat: number; lng: number } | undefined {\n const c = v as { lat?: unknown; lng?: unknown } | undefined;\n return c && isFiniteNumber(c.lat) && isFiniteNumber(c.lng) ? { lat: c.lat, lng: c.lng } : undefined;\n}\n\nfunction normalizeMapData(data: MapData | MapMarker[] | undefined, config: Record<string, unknown>): NormalizedMapData {\n // The widget may be handed either the documented { markers, center, zoom } shape\n // or a bare marker array (parser output); narrow once so the rest is type-safe.\n const payload: MapData | undefined = Array.isArray(data) ? undefined : data;\n const rawMarkers: unknown = Array.isArray(data) ? data : data?.markers;\n const markers: MapMarker[] = Array.isArray(rawMarkers)\n ? (rawMarkers as MapMarker[]).filter((m) => m && isFiniteNumber(m.lat) && isFiniteNumber(m.lng))\n : [];\n\n // Choropleth inputs: GeoJSON (from data payload or config) + a region→value join.\n const geoJson = (payload?.geoJson ?? (config.geoJson as GeoJsonFeatureCollection | undefined)) || undefined;\n const regionIdField = (config.regionIdField as string) || 'id';\n const regionValues = new Map<string, { value: number; name?: string }>();\n const rawRegions: unknown = payload?.regions ?? config.regions;\n if (Array.isArray(rawRegions)) {\n for (const r of rawRegions as Array<Record<string, unknown>>) {\n if (!r) continue;\n const id = r[regionIdField] ?? r.id ?? r.region ?? r.code;\n const value = r.value ?? r.count ?? r.total;\n if (id != null && isFiniteNumber(value)) {\n regionValues.set(String(id), { value, name: typeof r.name === 'string' ? r.name : undefined });\n }\n }\n }\n\n // Data-payload center/zoom are an explicit intent and win over markers. Config\n // center/zoom (e.g. the WidgetRegistry default of {0,0}/2) are only a static\n // fallback for marker-less maps — otherwise that default would pin every marker\n // dataset to the world view instead of framing the markers.\n return {\n markers,\n center: asLatLng(payload?.center),\n zoom: isFiniteNumber(payload?.zoom) ? payload?.zoom : undefined,\n fallbackCenter: asLatLng(config.center),\n fallbackZoom: isFiniteNumber(config.zoom) ? (config.zoom as number) : undefined,\n geoJson: geoJson && Array.isArray(geoJson.features) ? geoJson : undefined,\n regionValues,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MapWidget: FC<MapWidgetProps> = memo(function MapWidget({ widget, data, onClick, onDrilldown }) {\n const containerRef = useRef<HTMLDivElement>(null);\n const mapRef = useRef<L.Map | null>(null);\n // Signature of the last view we auto-framed to; lets us re-render markers on\n // every data change without snapping the viewport back when the framing\n // inputs (coords/center/zoom) are unchanged — e.g. a refetch of identical data.\n const lastFrameSigRef = useRef<string | null>(null);\n const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading');\n\n const config = useMemo(() => (widget?.config ?? {}) as Record<string, unknown>, [widget?.config]);\n const showMarkers = config.showMarkers !== false;\n // Heat-point density mode: render weighted, translucent circles whose overlap\n // accumulates into a heat-like surface (dependency-free — no leaflet.heat).\n const heatMode = config.mapMode === 'heat';\n const regionIdField = (config.regionIdField as string) || 'id';\n const { markers, center, zoom, fallbackCenter, fallbackZoom, geoJson, regionValues } = useMemo(\n () => normalizeMapData(data, config),\n [data, config]\n );\n const hasChoropleth = Boolean(geoJson && regionValues.size > 0);\n\n // Keep the latest click handler in a ref so marker bindings don't need rebinding.\n // Assign in an effect (not during render) per the React 19 useRef guidance.\n const clickRef = useRef<(marker: MapMarker) => void>(() => undefined);\n useEffect(() => {\n clickRef.current = (marker: MapMarker) => {\n if (onDrilldown) onDrilldown(marker as unknown as Record<string, unknown>);\n else if (onClick) onClick(marker);\n };\n }, [onClick, onDrilldown]);\n\n // Initialise the map once.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return undefined;\n ensureLeafletCss();\n\n let map: L.Map;\n try {\n map = L.map(el, { center: [20, 0], zoom: 2, scrollWheelZoom: true, attributionControl: true });\n const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n attribution: '© OpenStreetMap contributors',\n maxZoom: 18,\n });\n // Leaflet reports tile failures asynchronously, so addTo() won't throw when\n // tiles are blocked/offline. Surface the error overlay only if a tile errors\n // before any tile has loaded (true offline/blocked), tolerating the odd\n // transient miss once the base map is up.\n let anyTileLoaded = false;\n tiles.on('tileload', () => {\n anyTileLoaded = true;\n // Recover if an early tile miss had shown the offline overlay — a flaky\n // connection that later succeeds shouldn't stay hidden behind it.\n setStatus((s) => (s === 'error' ? 'ready' : s));\n });\n tiles.on('tileerror', () => {\n if (!anyTileLoaded) setStatus('error');\n });\n tiles.addTo(map);\n mapRef.current = map;\n // Fresh map ⇒ force the next marker effect to (re)frame it. Without this the\n // frame-signature ref would survive a remount (e.g. React StrictMode's\n // double-invoke) and the new map would stay stuck at this init view.\n lastFrameSigRef.current = null;\n setStatus('ready');\n } catch {\n setStatus('error');\n return undefined;\n }\n\n // Widgets live in a resizable grid; keep tiles correct as the box changes.\n const ro =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => mapRef.current?.invalidateSize()) : undefined;\n ro?.observe(el);\n const t = window.setTimeout(() => map.invalidateSize(), 80);\n\n return () => {\n window.clearTimeout(t);\n ro?.disconnect();\n map.remove();\n mapRef.current = null;\n };\n }, []);\n\n // Render markers + frame the view whenever the data changes.\n useEffect(() => {\n const map = mapRef.current;\n if (!map) return undefined;\n\n const layer = L.layerGroup();\n\n // Choropleth: shade GeoJSON regions by a single-hue intensity ramp (the\n // semantic `info` token at varying opacity) joined on the region id field.\n let geoLayer: L.GeoJSON | null = null;\n if (geoJson && regionValues.size > 0) {\n const vals = Array.from(regionValues.values()).map((v) => v.value);\n const min = Math.min(...vals);\n const max = Math.max(...vals);\n const span = max - min || 1;\n const FILL = 'var(--color-status-info, #3b82f6)';\n\n const lookup = (feature: { id?: string | number; properties?: Record<string, unknown> | null }) => {\n const props = feature.properties ?? {};\n const joinId = feature.id ?? props[regionIdField] ?? props.id ?? props.code;\n return joinId != null ? regionValues.get(String(joinId)) : undefined;\n };\n\n geoLayer = L.geoJSON(geoJson as unknown as Parameters<typeof L.geoJSON>[0], {\n style: (feature) => {\n const hit = feature ? lookup(feature) : undefined;\n const norm = hit ? (hit.value - min) / span : 0;\n return {\n fillColor: FILL,\n fillOpacity: hit ? 0.2 + norm * 0.65 : 0.05,\n color: 'var(--color-border-default, #cbd5e1)',\n weight: 1,\n };\n },\n onEachFeature: (feature, lyr) => {\n const hit = lookup(feature);\n const name =\n hit?.name ??\n (typeof feature.properties?.name === 'string' ? (feature.properties.name as string) : undefined) ??\n String(feature.id ?? '');\n const popupEl = document.createElement('div');\n const title = document.createElement('div');\n title.style.fontWeight = '600';\n title.textContent = name || 'Region';\n popupEl.appendChild(title);\n if (hit) {\n const val = document.createElement('div');\n val.textContent = hit.value.toLocaleString();\n popupEl.appendChild(val);\n }\n lyr.bindPopup(popupEl);\n lyr.on('click', () => clickRef.current({ name, lat: NaN, lng: NaN, value: hit?.value } as MapMarker));\n },\n });\n geoLayer.addTo(layer);\n }\n\n if (showMarkers && heatMode) {\n // Heat-point layer: translucent weighted circles, sized + colored by value.\n const weights = markers.map((mk) => (isFiniteNumber(mk.value) ? mk.value : 1));\n // reduce, not Math.min(...spread) — spreading a large array as args can\n // blow the call-stack (RangeError) for big datasets.\n const minW = weights.reduce((a, b) => Math.min(a, b), Infinity);\n const maxW = weights.reduce((a, b) => Math.max(a, b), -Infinity);\n const spanW = maxW - minW || 1;\n markers.forEach((marker, i) => {\n const norm = (weights[i] - minW) / spanW;\n const circle = L.circleMarker([marker.lat, marker.lng], {\n radius: 10 + norm * 22,\n fillColor: heatColor(norm),\n // Both radius and opacity ramp with intensity for a truer density look.\n fillOpacity: 0.25 + norm * 0.35,\n stroke: false,\n }).addTo(layer);\n circle.bindPopup(buildMarkerPopup(marker));\n circle.on('click', () => clickRef.current(marker));\n });\n } else if (showMarkers) {\n for (const marker of markers) {\n // Build the pin as a DOM element (not an HTML string) so the marker colour\n // is assigned via the style API and can never be interpreted as markup —\n // defense-in-depth alongside safeColor().\n const pin = document.createElement('div');\n Object.assign(pin.style, {\n width: '14px',\n height: '14px',\n borderRadius: '50% 50% 50% 0',\n transform: 'rotate(-45deg)',\n background: safeColor(marker.color),\n border: '2px solid #fff',\n boxShadow: '0 1px 4px rgba(0,0,0,.4)',\n });\n const icon = L.divIcon({\n className: 'bc-map-marker',\n html: pin,\n iconSize: [14, 14],\n iconAnchor: [7, 14],\n });\n const m = L.marker([marker.lat, marker.lng], { icon }).addTo(layer);\n m.bindPopup(buildMarkerPopup(marker));\n m.on('click', () => clickRef.current(marker));\n }\n }\n layer.addTo(map);\n\n // Only (re)frame the view when the framing inputs actually change — otherwise\n // a data-prop reference change (e.g. a refetch returning identical markers)\n // would discard the user's current pan/zoom.\n const frameSig = JSON.stringify({\n m: markers.map((mk) => [mk.lat, mk.lng]),\n c: center ? [center.lat, center.lng] : null,\n z: zoom ?? null,\n fc: fallbackCenter ? [fallbackCenter.lat, fallbackCenter.lng] : null,\n fz: fallbackZoom ?? null,\n g: geoJson ? regionValues.size : 0,\n });\n if (frameSig !== lastFrameSigRef.current) {\n lastFrameSigRef.current = frameSig;\n // Framing priority: data-payload center → frame the markers → frame the\n // choropleth regions → config fallback center → zoom only → world view.\n // Markers/regions win over the config default so a palette-created map frames\n // its data instead of {0,0}.\n if (center) {\n map.setView([center.lat, center.lng], zoom ?? map.getZoom());\n } else if (!markers.length && geoLayer) {\n const b = geoLayer.getBounds();\n if (b.isValid()) map.fitBounds(b, { padding: [24, 24], maxZoom: 12 });\n } else if (markers.length === 1) {\n map.setView([markers[0].lat, markers[0].lng], zoom ?? 8);\n } else if (markers.length > 1) {\n const bounds = L.latLngBounds(markers.map((m) => [m.lat, m.lng] as [number, number]));\n map.fitBounds(bounds, { padding: [32, 32], maxZoom: 12 });\n } else if (fallbackCenter) {\n map.setView([fallbackCenter.lat, fallbackCenter.lng], zoom ?? fallbackZoom ?? map.getZoom());\n } else if (isFiniteNumber(zoom ?? fallbackZoom)) {\n map.setZoom((zoom ?? fallbackZoom) as number);\n }\n }\n\n const resize = window.setTimeout(() => map.invalidateSize(), 0);\n\n return () => {\n window.clearTimeout(resize);\n layer.remove();\n };\n }, [\n markers,\n center,\n zoom,\n fallbackCenter,\n fallbackZoom,\n showMarkers,\n heatMode,\n geoJson,\n regionValues,\n regionIdField,\n ]);\n\n return (\n <div className=\"relative h-full w-full overflow-hidden rounded-lg bg-bg-sunken\">\n <div ref={containerRef} className=\"h-full w-full\" />\n\n {status !== 'error' && markers.length === 0 && !hasChoropleth && (\n <div className=\"pointer-events-none absolute inset-0 z-[500] flex items-center justify-center bg-bg-sunken/70 text-sm text-text-secondary\">\n <div className=\"text-center\">\n <svg\n className=\"mx-auto mb-2 h-12 w-12 text-text-muted\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <p>No map data</p>\n </div>\n </div>\n )}\n\n {status === 'error' && (\n <div className=\"absolute inset-0 z-[500] flex items-center justify-center bg-bg-sunken px-4 text-center text-sm text-text-secondary\">\n The map needs an internet connection and couldn’t load.\n </div>\n )}\n </div>\n );\n});\n\nexport default MapWidget;\n"],"mappings":"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"}
@@ -1,150 +1,157 @@
1
1
  import { memo as e, useCallback as t } from "react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
2
+ import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
3
4
  //#region src/bigconsole/components/widgets/metric-card/MetricCardConfig.tsx
4
- var i = e(function({ config: e, onChange: i }) {
5
- let a = t((t, n) => {
6
- i({
5
+ var a = e(function({ config: e, onChange: a }) {
6
+ let { t: o } = n(), s = t((t, n) => {
7
+ a({
7
8
  ...e,
8
9
  [t]: n
9
10
  });
10
- }, [e, i]);
11
- return /* @__PURE__ */ r("div", {
12
- className: "space-y-4",
11
+ }, [e, a]);
12
+ return /* @__PURE__ */ i("div", {
13
+ className: "min-w-0 space-y-form-gap",
13
14
  children: [
14
- /* @__PURE__ */ r("div", { children: [
15
- /* @__PURE__ */ n("label", {
15
+ /* @__PURE__ */ i("div", { children: [
16
+ /* @__PURE__ */ r("label", {
16
17
  className: "block text-sm font-medium text-text-primary mb-1",
17
- children: "Value Field"
18
+ children: o("bigconsole.widget.config.metricCard.valueField.label", "Value Field")
18
19
  }),
19
- /* @__PURE__ */ n("input", {
20
+ /* @__PURE__ */ r("input", {
20
21
  type: "text",
21
22
  value: e.valueField || "",
22
- onChange: (e) => a("valueField", e.target.value),
23
- placeholder: "e.g., totalSales, value, amount",
23
+ onChange: (e) => s("valueField", e.target.value),
24
+ placeholder: o("bigconsole.widget.config.metricCard.valueField.placeholder", "e.g., totalSales, value, amount"),
24
25
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n "
25
26
  }),
26
- /* @__PURE__ */ n("p", {
27
+ /* @__PURE__ */ r("p", {
27
28
  className: "text-xs text-text-secondary mt-1",
28
- children: "Field name to extract from data (leave empty for auto-detect)"
29
+ children: o("bigconsole.widget.config.metricCard.valueField.help", "Field name to extract from data (leave empty for auto-detect)")
29
30
  })
30
31
  ] }),
31
- /* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("label", {
32
+ /* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
32
33
  className: "block text-sm font-medium text-text-primary mb-1",
33
- children: "Subtitle"
34
- }), /* @__PURE__ */ n("input", {
34
+ children: o("bigconsole.widget.config.metricCard.subtitle.label", "Subtitle")
35
+ }), /* @__PURE__ */ r("input", {
35
36
  type: "text",
36
37
  value: e.subtitle || "",
37
- onChange: (e) => a("subtitle", e.target.value),
38
- placeholder: "e.g., Total Revenue",
38
+ onChange: (e) => s("subtitle", e.target.value),
39
+ placeholder: o("bigconsole.widget.config.metricCard.subtitle.placeholder", "e.g., Total Revenue"),
39
40
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n "
40
41
  })] }),
41
- /* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("label", {
42
+ /* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
42
43
  className: "block text-sm font-medium text-text-primary mb-1",
43
- children: "Value Format"
44
- }), /* @__PURE__ */ r("select", {
44
+ children: o("bigconsole.widget.config.metricCard.format.label", "Value Format")
45
+ }), /* @__PURE__ */ i("select", {
45
46
  value: e.format || "number",
46
- onChange: (e) => a("format", e.target.value),
47
+ onChange: (e) => s("format", e.target.value),
47
48
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n ",
48
49
  children: [
49
- /* @__PURE__ */ n("option", {
50
+ /* @__PURE__ */ r("option", {
50
51
  value: "number",
51
- children: "Number"
52
+ children: o("bigconsole.widget.config.metricCard.format.number", "Number")
52
53
  }),
53
- /* @__PURE__ */ n("option", {
54
+ /* @__PURE__ */ r("option", {
54
55
  value: "currency",
55
- children: "Currency"
56
+ children: o("bigconsole.widget.config.metricCard.format.currency", "Currency")
56
57
  }),
57
- /* @__PURE__ */ n("option", {
58
+ /* @__PURE__ */ r("option", {
58
59
  value: "percentage",
59
- children: "Percentage"
60
+ children: o("bigconsole.widget.config.metricCard.format.percentage", "Percentage")
60
61
  }),
61
- /* @__PURE__ */ n("option", {
62
+ /* @__PURE__ */ r("option", {
62
63
  value: "compact",
63
- children: "Compact (1K, 1M)"
64
+ children: o("bigconsole.widget.config.metricCard.format.compact", "Compact (1K, 1M)")
64
65
  })
65
66
  ]
66
67
  })] }),
67
- /* @__PURE__ */ r("div", {
68
- className: "grid grid-cols-2 gap-3",
69
- children: [/* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("label", {
68
+ /* @__PURE__ */ i("div", {
69
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
70
+ children: [/* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
70
71
  className: "block text-sm font-medium text-text-primary mb-1",
71
- children: "Prefix"
72
- }), /* @__PURE__ */ n("input", {
72
+ children: o("bigconsole.widget.config.metricCard.prefix.label", "Prefix")
73
+ }), /* @__PURE__ */ r("input", {
73
74
  type: "text",
74
75
  value: e.prefix || "",
75
- onChange: (e) => a("prefix", e.target.value),
76
- placeholder: "e.g., $",
76
+ onChange: (e) => s("prefix", e.target.value),
77
+ placeholder: o("bigconsole.widget.config.metricCard.prefix.placeholder", "e.g., $"),
77
78
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n "
78
- })] }), /* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("label", {
79
+ })] }), /* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
79
80
  className: "block text-sm font-medium text-text-primary mb-1",
80
- children: "Suffix"
81
- }), /* @__PURE__ */ n("input", {
81
+ children: o("bigconsole.widget.config.metricCard.suffix.label", "Suffix")
82
+ }), /* @__PURE__ */ r("input", {
82
83
  type: "text",
83
84
  value: e.suffix || "",
84
- onChange: (e) => a("suffix", e.target.value),
85
- placeholder: "e.g., users",
85
+ onChange: (e) => s("suffix", e.target.value),
86
+ placeholder: o("bigconsole.widget.config.metricCard.suffix.placeholder", "e.g., users"),
86
87
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n "
87
88
  })] })]
88
89
  }),
89
- /* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("label", {
90
+ /* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
90
91
  className: "block text-sm font-medium text-text-primary mb-1",
91
- children: "Comparison Label"
92
- }), /* @__PURE__ */ n("input", {
92
+ children: o("bigconsole.widget.config.metricCard.comparisonLabel.label", "Comparison Label")
93
+ }), /* @__PURE__ */ r("input", {
93
94
  type: "text",
94
95
  value: e.comparisonLabel || "",
95
- onChange: (e) => a("comparisonLabel", e.target.value),
96
- placeholder: "e.g., vs last week",
96
+ onChange: (e) => s("comparisonLabel", e.target.value),
97
+ placeholder: o("bigconsole.widget.config.metricCard.comparisonLabel.placeholder", "e.g., vs last week"),
97
98
  className: "\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n "
98
99
  })] }),
99
- /* @__PURE__ */ r("div", {
100
- className: "space-y-3",
100
+ /* @__PURE__ */ i("div", {
101
+ role: "group",
102
+ "aria-label": "Display options",
103
+ className: "space-y-3 border-t border-[var(--color-border-subtle)] pt-form-gap",
101
104
  children: [
102
- /* @__PURE__ */ r("label", {
105
+ /* @__PURE__ */ r("p", {
106
+ className: "text-xs font-bold uppercase tracking-wider text-text-secondary",
107
+ children: "Display options"
108
+ }),
109
+ /* @__PURE__ */ i("label", {
103
110
  className: "flex items-center gap-3",
104
- children: [/* @__PURE__ */ n("input", {
111
+ children: [/* @__PURE__ */ r("input", {
105
112
  type: "checkbox",
106
113
  checked: e.showTrend !== !1,
107
- onChange: (e) => a("showTrend", e.target.checked),
114
+ onChange: (e) => s("showTrend", e.target.checked),
108
115
  className: "\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n "
109
- }), /* @__PURE__ */ n("span", {
116
+ }), /* @__PURE__ */ r("span", {
110
117
  className: "text-sm text-text-primary",
111
- children: "Show trend indicator"
118
+ children: o("bigconsole.widget.config.metricCard.showTrend", "Show trend indicator")
112
119
  })]
113
120
  }),
114
- /* @__PURE__ */ r("label", {
121
+ /* @__PURE__ */ i("label", {
115
122
  className: "flex items-center gap-3",
116
- children: [/* @__PURE__ */ n("input", {
123
+ children: [/* @__PURE__ */ r("input", {
117
124
  type: "checkbox",
118
125
  checked: e.showComparison !== !1,
119
- onChange: (e) => a("showComparison", e.target.checked),
126
+ onChange: (e) => s("showComparison", e.target.checked),
120
127
  className: "\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n "
121
- }), /* @__PURE__ */ n("span", {
128
+ }), /* @__PURE__ */ r("span", {
122
129
  className: "text-sm text-text-primary",
123
- children: "Show comparison badge"
130
+ children: o("bigconsole.widget.config.metricCard.showComparison", "Show comparison badge")
124
131
  })]
125
132
  }),
126
- /* @__PURE__ */ r("label", {
133
+ /* @__PURE__ */ i("label", {
127
134
  className: "flex items-center gap-3",
128
- children: [/* @__PURE__ */ n("input", {
135
+ children: [/* @__PURE__ */ r("input", {
129
136
  type: "checkbox",
130
137
  checked: e.showSparkline !== !1,
131
- onChange: (e) => a("showSparkline", e.target.checked),
138
+ onChange: (e) => s("showSparkline", e.target.checked),
132
139
  className: "\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n "
133
- }), /* @__PURE__ */ n("span", {
140
+ }), /* @__PURE__ */ r("span", {
134
141
  className: "text-sm text-text-primary",
135
- children: "Show sparkline chart"
142
+ children: o("bigconsole.widget.config.metricCard.showSparkline", "Show sparkline chart")
136
143
  })]
137
144
  }),
138
- /* @__PURE__ */ r("label", {
145
+ /* @__PURE__ */ i("label", {
139
146
  className: "flex items-center gap-3",
140
- children: [/* @__PURE__ */ n("input", {
147
+ children: [/* @__PURE__ */ r("input", {
141
148
  type: "checkbox",
142
149
  checked: e.positiveIsGood !== !1,
143
- onChange: (e) => a("positiveIsGood", e.target.checked),
150
+ onChange: (e) => s("positiveIsGood", e.target.checked),
144
151
  className: "\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n "
145
- }), /* @__PURE__ */ n("span", {
152
+ }), /* @__PURE__ */ r("span", {
146
153
  className: "text-sm text-text-primary",
147
- children: "Positive change is good"
154
+ children: o("bigconsole.widget.config.metricCard.positiveIsGood", "Positive change is good")
148
155
  })]
149
156
  })
150
157
  ]
@@ -153,6 +160,6 @@ var i = e(function({ config: e, onChange: i }) {
153
160
  });
154
161
  });
155
162
  //#endregion
156
- export { i as default };
163
+ export { a as default };
157
164
 
158
165
  //# sourceMappingURL=MetricCardConfig.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MetricCardConfig.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardConfig.tsx"],"sourcesContent":["/**\n * MetricCardConfig Component\n *\n * Configuration panel for MetricCard widgets.\n */\n\nimport { type FC, memo, useCallback } from 'react';\nimport type { MetricCardConfig as ConfigType } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardConfigProps {\n /** Current configuration */\n config: ConfigType;\n /** Callback when config changes */\n onChange: (config: ConfigType) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardConfig: FC<MetricCardConfigProps> = memo(function MetricCardConfig({ config, onChange }) {\n const handleChange = useCallback(\n <K extends keyof ConfigType>(key: K, value: ConfigType[K]) => {\n onChange({ ...config, [key]: value });\n },\n [config, onChange]\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Value Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Value Field</label>\n <input\n type=\"text\"\n value={((config as unknown as Record<string, unknown>).valueField as string) || ''}\n onChange={(e) => handleChange('valueField' as keyof ConfigType, e.target.value as never)}\n placeholder=\"e.g., totalSales, value, amount\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n Field name to extract from data (leave empty for auto-detect)\n </p>\n </div>\n\n {/* Subtitle */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Subtitle</label>\n <input\n type=\"text\"\n value={config.subtitle || ''}\n onChange={(e) => handleChange('subtitle', e.target.value)}\n placeholder=\"e.g., Total Revenue\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Format */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Value Format</label>\n <select\n value={config.format || 'number'}\n onChange={(e) => handleChange('format', e.target.value as ConfigType['format'])}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n >\n <option value=\"number\">Number</option>\n <option value=\"currency\">Currency</option>\n <option value=\"percentage\">Percentage</option>\n <option value=\"compact\">Compact (1K, 1M)</option>\n </select>\n </div>\n\n {/* Prefix & Suffix */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Prefix</label>\n <input\n type=\"text\"\n value={config.prefix || ''}\n onChange={(e) => handleChange('prefix', e.target.value)}\n placeholder=\"e.g., $\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Suffix</label>\n <input\n type=\"text\"\n value={config.suffix || ''}\n onChange={(e) => handleChange('suffix', e.target.value)}\n placeholder=\"e.g., users\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n </div>\n\n {/* Comparison Label */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Comparison Label</label>\n <input\n type=\"text\"\n value={config.comparisonLabel || ''}\n onChange={(e) => handleChange('comparisonLabel', e.target.value)}\n placeholder=\"e.g., vs last week\"\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Toggle Options */}\n <div className=\"space-y-3\">\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showTrend !== false}\n onChange={(e) => handleChange('showTrend', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show trend indicator</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showComparison !== false}\n onChange={(e) => handleChange('showComparison', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show comparison badge</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showSparkline !== false}\n onChange={(e) => handleChange('showSparkline', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Show sparkline chart</span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.positiveIsGood !== false}\n onChange={(e) => handleChange('positiveIsGood', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">Positive change is good</span>\n </label>\n </div>\n </div>\n );\n});\n\nexport default MetricCardConfig;\n"],"mappings":"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"}
1
+ {"version":3,"file":"MetricCardConfig.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardConfig.tsx"],"sourcesContent":["/**\n * MetricCardConfig Component\n *\n * Configuration panel for MetricCard widgets.\n */\n\nimport { type FC, memo, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { MetricCardConfig as ConfigType } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardConfigProps {\n /** Current configuration */\n config: ConfigType;\n /** Callback when config changes */\n onChange: (config: ConfigType) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardConfig: FC<MetricCardConfigProps> = memo(function MetricCardConfig({ config, onChange }) {\n const { t } = useI18n();\n const handleChange = useCallback(\n <K extends keyof ConfigType>(key: K, value: ConfigType[K]) => {\n onChange({ ...config, [key]: value });\n },\n [config, onChange]\n );\n\n return (\n <div className=\"min-w-0 space-y-form-gap\">\n {/* Value Field */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.valueField.label', 'Value Field')}\n </label>\n <input\n type=\"text\"\n value={((config as unknown as Record<string, unknown>).valueField as string) || ''}\n onChange={(e) => handleChange('valueField' as keyof ConfigType, e.target.value as never)}\n placeholder={t(\n 'bigconsole.widget.config.metricCard.valueField.placeholder',\n 'e.g., totalSales, value, amount'\n )}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n {t(\n 'bigconsole.widget.config.metricCard.valueField.help',\n 'Field name to extract from data (leave empty for auto-detect)'\n )}\n </p>\n </div>\n\n {/* Subtitle */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.subtitle.label', 'Subtitle')}\n </label>\n <input\n type=\"text\"\n value={config.subtitle || ''}\n onChange={(e) => handleChange('subtitle', e.target.value)}\n placeholder={t('bigconsole.widget.config.metricCard.subtitle.placeholder', 'e.g., Total Revenue')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Format */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.format.label', 'Value Format')}\n </label>\n <select\n value={config.format || 'number'}\n onChange={(e) => handleChange('format', e.target.value as ConfigType['format'])}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n >\n <option value=\"number\">{t('bigconsole.widget.config.metricCard.format.number', 'Number')}</option>\n <option value=\"currency\">{t('bigconsole.widget.config.metricCard.format.currency', 'Currency')}</option>\n <option value=\"percentage\">{t('bigconsole.widget.config.metricCard.format.percentage', 'Percentage')}</option>\n <option value=\"compact\">{t('bigconsole.widget.config.metricCard.format.compact', 'Compact (1K, 1M)')}</option>\n </select>\n </div>\n\n {/* Prefix & Suffix */}\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.prefix.label', 'Prefix')}\n </label>\n <input\n type=\"text\"\n value={config.prefix || ''}\n onChange={(e) => handleChange('prefix', e.target.value)}\n placeholder={t('bigconsole.widget.config.metricCard.prefix.placeholder', 'e.g., $')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.suffix.label', 'Suffix')}\n </label>\n <input\n type=\"text\"\n value={config.suffix || ''}\n onChange={(e) => handleChange('suffix', e.target.value)}\n placeholder={t('bigconsole.widget.config.metricCard.suffix.placeholder', 'e.g., users')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n </div>\n\n {/* Comparison Label */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.widget.config.metricCard.comparisonLabel.label', 'Comparison Label')}\n </label>\n <input\n type=\"text\"\n value={config.comparisonLabel || ''}\n onChange={(e) => handleChange('comparisonLabel', e.target.value)}\n placeholder={t('bigconsole.widget.config.metricCard.comparisonLabel.placeholder', 'e.g., vs last week')}\n className=\"\n w-full px-3 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n </div>\n\n {/* Toggle Options */}\n <div\n role=\"group\"\n aria-label=\"Display options\"\n className=\"space-y-3 border-t border-[var(--color-border-subtle)] pt-form-gap\"\n >\n {/* Toggles are a different KIND of control from the field stack above,\n so they get their own labelled group instead of being visually\n identical to it (BOFF-5636). */}\n <p className=\"text-xs font-bold uppercase tracking-wider text-text-secondary\">Display options</p>\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showTrend !== false}\n onChange={(e) => handleChange('showTrend', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.metricCard.showTrend', 'Show trend indicator')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showComparison !== false}\n onChange={(e) => handleChange('showComparison', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.metricCard.showComparison', 'Show comparison badge')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.showSparkline !== false}\n onChange={(e) => handleChange('showSparkline', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.metricCard.showSparkline', 'Show sparkline chart')}\n </span>\n </label>\n\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={config.positiveIsGood !== false}\n onChange={(e) => handleChange('positiveIsGood', e.target.checked)}\n className=\"\n w-4 h-4\n text-action-primary-bg\n bg-bg-surface\n border-border-default\n rounded\n focus:ring-action-primary-bg\n \"\n />\n <span className=\"text-sm text-text-primary\">\n {t('bigconsole.widget.config.metricCard.positiveIsGood', 'Positive change is good')}\n </span>\n </label>\n </div>\n </div>\n );\n});\n\nexport default MetricCardConfig;\n"],"mappings":"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"}