@keyfunc/keybricks-ds 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/DEPLOY.md +204 -0
  2. package/DESIGN.md +269 -0
  3. package/INTEGRATION.md +417 -0
  4. package/SKILL.md +32 -0
  5. package/_adherence.oxlintrc.json +1469 -0
  6. package/_scripts/build-index.mjs +80 -0
  7. package/_scripts/build-tokens.mjs +135 -0
  8. package/assets/icons/icon-index.js +1 -0
  9. package/assets/icons/icon-index.json +1 -0
  10. package/assets/icons/unicons-index.js +1 -0
  11. package/assets/icons/unicons-index.json +1 -0
  12. package/assets/keybricks-logo.png +0 -0
  13. package/components/_cards/accordion.card.html +32 -0
  14. package/components/_cards/action-chip.card.html +22 -0
  15. package/components/_cards/activity.card.html +34 -0
  16. package/components/_cards/alert.card.html +32 -0
  17. package/components/_cards/app-chrome.card.html +36 -0
  18. package/components/_cards/banner.card.html +21 -0
  19. package/components/_cards/brand-visual.card.html +43 -0
  20. package/components/_cards/breadcrumb.card.html +23 -0
  21. package/components/_cards/button-1-base.card.html +39 -0
  22. package/components/_cards/button-2-states.card.html +56 -0
  23. package/components/_cards/button-3-sizes-icons.card.html +32 -0
  24. package/components/_cards/button-4-group.card.html +47 -0
  25. package/components/_cards/canvas-zoom-controls.card.html +19 -0
  26. package/components/_cards/cards-indicators.card.html +59 -0
  27. package/components/_cards/charts-1-sparkline.card.html +55 -0
  28. package/components/_cards/charts-10-gauge.card.html +31 -0
  29. package/components/_cards/charts-11-funnel.card.html +28 -0
  30. package/components/_cards/charts-12-treemap.card.html +23 -0
  31. package/components/_cards/charts-14-radar.card.html +31 -0
  32. package/components/_cards/charts-15-distribution.card.html +32 -0
  33. package/components/_cards/charts-16-pareto.card.html +25 -0
  34. package/components/_cards/charts-17-candlestick.card.html +34 -0
  35. package/components/_cards/charts-18-sankey.card.html +29 -0
  36. package/components/_cards/charts-19-chord.card.html +31 -0
  37. package/components/_cards/charts-2-line.card.html +46 -0
  38. package/components/_cards/charts-20-sunburst.card.html +38 -0
  39. package/components/_cards/charts-21-worldmap.card.html +33 -0
  40. package/components/_cards/charts-22-gantt.card.html +37 -0
  41. package/components/_cards/charts-23-bar3d.card.html +26 -0
  42. package/components/_cards/charts-24-pie3d.card.html +32 -0
  43. package/components/_cards/charts-25-surface3d.card.html +24 -0
  44. package/components/_cards/charts-26-scatter3d.card.html +24 -0
  45. package/components/_cards/charts-27-ribbon3d.card.html +23 -0
  46. package/components/_cards/charts-3-bar.card.html +71 -0
  47. package/components/_cards/charts-4-barlist.card.html +30 -0
  48. package/components/_cards/charts-5-donut.card.html +35 -0
  49. package/components/_cards/charts-6-scatter.card.html +26 -0
  50. package/components/_cards/charts-7-heatmap.card.html +28 -0
  51. package/components/_cards/charts-8-waterfall.card.html +25 -0
  52. package/components/_cards/charts-9-bullet.card.html +31 -0
  53. package/components/_cards/charts-99-fullpage.card.html +194 -0
  54. package/components/_cards/checkbox.card.html +34 -0
  55. package/components/_cards/codeblock.card.html +19 -0
  56. package/components/_cards/combobox.card.html +25 -0
  57. package/components/_cards/context-menu.card.html +21 -0
  58. package/components/_cards/datatable.card.html +48 -0
  59. package/components/_cards/date-picker.card.html +31 -0
  60. package/components/_cards/diffvalue.card.html +21 -0
  61. package/components/_cards/divider.card.html +24 -0
  62. package/components/_cards/drawer.card.html +30 -0
  63. package/components/_cards/dropdown-menu.card.html +38 -0
  64. package/components/_cards/dropzone.card.html +22 -0
  65. package/components/_cards/empty-state.card.html +29 -0
  66. package/components/_cards/filter-bar.card.html +34 -0
  67. package/components/_cards/icon-button.card.html +27 -0
  68. package/components/_cards/icons-1-core.card.html +49 -0
  69. package/components/_cards/icons-2-feather.card.html +39 -0
  70. package/components/_cards/icons-3-unicons.card.html +44 -0
  71. package/components/_cards/input.card.html +25 -0
  72. package/components/_cards/kbd.card.html +20 -0
  73. package/components/_cards/keyvalue.card.html +23 -0
  74. package/components/_cards/listrow.card.html +30 -0
  75. package/components/_cards/loading.card.html +33 -0
  76. package/components/_cards/log-viewer.card.html +23 -0
  77. package/components/_cards/modal.card.html +35 -0
  78. package/components/_cards/multi-select.card.html +26 -0
  79. package/components/_cards/number-stepper.card.html +24 -0
  80. package/components/_cards/pagination.card.html +25 -0
  81. package/components/_cards/people.card.html +46 -0
  82. package/components/_cards/pipeline-node.card.html +76 -0
  83. package/components/_cards/pipeline-palette.card.html +54 -0
  84. package/components/_cards/search.card.html +23 -0
  85. package/components/_cards/select.card.html +23 -0
  86. package/components/_cards/slider.card.html +25 -0
  87. package/components/_cards/smart-table.card.html +37 -0
  88. package/components/_cards/spreadsheet.card.html +31 -0
  89. package/components/_cards/stepper.card.html +25 -0
  90. package/components/_cards/tabs.card.html +25 -0
  91. package/components/_cards/tag.card.html +26 -0
  92. package/components/_cards/textarea.card.html +27 -0
  93. package/components/_cards/timeline.card.html +32 -0
  94. package/components/_cards/toast.card.html +33 -0
  95. package/components/_cards/toggles.card.html +68 -0
  96. package/components/_cards/tooltip.card.html +22 -0
  97. package/components/_cards/tree.card.html +150 -0
  98. package/components/_shared/frame-theme.js +16 -0
  99. package/components/_shared/src-brand.js +1 -0
  100. package/components/_shared/src-buttons.js +1 -0
  101. package/components/_shared/src-charts.js +1 -0
  102. package/components/_shared/src-data.js +1 -0
  103. package/components/_shared/src-display.js +1 -0
  104. package/components/_shared/src-feedback.js +1 -0
  105. package/components/_shared/src-forms.js +1 -0
  106. package/components/_shared/src-icons.js +1 -0
  107. package/components/_shared/src-navigation.js +1 -0
  108. package/components/_shared/src-pipeline.js +1 -0
  109. package/components/_shared/src-tabs.js +1 -0
  110. package/components/_shared/svg-download.js +19 -0
  111. package/components/_shared/view-source.js +185 -0
  112. package/components/_vue/index.mjs +114 -0
  113. package/components/brand/BrandVisual.d.ts +17 -0
  114. package/components/brand/BrandVisual.jsx +29 -0
  115. package/components/brand/BrandVisual.vue +34 -0
  116. package/components/brand/_dir.card.html +13 -0
  117. package/components/brand/brandMotifs.js +210 -0
  118. package/components/buttons/ActionChip.d.ts +10 -0
  119. package/components/buttons/ActionChip.jsx +20 -0
  120. package/components/buttons/ActionChip.vue +18 -0
  121. package/components/buttons/Button.d.ts +40 -0
  122. package/components/buttons/Button.jsx +101 -0
  123. package/components/buttons/Button.vue +87 -0
  124. package/components/buttons/IconButton.d.ts +26 -0
  125. package/components/buttons/IconButton.jsx +28 -0
  126. package/components/buttons/IconButton.vue +21 -0
  127. package/components/buttons/_dir.card.html +13 -0
  128. package/components/buttons/buttons.prompt.md +19 -0
  129. package/components/charts/Bar3D.d.ts +15 -0
  130. package/components/charts/Bar3D.jsx +93 -0
  131. package/components/charts/Bar3D.vue +79 -0
  132. package/components/charts/BarChart.d.ts +23 -0
  133. package/components/charts/BarChart.jsx +135 -0
  134. package/components/charts/BarChart.vue +139 -0
  135. package/components/charts/BarList.d.ts +13 -0
  136. package/components/charts/BarList.jsx +31 -0
  137. package/components/charts/BarList.vue +25 -0
  138. package/components/charts/BoxPlot.d.ts +18 -0
  139. package/components/charts/BoxPlot.jsx +62 -0
  140. package/components/charts/BoxPlot.vue +58 -0
  141. package/components/charts/BulletChart.d.ts +12 -0
  142. package/components/charts/BulletChart.jsx +41 -0
  143. package/components/charts/BulletChart.vue +33 -0
  144. package/components/charts/CandlestickChart.d.ts +15 -0
  145. package/components/charts/CandlestickChart.jsx +63 -0
  146. package/components/charts/CandlestickChart.vue +62 -0
  147. package/components/charts/ChartKit.d.ts +8 -0
  148. package/components/charts/ChartKit.jsx +76 -0
  149. package/components/charts/ChartKit.vue +47 -0
  150. package/components/charts/ChordChart.d.ts +13 -0
  151. package/components/charts/ChordChart.jsx +55 -0
  152. package/components/charts/ChordChart.vue +54 -0
  153. package/components/charts/DonutChart.d.ts +15 -0
  154. package/components/charts/DonutChart.jsx +73 -0
  155. package/components/charts/DonutChart.vue +61 -0
  156. package/components/charts/FunnelChart.d.ts +11 -0
  157. package/components/charts/FunnelChart.jsx +37 -0
  158. package/components/charts/FunnelChart.vue +29 -0
  159. package/components/charts/GanttChart.d.ts +20 -0
  160. package/components/charts/GanttChart.jsx +80 -0
  161. package/components/charts/GanttChart.vue +60 -0
  162. package/components/charts/GaugeChart.d.ts +13 -0
  163. package/components/charts/GaugeChart.jsx +33 -0
  164. package/components/charts/GaugeChart.vue +29 -0
  165. package/components/charts/HeatmapChart.d.ts +13 -0
  166. package/components/charts/HeatmapChart.jsx +62 -0
  167. package/components/charts/HeatmapChart.vue +38 -0
  168. package/components/charts/Histogram.d.ts +16 -0
  169. package/components/charts/Histogram.jsx +56 -0
  170. package/components/charts/Histogram.vue +53 -0
  171. package/components/charts/LineChart.d.ts +17 -0
  172. package/components/charts/LineChart.jsx +84 -0
  173. package/components/charts/LineChart.vue +86 -0
  174. package/components/charts/ParetoChart.d.ts +13 -0
  175. package/components/charts/ParetoChart.jsx +54 -0
  176. package/components/charts/ParetoChart.vue +55 -0
  177. package/components/charts/Pie3D.d.ts +17 -0
  178. package/components/charts/Pie3D.jsx +145 -0
  179. package/components/charts/Pie3D.vue +115 -0
  180. package/components/charts/RadarChart.d.ts +14 -0
  181. package/components/charts/RadarChart.jsx +79 -0
  182. package/components/charts/RadarChart.vue +73 -0
  183. package/components/charts/Ribbon3D.d.ts +13 -0
  184. package/components/charts/Ribbon3D.jsx +89 -0
  185. package/components/charts/Ribbon3D.vue +79 -0
  186. package/components/charts/SankeyChart.d.ts +15 -0
  187. package/components/charts/SankeyChart.jsx +59 -0
  188. package/components/charts/SankeyChart.vue +53 -0
  189. package/components/charts/Scatter3D.d.ts +12 -0
  190. package/components/charts/Scatter3D.jsx +69 -0
  191. package/components/charts/Scatter3D.vue +59 -0
  192. package/components/charts/ScatterChart.d.ts +12 -0
  193. package/components/charts/ScatterChart.jsx +53 -0
  194. package/components/charts/ScatterChart.vue +57 -0
  195. package/components/charts/Sparkline.d.ts +19 -0
  196. package/components/charts/Sparkline.jsx +63 -0
  197. package/components/charts/Sparkline.vue +54 -0
  198. package/components/charts/SunburstChart.d.ts +12 -0
  199. package/components/charts/SunburstChart.jsx +61 -0
  200. package/components/charts/SunburstChart.vue +60 -0
  201. package/components/charts/Surface3D.d.ts +13 -0
  202. package/components/charts/Surface3D.jsx +70 -0
  203. package/components/charts/Surface3D.vue +63 -0
  204. package/components/charts/TreemapChart.d.ts +11 -0
  205. package/components/charts/TreemapChart.jsx +35 -0
  206. package/components/charts/TreemapChart.vue +27 -0
  207. package/components/charts/WaterfallChart.d.ts +11 -0
  208. package/components/charts/WaterfallChart.jsx +59 -0
  209. package/components/charts/WaterfallChart.vue +46 -0
  210. package/components/charts/WorldMap.d.ts +21 -0
  211. package/components/charts/WorldMap.jsx +107 -0
  212. package/components/charts/WorldMap.vue +73 -0
  213. package/components/charts/_dir.card.html +7 -0
  214. package/components/charts/chart-3d.js +84 -0
  215. package/components/charts/chart-geometry.js +223 -0
  216. package/components/charts/charts.prompt.md +24 -0
  217. package/components/data/CodeBlock.d.ts +9 -0
  218. package/components/data/CodeBlock.jsx +19 -0
  219. package/components/data/CodeBlock.vue +22 -0
  220. package/components/data/DataTable.d.ts +28 -0
  221. package/components/data/DataTable.jsx +32 -0
  222. package/components/data/DataTable.vue +25 -0
  223. package/components/data/DiffValue.d.ts +11 -0
  224. package/components/data/DiffValue.jsx +15 -0
  225. package/components/data/DiffValue.vue +13 -0
  226. package/components/data/FilterBar.d.ts +16 -0
  227. package/components/data/FilterBar.jsx +22 -0
  228. package/components/data/FilterBar.vue +23 -0
  229. package/components/data/KeyValue.d.ts +16 -0
  230. package/components/data/KeyValue.jsx +25 -0
  231. package/components/data/KeyValue.vue +26 -0
  232. package/components/data/ListRow.d.ts +14 -0
  233. package/components/data/ListRow.jsx +25 -0
  234. package/components/data/ListRow.vue +25 -0
  235. package/components/data/Pagination.d.ts +12 -0
  236. package/components/data/Pagination.jsx +27 -0
  237. package/components/data/Pagination.vue +24 -0
  238. package/components/data/SmartTable.d.ts +30 -0
  239. package/components/data/SmartTable.jsx +73 -0
  240. package/components/data/SmartTable.vue +74 -0
  241. package/components/data/Spreadsheet.d.ts +33 -0
  242. package/components/data/Spreadsheet.jsx +110 -0
  243. package/components/data/Spreadsheet.vue +95 -0
  244. package/components/data/_dir.card.html +13 -0
  245. package/components/data/data.prompt.md +33 -0
  246. package/components/display/Avatar.d.ts +21 -0
  247. package/components/display/Avatar.jsx +31 -0
  248. package/components/display/Avatar.vue +34 -0
  249. package/components/display/Badge.d.ts +27 -0
  250. package/components/display/Badge.jsx +40 -0
  251. package/components/display/Badge.vue +47 -0
  252. package/components/display/Card.d.ts +27 -0
  253. package/components/display/Card.jsx +27 -0
  254. package/components/display/Card.vue +28 -0
  255. package/components/display/Divider.d.ts +8 -0
  256. package/components/display/Divider.jsx +14 -0
  257. package/components/display/Divider.vue +13 -0
  258. package/components/display/Kbd.d.ts +7 -0
  259. package/components/display/Kbd.jsx +12 -0
  260. package/components/display/Kbd.vue +9 -0
  261. package/components/display/ProgressBar.d.ts +42 -0
  262. package/components/display/ProgressBar.jsx +70 -0
  263. package/components/display/ProgressBar.vue +69 -0
  264. package/components/display/Tag.d.ts +9 -0
  265. package/components/display/Tag.jsx +15 -0
  266. package/components/display/Tag.vue +15 -0
  267. package/components/display/_dir.card.html +13 -0
  268. package/components/display/display.prompt.md +30 -0
  269. package/components/feedback/Accordion.d.ts +13 -0
  270. package/components/feedback/Accordion.jsx +21 -0
  271. package/components/feedback/Accordion.vue +22 -0
  272. package/components/feedback/ActivityItem.d.ts +25 -0
  273. package/components/feedback/ActivityItem.jsx +39 -0
  274. package/components/feedback/ActivityItem.vue +46 -0
  275. package/components/feedback/Alert.d.ts +36 -0
  276. package/components/feedback/Alert.jsx +48 -0
  277. package/components/feedback/Alert.vue +59 -0
  278. package/components/feedback/Banner.d.ts +15 -0
  279. package/components/feedback/Banner.jsx +25 -0
  280. package/components/feedback/Banner.vue +25 -0
  281. package/components/feedback/ContextMenu.d.ts +8 -0
  282. package/components/feedback/ContextMenu.jsx +18 -0
  283. package/components/feedback/ContextMenu.vue +19 -0
  284. package/components/feedback/Drawer.d.ts +18 -0
  285. package/components/feedback/Drawer.jsx +26 -0
  286. package/components/feedback/Drawer.vue +24 -0
  287. package/components/feedback/Menu.d.ts +10 -0
  288. package/components/feedback/Menu.jsx +24 -0
  289. package/components/feedback/Menu.vue +21 -0
  290. package/components/feedback/Modal.d.ts +38 -0
  291. package/components/feedback/Modal.jsx +69 -0
  292. package/components/feedback/Modal.vue +65 -0
  293. package/components/feedback/Stepper.d.ts +9 -0
  294. package/components/feedback/Stepper.jsx +24 -0
  295. package/components/feedback/Stepper.vue +18 -0
  296. package/components/feedback/Timeline.d.ts +15 -0
  297. package/components/feedback/Timeline.jsx +27 -0
  298. package/components/feedback/Timeline.vue +21 -0
  299. package/components/feedback/_dir.card.html +13 -0
  300. package/components/feedback/feedback.prompt.md +35 -0
  301. package/components/forms/Checkbox.d.ts +13 -0
  302. package/components/forms/Checkbox.jsx +17 -0
  303. package/components/forms/Checkbox.vue +18 -0
  304. package/components/forms/Combobox.d.ts +13 -0
  305. package/components/forms/Combobox.jsx +34 -0
  306. package/components/forms/Combobox.vue +35 -0
  307. package/components/forms/DatePicker.d.ts +12 -0
  308. package/components/forms/DatePicker.jsx +48 -0
  309. package/components/forms/DatePicker.vue +46 -0
  310. package/components/forms/Dropzone.d.ts +20 -0
  311. package/components/forms/Dropzone.jsx +36 -0
  312. package/components/forms/Dropzone.vue +28 -0
  313. package/components/forms/Field.d.ts +13 -0
  314. package/components/forms/Field.jsx +13 -0
  315. package/components/forms/Field.vue +12 -0
  316. package/components/forms/Input.d.ts +18 -0
  317. package/components/forms/Input.jsx +35 -0
  318. package/components/forms/Input.vue +38 -0
  319. package/components/forms/MultiSelect.d.ts +14 -0
  320. package/components/forms/MultiSelect.jsx +33 -0
  321. package/components/forms/MultiSelect.vue +36 -0
  322. package/components/forms/NumberStepper.d.ts +13 -0
  323. package/components/forms/NumberStepper.jsx +15 -0
  324. package/components/forms/NumberStepper.vue +17 -0
  325. package/components/forms/Radio.vue +16 -0
  326. package/components/forms/RadioGroup.vue +11 -0
  327. package/components/forms/SearchInput.d.ts +12 -0
  328. package/components/forms/SearchInput.jsx +17 -0
  329. package/components/forms/SearchInput.vue +14 -0
  330. package/components/forms/Select.d.ts +14 -0
  331. package/components/forms/Select.jsx +17 -0
  332. package/components/forms/Select.vue +16 -0
  333. package/components/forms/Slider.d.ts +15 -0
  334. package/components/forms/Slider.jsx +21 -0
  335. package/components/forms/Slider.vue +23 -0
  336. package/components/forms/Switch.d.ts +31 -0
  337. package/components/forms/Switch.jsx +40 -0
  338. package/components/forms/Switch.vue +16 -0
  339. package/components/forms/_dir.card.html +13 -0
  340. package/components/forms/forms.prompt.md +23 -0
  341. package/components/icons/Icon.d.ts +19 -0
  342. package/components/icons/Icon.jsx +93 -0
  343. package/components/icons/Icon.prompt.md +8 -0
  344. package/components/icons/Icon.vue +12 -0
  345. package/components/icons/Unicon.d.ts +12 -0
  346. package/components/icons/Unicon.jsx +16 -0
  347. package/components/icons/Unicon.vue +15 -0
  348. package/components/icons/_dir.card.html +13 -0
  349. package/components/icons/feather-paths.js +2 -0
  350. package/components/icons/icon-paths.js +73 -0
  351. package/components/index.d.ts +87 -0
  352. package/components/index.js +110 -0
  353. package/components/navigation/Breadcrumb.d.ts +24 -0
  354. package/components/navigation/Breadcrumb.jsx +120 -0
  355. package/components/navigation/Breadcrumb.vue +94 -0
  356. package/components/navigation/DocTree.d.ts +18 -0
  357. package/components/navigation/DocTree.jsx +37 -0
  358. package/components/navigation/DocTree.vue +25 -0
  359. package/components/navigation/Sidebar.d.ts +29 -0
  360. package/components/navigation/Sidebar.jsx +75 -0
  361. package/components/navigation/Sidebar.vue +54 -0
  362. package/components/navigation/TopBar.d.ts +27 -0
  363. package/components/navigation/TopBar.jsx +44 -0
  364. package/components/navigation/TopBar.vue +51 -0
  365. package/components/navigation/Tree.d.ts +83 -0
  366. package/components/navigation/Tree.jsx +347 -0
  367. package/components/navigation/Tree.vue +258 -0
  368. package/components/navigation/_dir.card.html +13 -0
  369. package/components/navigation/navigation.prompt.md +22 -0
  370. package/components/pipeline/ConfigPanel.d.ts +18 -0
  371. package/components/pipeline/ConfigPanel.jsx +30 -0
  372. package/components/pipeline/ConfigPanel.vue +32 -0
  373. package/components/pipeline/DataPreview.d.ts +14 -0
  374. package/components/pipeline/DataPreview.jsx +24 -0
  375. package/components/pipeline/DataPreview.vue +21 -0
  376. package/components/pipeline/NodeCard.d.ts +47 -0
  377. package/components/pipeline/NodeCard.jsx +97 -0
  378. package/components/pipeline/NodeCard.vue +75 -0
  379. package/components/pipeline/PaletteItem.vue +15 -0
  380. package/components/pipeline/PaletteListItem.vue +15 -0
  381. package/components/pipeline/PalettePanel.d.ts +20 -0
  382. package/components/pipeline/PalettePanel.jsx +74 -0
  383. package/components/pipeline/PalettePanel.vue +45 -0
  384. package/components/pipeline/PipelineCanvas.d.ts +25 -0
  385. package/components/pipeline/PipelineCanvas.jsx +94 -0
  386. package/components/pipeline/PipelineCanvas.vue +73 -0
  387. package/components/pipeline/ZoomControls.d.ts +9 -0
  388. package/components/pipeline/ZoomControls.jsx +15 -0
  389. package/components/pipeline/ZoomControls.vue +15 -0
  390. package/components/pipeline/_dir.card.html +13 -0
  391. package/components/pipeline/pipeline.prompt.md +22 -0
  392. package/components/tabs/SegmentedControl.d.ts +11 -0
  393. package/components/tabs/SegmentedControl.jsx +21 -0
  394. package/components/tabs/SegmentedControl.vue +15 -0
  395. package/components/tabs/Tabs.d.ts +13 -0
  396. package/components/tabs/Tabs.jsx +52 -0
  397. package/components/tabs/Tabs.vue +39 -0
  398. package/components/tabs/ViewToggle.d.ts +8 -0
  399. package/components/tabs/ViewToggle.jsx +12 -0
  400. package/components/tabs/ViewToggle.vue +12 -0
  401. package/components/tabs/_dir.card.html +13 -0
  402. package/components/tabs/tabs.prompt.md +14 -0
  403. package/package.json +76 -0
  404. package/readme.md +150 -0
  405. package/styles.css +10 -0
  406. package/tokens/alpha.css +27 -0
  407. package/tokens/charts.css +55 -0
  408. package/tokens/colors-extended.css +40 -0
  409. package/tokens/colors.css +39 -0
  410. package/tokens/dark.css +96 -0
  411. package/tokens/fonts.css +4 -0
  412. package/tokens/interaction.css +48 -0
  413. package/tokens/layout.css +30 -0
  414. package/tokens/motion.css +22 -0
  415. package/tokens/tokens.d.ts +680 -0
  416. package/tokens/tokens.js +1018 -0
  417. package/tokens/tokens.json +2928 -0
  418. package/tokens/typography.css +22 -0
@@ -0,0 +1 @@
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"forms/Checkbox.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** 16px (sm: 13px) checkbox — 1.5px #d1d5db border, r4; checked = blue fill + white check; indeterminate = blue fill + dash; disabled = 50% opacity. */\nexport function Checkbox({ checked, indeterminate, onChange, label, size = \"md\", disabled, style }) {\n const box = size === \"sm\" ? 13 : 16;\n const filled = checked || indeterminate;\n return (\n <label style={{ display: \"inline-flex\", alignItems: \"center\", verticalAlign: \"middle\", lineHeight: \"var(--leading-sm)\", gap: 6, cursor: disabled ? \"not-allowed\" : \"pointer\", opacity: disabled ? .5 : 1, ...style }}\n onClick={() => !disabled && onChange && onChange(!checked)}>\n <span style={{ width: box, height: box, borderRadius: 4, boxSizing: \"border-box\", border: filled ? \"none\" : \"1.5px solid var(--checkbox-border)\", background: filled ? \"var(--action-primary)\" : \"var(--bg-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--on-accent)\", flexShrink: 0, transition: \"var(--t-hover)\" }}>\n {checked ? <Icon name=\"check\" size={box - 4} strokeWidth={3} /> : indeterminate ? <Icon name=\"minus\" size={box - 4} strokeWidth={3} /> : null}\n </span>\n {label ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{label}</span> : null}\n </label>\n );\n}\n","forms/Checkbox.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// 16px (sm: 13px) checkbox — 1.5px border, r4; checked = blue fill + white check; indeterminate = blue fill + dash; disabled = 50% opacity.\nconst props = defineProps({ checked: Boolean, indeterminate: Boolean, label: String, size: { type: String, default: 'md' }, disabled: Boolean, style: Object })\nconst emit = defineEmits(['change'])\nconst box = computed(() => props.size === 'sm' ? 13 : 16)\nconst filled = computed(() => props.checked || props.indeterminate)\n</script>\n<template>\n <label :style=\"{ display: 'inline-flex', alignItems: 'center', verticalAlign: 'middle', lineHeight: 'var(--leading-sm)', gap: 6, cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? .5 : 1, ...style }\" @click=\"!disabled && emit('change', !checked)\">\n <span :style=\"{ width: box, height: box, borderRadius: 4, boxSizing: 'border-box', border: filled ? 'none' : '1.5px solid var(--checkbox-border)', background: filled ? 'var(--action-primary)' : 'var(--bg-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--on-accent)', flexShrink: 0, transition: 'var(--t-hover)' }\">\n <Icon v-if=\"checked\" name=\"check\" :size=\"box - 4\" :stroke-width=\"3\" />\n <Icon v-else-if=\"indeterminate\" name=\"minus\" :size=\"box - 4\" :stroke-width=\"3\" />\n </span>\n <span v-if=\"label\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\">{{ label }}</span>\n </label>\n</template>\n","forms/Combobox.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { inputBase } from \"./Input.jsx\";\n\n/** Typeahead single-select: type to filter options, click or Enter to pick. Chevron toggles the list; shows \"No results\" when the filter matches nothing. */\nexport function Combobox({ options = [], value = \"\", placeholder = \"Select...\", disabled, error, width, onChange, style }) {\n const [open, setOpen] = React.useState(false);\n const [query, setQuery] = React.useState(null);\n const [hov, setHov] = React.useState(-1);\n const root = React.useRef(null);\n React.useEffect(() => { const h = (e) => { if (root.current && !root.current.contains(e.target)) { setOpen(false); setQuery(null); } }; document.addEventListener(\"mousedown\", h); return () => document.removeEventListener(\"mousedown\", h); }, []);\n const shown = query == null ? options : options.filter(o => o.toLowerCase().includes(query.toLowerCase()));\n const pick = (o) => { onChange && onChange(o); setOpen(false); setQuery(null); };\n return (\n <div ref={root} style={{ position: \"relative\", width, ...style }}>\n <div style={{ position: \"relative\" }}>\n <input value={query != null ? query : value} placeholder={placeholder} disabled={disabled}\n onFocus={() => setOpen(true)} onChange={(e) => { setQuery(e.target.value); setOpen(true); setHov(0); }}\n onKeyDown={(e) => { if (e.key === \"ArrowDown\") setHov(Math.min(hov + 1, shown.length - 1)); if (e.key === \"ArrowUp\") setHov(Math.max(hov - 1, 0)); if (e.key === \"Enter\" && shown[hov]) pick(shown[hov]); if (e.key === \"Escape\") { setOpen(false); setQuery(null); } }}\n style={{ ...inputBase, paddingRight: 32, borderColor: error ? \"var(--danger)\" : open ? \"var(--focus-border)\" : \"var(--border-control)\", boxShadow: open ? \"0 0 0 2px var(--focus-ring)\" : \"none\", opacity: disabled ? .5 : 1, cursor: disabled ? \"not-allowed\" : \"text\" }} />\n <button type=\"button\" disabled={disabled} onClick={() => { setOpen(!open); setQuery(null); }} style={{ position: \"absolute\", right: 4, top: \"50%\", transform: \"translateY(-50%)\", width: 26, height: 26, border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"var(--text-muted)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0 }}>\n <span style={{ display: \"inline-flex\", transform: open ? \"rotate(180deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-down\" size={14} /></span>\n </button>\n </div>\n {open && !disabled && <div style={{ position: \"absolute\", top: \"calc(100% + 4px)\", left: 0, right: 0, zIndex: \"var(--z-menu)\", background: \"var(--bg-surface)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-md)\", boxShadow: \"var(--shadow-menu)\", padding: 4, maxHeight: 208, overflowY: \"auto\" }}>\n {shown.length === 0 && <div style={{ padding: \"8px 10px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-muted)\" }}>No results</div>}\n {shown.map((o, i) => <button key={o} type=\"button\" onClick={() => pick(o)} onMouseEnter={() => setHov(i)}\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", width: \"100%\", padding: \"7px 10px\", borderRadius: \"var(--radius-sm)\", border: \"none\", cursor: \"pointer\", background: hov === i ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-heading)\", textAlign: \"left\" }}>\n {o}{o === value && <span style={{ color: \"var(--action-primary)\", display: \"inline-flex\" }}><Icon name=\"check\" size={13} /></span>}\n </button>)}\n </div>}\n </div>\n );\n}\n","forms/Combobox.vue":"<script setup>\nimport { ref, computed, onMounted, onUnmounted } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport { inputBase } from './Input.vue'\n// Typeahead single-select: type to filter options, click or Enter to pick; \"No results\" empty row.\nconst props = defineProps({ options: { type: Array, default: () => [] }, modelValue: { type: String, default: '' }, placeholder: { type: String, default: 'Select...' }, disabled: Boolean, error: Boolean, width: [Number, String], style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst open = ref(false), query = ref(null), hov = ref(-1), root = ref(null)\nconst shown = computed(() => query.value == null ? props.options : props.options.filter(o => o.toLowerCase().includes(query.value.toLowerCase())))\nfunction pick(o) { emit('update:modelValue', o); open.value = false; query.value = null }\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) { open.value = false; query.value = null } }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', width: typeof width === 'number' ? width + 'px' : width, ...style }\">\n <div style=\"position:relative\">\n <input :value=\"query != null ? query : modelValue\" :placeholder=\"placeholder\" :disabled=\"disabled\"\n @focus=\"open = true\" @input=\"query = $event.target.value; open = true; hov = 0\"\n @keydown.down=\"hov = Math.min(hov + 1, shown.length - 1)\" @keydown.up=\"hov = Math.max(hov - 1, 0)\"\n @keydown.enter=\"shown[hov] && pick(shown[hov])\" @keydown.esc=\"open = false; query = null\"\n :style=\"{ ...inputBase, paddingRight: '32px', borderColor: error ? 'var(--danger)' : open ? 'var(--focus-border)' : 'var(--border-control)', boxShadow: open ? '0 0 0 2px var(--focus-ring)' : 'none', opacity: disabled ? .5 : 1 }\" />\n <button type=\"button\" :disabled=\"disabled\" @click=\"open = !open; query = null\" style=\"position:absolute;right:4px;top:50%;transform:translateY(-50%);width:26px;height:26px;border:none;background:transparent;cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center;padding:0\">\n <span :style=\"{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-down\" :size=\"14\" /></span>\n </button>\n </div>\n <div v-if=\"open && !disabled\" style=\"position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:var(--z-menu);background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;max-height:208px;overflow-y:auto\">\n <div v-if=\"shown.length === 0\" style=\"padding:8px 10px;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-muted)\">No results</div>\n <button v-for=\"(o, i) in shown\" :key=\"o\" type=\"button\" @click=\"pick(o)\" @mouseenter=\"hov = i\"\n :style=\"{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', padding: '7px 10px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: 'pointer', background: hov === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', color: 'var(--text-heading)', textAlign: 'left' }\">\n {{ o }}<span v-if=\"o === modelValue\" style=\"color:var(--action-primary);display:inline-flex\"><Icon name=\"check\" :size=\"13\" /></span>\n </button>\n </div>\n </div>\n</template>\n","forms/DatePicker.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\nimport { inputBase } from \"./Input.jsx\";\n\nconst MONTHS = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\nconst pad = (n) => String(n).padStart(2, \"0\");\nconst iso = (d) => d.getFullYear() + \"-\" + pad(d.getMonth() + 1) + \"-\" + pad(d.getDate());\n\n/** Date picker: input with calendar glyph opening a month-grid popover — chevron month nav, today outlined, selected day filled blue. value/onChange use \"YYYY-MM-DD\". */\nexport function DatePicker({ value = \"\", placeholder = \"Select date\", disabled, error, width = 180, onChange, style }) {\n const [open, setOpen] = React.useState(false);\n const sel = value ? new Date(value + \"T00:00:00\") : null;\n const [view, setView] = React.useState(() => { const d = sel || new Date(); return { y: d.getFullYear(), m: d.getMonth() }; });\n const root = React.useRef(null);\n React.useEffect(() => { const h = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); }; document.addEventListener(\"mousedown\", h); return () => document.removeEventListener(\"mousedown\", h); }, []);\n const first = new Date(view.y, view.m, 1);\n const start = (first.getDay() + 6) % 7; // Monday-first\n const days = new Date(view.y, view.m + 1, 0).getDate();\n const todayIso = iso(new Date());\n const nav = (d) => { let m = view.m + d, y = view.y; if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; } setView({ y, m }); };\n const label = sel ? MONTHS[sel.getMonth()].slice(0, 3) + \" \" + sel.getDate() + \", \" + sel.getFullYear() : \"\";\n return (\n <div ref={root} style={{ position: \"relative\", width, ...style }}>\n <div onClick={() => !disabled && setOpen(!open)} style={{ ...inputBase, display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", cursor: disabled ? \"not-allowed\" : \"pointer\", borderColor: error ? \"var(--danger)\" : open ? \"var(--focus-border)\" : \"var(--border-control)\", boxShadow: open ? \"0 0 0 2px var(--focus-ring)\" : \"none\", opacity: disabled ? .5 : 1, color: label ? \"var(--text-primary)\" : \"var(--text-muted)\" }}>\n {label || placeholder}<span style={{ color: \"var(--text-muted)\", display: \"inline-flex\" }}><Icon name=\"calendar\" size={14} /></span>\n </div>\n {open && <div style={{ position: \"absolute\", top: \"calc(100% + 4px)\", left: 0, zIndex: \"var(--z-menu)\", width: 252, background: \"var(--bg-surface)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", boxShadow: \"var(--shadow-menu)\", padding: 12, boxSizing: \"border-box\" }}>\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", paddingBottom: 8 }}>\n <IconButton icon=\"chevron-left\" size=\"sm\" label=\"Previous month\" onClick={() => nav(-1)} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", color: \"var(--text-heading)\" }}>{MONTHS[view.m]} {view.y}</span>\n <IconButton icon=\"chevron-right\" size=\"sm\" label=\"Next month\" onClick={() => nav(1)} />\n </div>\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(7,1fr)\", gap: 2 }}>\n {[\"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"].map(d => <span key={d} style={{ textAlign: \"center\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-2xs)\", letterSpacing: \"var(--tracking-2xs)\", color: \"var(--text-muted)\", padding: \"2px 0\" }}>{d}</span>)}\n {Array.from({ length: start }, (_, i) => <span key={\"e\" + i}></span>)}\n {Array.from({ length: days }, (_, i) => {\n const dIso = view.y + \"-\" + pad(view.m + 1) + \"-\" + pad(i + 1);\n const isSel = value === dIso, isToday = todayIso === dIso;\n return <button key={i} type=\"button\" onClick={() => { onChange && onChange(dIso); setOpen(false); }}\n style={{ height: 28, borderRadius: \"var(--radius-sm)\", border: isToday && !isSel ? \"1px solid var(--action-primary)\" : \"1px solid transparent\", cursor: \"pointer\", background: isSel ? \"var(--action-primary)\" : \"transparent\", color: isSel ? \"var(--on-accent)\" : \"var(--text-body)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", padding: 0, boxSizing: \"border-box\" }}\n onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = \"var(--bg-hover)\"; }} onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = \"transparent\"; }}>{i + 1}</button>;\n })}\n </div>\n </div>}\n </div>\n );\n}\n","forms/DatePicker.vue":"<script setup>\nimport { ref, computed, onMounted, onUnmounted } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nimport { inputBase } from './Input.vue'\n// Date picker: input with calendar glyph opening a month-grid popover — Monday-first, today outlined, selected filled. v-model is \"YYYY-MM-DD\".\nconst MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']\nconst pad = (n) => String(n).padStart(2, '0')\nconst props = defineProps({ modelValue: { type: String, default: '' }, placeholder: { type: String, default: 'Select date' }, disabled: Boolean, error: Boolean, width: { type: [Number, String], default: 180 }, style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst open = ref(false), root = ref(null)\nconst sel = computed(() => props.modelValue ? new Date(props.modelValue + 'T00:00:00') : null)\nconst d0 = sel.value || new Date()\nconst view = ref({ y: d0.getFullYear(), m: d0.getMonth() })\nconst start = computed(() => (new Date(view.value.y, view.value.m, 1).getDay() + 6) % 7)\nconst days = computed(() => new Date(view.value.y, view.value.m + 1, 0).getDate())\nconst todayIso = (() => { const d = new Date(); return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) })()\nconst label = computed(() => sel.value ? MONTHS[sel.value.getMonth()].slice(0, 3) + ' ' + sel.value.getDate() + ', ' + sel.value.getFullYear() : '')\nfunction nav(d) { let m = view.value.m + d, y = view.value.y; if (m < 0) { m = 11; y-- } if (m > 11) { m = 0; y++ } view.value = { y, m } }\nfunction dayIso(i) { return view.value.y + '-' + pad(view.value.m + 1) + '-' + pad(i) }\nfunction pick(i) { emit('update:modelValue', dayIso(i)); open.value = false }\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', width: typeof width === 'number' ? width + 'px' : width, ...style }\">\n <div @click=\"!disabled && (open = !open)\" :style=\"{ ...inputBase, display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: disabled ? 'not-allowed' : 'pointer', borderColor: error ? 'var(--danger)' : open ? 'var(--focus-border)' : 'var(--border-control)', boxShadow: open ? '0 0 0 2px var(--focus-ring)' : 'none', opacity: disabled ? .5 : 1, color: label ? 'var(--text-primary)' : 'var(--text-muted)' }\">\n {{ label || placeholder }}<span style=\"color:var(--text-muted);display:inline-flex\"><Icon name=\"calendar\" :size=\"14\" /></span>\n </div>\n <div v-if=\"open\" style=\"position:absolute;top:calc(100% + 4px);left:0;z-index:var(--z-menu);width:252px;background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-lg);box-shadow:var(--shadow-menu);padding:12px;box-sizing:border-box\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;padding-bottom:8px\">\n <IconButton icon=\"chevron-left\" size=\"sm\" label=\"Previous month\" @click=\"nav(-1)\" />\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);color:var(--text-heading)\">{{ MONTHS[view.m] }} {{ view.y }}</span>\n <IconButton icon=\"chevron-right\" size=\"sm\" label=\"Next month\" @click=\"nav(1)\" />\n </div>\n <div style=\"display:grid;grid-template-columns:repeat(7,1fr);gap:2px\">\n <span v-for=\"d in ['Mo','Tu','We','Th','Fr','Sa','Su']\" :key=\"d\" style=\"text-align:center;font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding:2px 0\">{{ d }}</span>\n <span v-for=\"i in start\" :key=\"'e' + i\" />\n <button v-for=\"i in days\" :key=\"i\" type=\"button\" @click=\"pick(i)\" class=\"kb-dp-day\"\n :style=\"{ height: '28px', borderRadius: 'var(--radius-sm)', border: todayIso === dayIso(i) && modelValue !== dayIso(i) ? '1px solid var(--action-primary)' : '1px solid transparent', cursor: 'pointer', background: modelValue === dayIso(i) ? 'var(--action-primary)' : 'transparent', color: modelValue === dayIso(i) ? 'var(--on-accent)' : 'var(--text-body)', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', padding: 0, boxSizing: 'border-box' }\">{{ i }}</button>\n </div>\n </div>\n </div>\n</template>\n<style scoped>.kb-dp-day:hover{background:var(--bg-hover)!important}</style>\n","forms/Dropzone.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst toneColor = { default: \"var(--dropzone-border)\", hover: \"var(--dropzone-border-hover)\", error: \"var(--danger)\", drag: \"var(--focus-border)\" };\n\n/** Dashed upload dropzone (2px, r10). States: default/hover, real drag-and-drop (blue border + tint while a file is dragged over), error, disabled, and a filled \"uploaded\" state showing the file name + remove. */\nexport function Dropzone({ title = \"Click to upload or drag and drop\", hint = \"JSON files only\", height = 185.5, error, disabled, dragOver, file, onRemove, onClick, onDropFiles, style }) {\n const [hover, setHover] = React.useState(false);\n const [dragging, setDragging] = React.useState(false);\n const dragActive = dragOver || dragging;\n if (file) {\n return (\n <div style={{ height, borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-control)\", background: \"var(--bg-inset)\", display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 8, boxSizing: \"border-box\", ...style }}>\n <span style={{ color: \"var(--success-strong)\" }}><Icon name=\"file-check\" size={22} strokeWidth={1.5} /></span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", color: \"var(--text-heading)\" }}>{file}</span>\n <button type=\"button\" onClick={onRemove} style={{ display: \"flex\", alignItems: \"center\", gap: 4, border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"var(--danger)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", padding: 0 }}>\n <Icon name=\"trash-2\" size={13} />Remove\n </button>\n </div>\n );\n }\n const tone = disabled ? \"default\" : error ? \"error\" : dragActive ? \"drag\" : hover ? \"hover\" : \"default\";\n const stop = (e) => { e.preventDefault(); e.stopPropagation(); };\n return (\n <div onClick={disabled ? undefined : onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n onDragEnter={disabled ? undefined : (e) => { stop(e); setDragging(true); }}\n onDragOver={disabled ? undefined : stop}\n onDragLeave={disabled ? undefined : (e) => { stop(e); setDragging(false); }}\n onDrop={disabled ? undefined : (e) => { stop(e); setDragging(false); onDropFiles && onDropFiles(e.dataTransfer.files); }}\n style={{ height, borderRadius: \"var(--radius-lg)\", border: `1px dashed ${toneColor[tone]}`, background: error ? \"var(--danger-bg)\" : dragActive ? \"var(--soft-bg)\" : \"transparent\", display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 4, cursor: disabled ? \"not-allowed\" : \"pointer\", boxSizing: \"border-box\", opacity: disabled ? .5 : 1, transition: \"var(--t-hover)\", ...style }}>\n <span style={{ color: error ? \"var(--danger)\" : dragActive ? \"var(--action-primary)\" : \"var(--text-muted)\", marginBottom: 6 }}><Icon name={error ? \"triangle-alert\" : \"upload\"} size={22} strokeWidth={1.5} /></span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: error ? \"var(--danger-text)\" : dragActive ? \"var(--action-primary)\" : \"var(--text-heading)\" }}>{error || (dragActive ? \"Drop file to upload\" : title)}</span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: dragActive ? \"var(--action-primary)\" : \"var(--text-secondary)\" }}>{hint}</span>\n </div>\n );\n}\n","forms/Dropzone.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst toneColor = { default: 'var(--dropzone-border)', hover: 'var(--dropzone-border-hover)', error: 'var(--danger)', drag: 'var(--focus-border)' }\n// Dashed upload dropzone (2px, r10). States: default/hover, real drag-and-drop (blue border + tint while a file is dragged over), error, disabled, and a filled \"uploaded\" state.\nconst props = defineProps({ title: { type: String, default: 'Click to upload or drag and drop' }, hint: { type: String, default: 'JSON files only' }, height: { type: [Number, String], default: 185.5 }, error: String, disabled: Boolean, dragOver: Boolean, file: String, style: Object })\nconst emit = defineEmits(['remove', 'click', 'dropFiles'])\nconst hover = ref(false), dragging = ref(false)\nconst dragActive = computed(() => props.dragOver || dragging.value)\nconst tone = computed(() => props.disabled ? 'default' : props.error ? 'error' : dragActive.value ? 'drag' : hover.value ? 'hover' : 'default')\nfunction stop(e) { e.preventDefault(); e.stopPropagation() }\nfunction onDrop(e) { stop(e); dragging.value = false; emit('dropFiles', e.dataTransfer.files) }\n</script>\n<template>\n <div v-if=\"file\" :style=\"{ height, borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-control)', background: 'var(--bg-inset)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, boxSizing: 'border-box', ...style }\">\n <span style=\"color:var(--success-strong)\"><Icon name=\"file-check\" :size=\"22\" :stroke-width=\"1.5\" /></span>\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)\">{{ file }}</span>\n <button type=\"button\" @click=\"$emit('remove')\" style=\"display:flex;align-items:center;gap:4px;border:none;background:transparent;cursor:pointer;color:var(--danger);font-family:var(--font-sans);font-size:var(--text-sm);padding:0\"><Icon name=\"trash-2\" :size=\"13\" />Remove</button>\n </div>\n <div v-else @click=\"!disabled && $emit('click')\" @mouseenter=\"hover = true\" @mouseleave=\"hover = false\"\n @dragenter=\"!disabled && ((e) => { stop(e); dragging = true })($event)\" @dragover=\"!disabled && stop($event)\"\n @dragleave=\"!disabled && ((e) => { stop(e); dragging = false })($event)\" @drop=\"!disabled && onDrop($event)\"\n :style=\"{ height, borderRadius: 'var(--radius-lg)', border: `1px dashed ${toneColor[tone]}`, background: error ? 'var(--danger-bg)' : dragActive ? 'var(--soft-bg)' : 'transparent', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4, cursor: disabled ? 'not-allowed' : 'pointer', boxSizing: 'border-box', opacity: disabled ? .5 : 1, transition: 'var(--t-hover)', ...style }\">\n <span :style=\"{ color: error ? 'var(--danger)' : dragActive ? 'var(--action-primary)' : 'var(--text-muted)', marginBottom: 6 }\"><Icon :name=\"error ? 'triangle-alert' : 'upload'\" :size=\"22\" :stroke-width=\"1.5\" /></span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: error ? 'var(--danger-text)' : dragActive ? 'var(--action-primary)' : 'var(--text-heading)' }\">{{ error || (dragActive ? 'Drop file to upload' : title) }}</span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: dragActive ? 'var(--action-primary)' : 'var(--text-secondary)' }\">{{ hint }}</span>\n </div>\n</template>\n","forms/Field.jsx":"import React from \"react\";\n\n/** Form field wrapper: 12px/500 label (red + * when required) above the control, optional hint/error below. */\nexport function Field({ label, required, hint, error, children, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n <label style={{ paddingBottom: 6, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{label}{required ? <span style={{ color: \"var(--danger)\" }}> *</span> : null}</label>\n {children}\n {error ? <span style={{ paddingTop: 5, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--danger)\" }}>{error}</span>\n : hint ? <span style={{ paddingTop: 5, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-muted)\" }}>{hint}</span> : null}\n </div>\n );\n}\n","forms/Field.vue":"<script setup>\n// Form field wrapper: 12px/500 label (red + * when required) above the control, optional hint/error below.\ndefineProps({ label: String, required: Boolean, hint: String, error: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <label style=\"padding-bottom:6px;font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\">{{ label }}<span v-if=\"required\" style=\"color:var(--danger)\"> *</span></label>\n <slot />\n <span v-if=\"error\" style=\"padding-top:5px;font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--danger)\">{{ error }}</span>\n <span v-else-if=\"hint\" style=\"padding-top:5px;font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted)\">{{ hint }}</span>\n </div>\n</template>\n","forms/Input.jsx":"import React from \"react\";\n\nexport const inputBase = { height: \"var(--h-input)\", borderRadius: \"var(--radius-md)\", border: \"1px solid var(--border-control)\", background: \"var(--bg-surface)\", padding: \"8px 12px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-primary)\", outline: \"none\", boxSizing: \"border-box\", width: \"100%\" };\n\n/** Text input, 36px r8. readOnly = #fafafa inset; disabled = 50% opacity; error = red border + ring. */\nexport function Input({ readOnly, disabled, error, style, ...rest }) {\n const [focus, setFocus] = React.useState(false);\n return <input readOnly={readOnly} disabled={disabled} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}\n style={{ ...inputBase, ...(readOnly ? { background: \"var(--bg-inset)\", color: \"var(--text-secondary)\" } : null),\n ...(error ? { borderColor: \"var(--danger)\" } : null),\n ...(focus && !readOnly && !disabled ? { borderColor: error ? \"var(--danger)\" : \"var(--focus-border)\", boxShadow: `0 0 0 2px ${error ? \"var(--danger-bg)\" : \"var(--focus-ring)\"}` } : null),\n ...(disabled ? { opacity: .5, cursor: \"not-allowed\", background: \"var(--bg-inset)\" } : null), ...style }} {...rest} />;\n}\n\n/** Multiline textarea, r8, default 90px tall. showCount + maxLength renders a bottom-right \"n/max\" counter (red past the limit). */\nexport function Textarea({ style, rows = 4, error, disabled, showCount, maxLength, value, defaultValue, onChange, ...rest }) {\n const [focus, setFocus] = React.useState(false);\n const [inner, setInner] = React.useState(defaultValue || \"\");\n const val = value != null ? value : inner;\n const len = (val || \"\").length;\n const over = maxLength != null && len > maxLength;\n return (\n <div style={{ position: \"relative\" }}>\n <textarea rows={rows} disabled={disabled} maxLength={showCount ? undefined : maxLength}\n value={value} defaultValue={value == null ? defaultValue : undefined}\n onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}\n onChange={(e) => { if (value == null) setInner(e.target.value); onChange && onChange(e); }}\n style={{ ...inputBase, height: 90, resize: \"vertical\", paddingBottom: showCount ? 22 : 8,\n ...(error || over ? { borderColor: \"var(--danger)\" } : null),\n ...(focus && !disabled ? { borderColor: error || over ? \"var(--danger)\" : \"var(--focus-border)\", boxShadow: `0 0 0 2px ${error || over ? \"var(--danger-bg)\" : \"var(--focus-ring)\"}` } : null),\n ...(disabled ? { opacity: .5, cursor: \"not-allowed\", background: \"var(--bg-inset)\" } : null), ...style }} {...rest} />\n {showCount ? <span style={{ position: \"absolute\", right: 10, bottom: 7, fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: over ? \"var(--danger)\" : \"var(--text-muted)\" }}>{len}{maxLength != null ? `/${maxLength}` : \"\"}</span> : null}\n </div>\n );\n}\n","forms/Input.vue":"<script setup>\nimport { ref, computed } from 'vue'\nexport const inputBase = { height: 'var(--h-input)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-control)', background: 'var(--bg-surface)', padding: '8px 12px', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', width: '100%' }\n// Text input, 36px r8. v-model. readonly = inset fill; disabled = 50% opacity; error = red border + ring.\nconst props = defineProps({ modelValue: String, readonly: Boolean, disabled: Boolean, error: Boolean, style: Object })\ndefineEmits(['update:modelValue'])\nconst focus = ref(false)\nconst s = computed(() => ({ ...inputBase, ...(props.readonly ? { background: 'var(--bg-inset)', color: 'var(--text-secondary)' } : null),\n ...(props.error ? { borderColor: 'var(--danger)' } : null),\n ...(focus.value && !props.readonly && !props.disabled ? { borderColor: props.error ? 'var(--danger)' : 'var(--focus-border)', boxShadow: `0 0 0 2px ${props.error ? 'var(--danger-bg)' : 'var(--focus-ring)'}` } : null),\n ...(props.disabled ? { opacity: .5, cursor: 'not-allowed', background: 'var(--bg-inset)' } : null), ...props.style }))\n</script>\n<template>\n <input :value=\"modelValue\" @input=\"$emit('update:modelValue', $event.target.value)\" :readonly=\"readonly\" :disabled=\"disabled\" @focus=\"focus = true\" @blur=\"focus = false\" :style=\"s\" />\n</template>\n\n<!-- Textarea — multiline textarea, r8, default 90px tall. showCount + maxLength renders a bottom-right \"n/max\" counter (red past the limit). -->\n<!--\n<script setup name=\"Textarea\">\nimport { ref, computed } from 'vue'\nimport { inputBase } from './Input.vue'\nconst props = defineProps({ modelValue: String, rows: { type: Number, default: 4 }, error: Boolean, disabled: Boolean, showCount: Boolean, maxLength: Number, style: Object })\ndefineEmits(['update:modelValue'])\nconst focus = ref(false)\nconst len = computed(() => (props.modelValue || '').length)\nconst over = computed(() => props.maxLength != null && len.value > props.maxLength)\nconst s = computed(() => ({ ...inputBase, height: 90, resize: 'vertical', paddingBottom: props.showCount ? 22 : 8,\n ...((props.error || over.value) ? { borderColor: 'var(--danger)' } : null),\n ...(focus.value && !props.disabled ? { borderColor: (props.error || over.value) ? 'var(--danger)' : 'var(--focus-border)', boxShadow: `0 0 0 2px ${(props.error || over.value) ? 'var(--danger-bg)' : 'var(--focus-ring)'}` } : null),\n ...(props.disabled ? { opacity: .5, cursor: 'not-allowed', background: 'var(--bg-inset)' } : null), ...props.style }))\n</script>\n<template>\n <div style=\"position:relative\">\n <textarea :rows=\"rows\" :disabled=\"disabled\" :maxlength=\"showCount ? undefined : maxLength\" :value=\"modelValue\" @input=\"$emit('update:modelValue', $event.target.value)\" @focus=\"focus = true\" @blur=\"focus = false\" :style=\"s\" />\n <span v-if=\"showCount\" :style=\"{ position: 'absolute', right: 10, bottom: 7, fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', color: over ? 'var(--danger)' : 'var(--text-muted)' }\">{{ len }}{{ maxLength != null ? `/${maxLength}` : '' }}</span>\n </div>\n</template>\n-->\n","forms/MultiSelect.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { Checkbox } from \"./Checkbox.jsx\";\nimport { Tag } from \"../display/Tag.jsx\";\n\n/** Multi-select dropdown: checkbox option list, selected values render as removable tags in the field (overflow collapses to \"+n\"), clear-all ×. */\nexport function MultiSelect({ options = [], value = [], placeholder = \"Select...\", maxTags = 2, disabled, width, onChange, style }) {\n const [open, setOpen] = React.useState(false);\n const root = React.useRef(null);\n React.useEffect(() => { const h = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); }; document.addEventListener(\"mousedown\", h); return () => document.removeEventListener(\"mousedown\", h); }, []);\n const toggle = (o) => { const next = value.includes(o) ? value.filter(x => x !== o) : [...value, o]; onChange && onChange(next); };\n const shown = value.slice(0, maxTags), extra = value.length - shown.length;\n return (\n <div ref={root} style={{ position: \"relative\", width, ...style }}>\n <div onClick={() => !disabled && setOpen(!open)} style={{ minHeight: \"var(--h-input)\", borderRadius: \"var(--radius-md)\", border: `1px solid ${open ? \"var(--focus-border)\" : \"var(--border-control)\"}`, boxShadow: open ? \"0 0 0 2px var(--focus-ring)\" : \"none\", background: \"var(--bg-surface)\", padding: \"5px 30px 5px 8px\", boxSizing: \"border-box\", display: \"flex\", alignItems: \"center\", flexWrap: \"wrap\", gap: 4, cursor: disabled ? \"not-allowed\" : \"pointer\", opacity: disabled ? .5 : 1, position: \"relative\" }}>\n {value.length === 0 && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-muted)\", paddingLeft: 4 }}>{placeholder}</span>}\n {shown.map(v => <Tag key={v} removable onRemove={() => toggle(v)}>{v}</Tag>)}\n {extra > 0 && <Tag>+{extra}</Tag>}\n <span style={{ position: \"absolute\", right: 8, top: \"50%\", transform: \"translateY(-50%)\", display: \"flex\", alignItems: \"center\", gap: 2, color: \"var(--text-muted)\" }}>\n {value.length > 0 && <button type=\"button\" onClick={(e) => { e.stopPropagation(); onChange && onChange([]); }} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", display: \"flex\", padding: 0 }}><Icon name=\"x\" size={13} /></button>}\n <span style={{ display: \"inline-flex\", transform: open ? \"rotate(180deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-down\" size={14} /></span>\n </span>\n </div>\n {open && <div style={{ position: \"absolute\", top: \"calc(100% + 4px)\", left: 0, right: 0, zIndex: \"var(--z-menu)\", background: \"var(--bg-surface)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-md)\", boxShadow: \"var(--shadow-menu)\", padding: 4, maxHeight: 208, overflowY: \"auto\" }}>\n {options.map(o => <div key={o} role=\"option\" aria-selected={value.includes(o)} tabIndex={0} onClick={() => toggle(o)} style={{ display: \"flex\", alignItems: \"center\", gap: 8, padding: \"7px 10px\", borderRadius: \"var(--radius-sm)\", cursor: \"pointer\", background: \"transparent\" }}\n onMouseEnter={(e) => e.currentTarget.style.background = \"var(--bg-hover)\"} onMouseLeave={(e) => e.currentTarget.style.background = \"transparent\"}>\n <Checkbox size=\"sm\" checked={value.includes(o)} onChange={() => toggle(o)} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-heading)\" }}>{o}</span>\n </div>)}\n </div>}\n </div>\n );\n}\n","forms/MultiSelect.vue":"<script setup>\nimport { ref, computed, onMounted, onUnmounted } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport Checkbox from './Checkbox.vue'\nimport Tag from '../display/Tag.vue'\n// Multi-select dropdown: checkbox options; picks render as removable tags with \"+n\" overflow and clear-all ×.\nconst props = defineProps({ options: { type: Array, default: () => [] }, modelValue: { type: Array, default: () => [] }, placeholder: { type: String, default: 'Select...' }, maxTags: { type: Number, default: 2 }, disabled: Boolean, width: [Number, String], style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst open = ref(false), root = ref(null)\nfunction toggle(o) { emit('update:modelValue', props.modelValue.includes(o) ? props.modelValue.filter(x => x !== o) : [...props.modelValue, o]) }\nconst shown = computed(() => props.modelValue.slice(0, props.maxTags))\nconst extra = computed(() => props.modelValue.length - shown.value.length)\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', width: typeof width === 'number' ? width + 'px' : width, ...style }\">\n <div @click=\"!disabled && (open = !open)\" :style=\"{ minHeight: 'var(--h-input)', borderRadius: 'var(--radius-md)', border: `1px solid ${open ? 'var(--focus-border)' : 'var(--border-control)'}`, boxShadow: open ? '0 0 0 2px var(--focus-ring)' : 'none', background: 'var(--bg-surface)', padding: '5px 30px 5px 8px', boxSizing: 'border-box', display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '4px', cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? .5 : 1, position: 'relative' }\">\n <span v-if=\"modelValue.length === 0\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-muted);padding-left:4px\">{{ placeholder }}</span>\n <Tag v-for=\"v in shown\" :key=\"v\" removable @remove=\"toggle(v)\">{{ v }}</Tag>\n <Tag v-if=\"extra > 0\">+{{ extra }}</Tag>\n <span style=\"position:absolute;right:8px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:2px;color:var(--text-muted)\">\n <button v-if=\"modelValue.length\" type=\"button\" @click.stop=\"emit('update:modelValue', [])\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;display:flex;padding:0\"><Icon name=\"x\" :size=\"13\" /></button>\n <span :style=\"{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-down\" :size=\"14\" /></span>\n </span>\n </div>\n <div v-if=\"open\" style=\"position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:var(--z-menu);background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;max-height:208px;overflow-y:auto\">\n <div v-for=\"o in options\" :key=\"o\" role=\"option\" :aria-selected=\"modelValue.includes(o)\" tabindex=\"0\" @click=\"toggle(o)\" class=\"kb-ms-row\" style=\"display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:var(--radius-sm);cursor:pointer\">\n <Checkbox size=\"sm\" :checked=\"modelValue.includes(o)\" @change=\"toggle(o)\" />\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-heading)\">{{ o }}</span>\n </div>\n </div>\n </div>\n</template>\n<style scoped>.kb-ms-row:hover{background:var(--bg-hover)}</style>\n","forms/NumberStepper.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Segmented − / value / + stepper, 25.5px tall, r4 (\"Preview rows\"). Buttons disable themselves at min/max. */\nexport function NumberStepper({ value = 100, onChange, step = 10, min = 0, max, disabled, style }) {\n const atMin = value <= min, atMax = max != null && value >= max;\n const seg = (dis) => ({ width: 30, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", cursor: dis ? \"not-allowed\" : \"pointer\", color: dis ? \"var(--text-muted)\" : \"var(--text-body)\", alignSelf: \"stretch\", background: \"transparent\", border: \"none\", padding: 0 });\n return (\n <div style={{ display: \"inline-flex\", alignItems: \"center\", height: 25.5, borderRadius: \"var(--radius-sm)\", border: \"1px solid var(--border-control)\", overflow: \"hidden\", background: \"var(--bg-surface)\", boxSizing: \"border-box\", opacity: disabled ? .5 : 1, ...style }}>\n <button type=\"button\" disabled={disabled || atMin} style={seg(disabled || atMin)} onClick={() => onChange && onChange(Math.max(min, value - step))}><Icon name=\"minus\" size={12} /></button>\n <span style={{ minWidth: 34, textAlign: \"center\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-primary)\", borderLeft: \"1px solid var(--border-control)\", borderRight: \"1px solid var(--border-control)\", alignSelf: \"stretch\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", padding: \"0 6px\" }}>{value}</span>\n <button type=\"button\" disabled={disabled || atMax} style={seg(disabled || atMax)} onClick={() => onChange && onChange(max != null ? Math.min(max, value + step) : value + step)}><Icon name=\"plus\" size={12} /></button>\n </div>\n );\n}\n","forms/NumberStepper.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Segmented − / value / + stepper, 25.5px tall, r4 (\"Preview rows\"). Buttons disable themselves at min/max.\nconst props = defineProps({ modelValue: { type: Number, default: 100 }, step: { type: Number, default: 10 }, min: { type: Number, default: 0 }, max: Number, disabled: Boolean, style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst atMin = computed(() => props.modelValue <= props.min)\nconst atMax = computed(() => props.max != null && props.modelValue >= props.max)\nfunction seg(dis) { return { width: 30, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: dis ? 'not-allowed' : 'pointer', color: dis ? 'var(--text-muted)' : 'var(--text-body)', alignSelf: 'stretch', background: 'transparent', border: 'none', padding: 0 } }\n</script>\n<template>\n <div :style=\"{ display: 'inline-flex', alignItems: 'center', height: 25.5, borderRadius: 'var(--radius-sm)', border: '1px solid var(--border-control)', overflow: 'hidden', background: 'var(--bg-surface)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...style }\">\n <button type=\"button\" :disabled=\"disabled || atMin\" :style=\"seg(disabled || atMin)\" @click=\"emit('update:modelValue', Math.max(min, modelValue - step))\"><Icon name=\"minus\" :size=\"12\" /></button>\n <span style=\"min-width:34px;text-align:center;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-primary);border-left:1px solid var(--border-control);border-right:1px solid var(--border-control);align-self:stretch;display:inline-flex;align-items:center;justify-content:center;padding:0 6px\">{{ modelValue }}</span>\n <button type=\"button\" :disabled=\"disabled || atMax\" :style=\"seg(disabled || atMax)\" @click=\"emit('update:modelValue', max != null ? Math.min(max, modelValue + step) : modelValue + step)\"><Icon name=\"plus\" :size=\"12\" /></button>\n </div>\n</template>\n","forms/Radio.vue":"<script setup>\nimport { computed } from 'vue'\n// Radio — 16px (sm: 13px) circle, 1.5px border, filled dot when selected.\nconst dims = { sm: { box: 13, dot: 6 }, md: { box: 16, dot: 8 } }\nconst props = defineProps({ checked: Boolean, label: String, size: { type: String, default: 'md' }, disabled: Boolean, style: Object })\nconst emit = defineEmits(['change'])\nconst d = computed(() => dims[props.size] || dims.md)\n</script>\n<template>\n <label :style=\"{ display: 'inline-flex', alignItems: 'center', verticalAlign: 'middle', lineHeight: 'var(--leading-sm)', gap: 6, cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .5 : 1, ...style }\" @click=\"!disabled && emit('change', true)\">\n <span :style=\"{ width: d.box + 'px', height: d.box + 'px', borderRadius: 'var(--radius-full)', boxSizing: 'border-box', border: `1.5px solid ${checked ? 'var(--action-primary)' : 'var(--checkbox-border)'}`, background: 'var(--bg-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transition: 'var(--t-hover)' }\">\n <span v-if=\"checked\" :style=\"{ width: d.dot + 'px', height: d.dot + 'px', borderRadius: 'var(--radius-full)', background: 'var(--action-primary)' }\" />\n </span>\n <span v-if=\"label\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: size === 'sm' ? 'var(--text-xs)' : 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-body)' }\">{{ label }}</span>\n </label>\n</template>\n","forms/RadioGroup.vue":"<script setup>\nimport Radio from './Radio.vue'\n// Vertical or horizontal group of Radios.\ndefineProps({ options: { type: Array, default: () => [] }, modelValue: String, row: Boolean, size: { type: String, default: 'md' }, style: Object })\nconst emit = defineEmits(['update:modelValue'])\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: row ? 'row' : 'column', gap: (row ? 16 : 10) + 'px', ...style }\">\n <Radio v-for=\"o in options\" :key=\"o\" :label=\"o\" :size=\"size\" :checked=\"modelValue === o\" @change=\"emit('update:modelValue', o)\" />\n </div>\n</template>\n","forms/SearchInput.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Bordered search field (r10) with leading search glyph; bare=true renders the borderless top-bar flavor. Shows a clear (×) button once there's a value. */\nexport function SearchInput({ placeholder = \"Search...\", bare, value, onChange, disabled, style }) {\n const [inner, setInner] = React.useState(\"\");\n const val = value != null ? value : inner;\n const set = (v) => { if (value == null) setInner(v); onChange && onChange(v); };\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: bare ? 12 : 8, opacity: disabled ? .5 : 1, ...(bare ? null : { border: \"1px solid var(--border-control)\", borderRadius: \"var(--radius-lg)\", padding: \"6px 12px\", background: \"var(--bg-surface)\" }), ...style }}>\n <span style={{ color: \"var(--text-muted)\" }}><Icon name=\"search\" size={bare ? 16 : 14} /></span>\n <input value={val} disabled={disabled} onChange={(e) => set(e.target.value)} placeholder={placeholder}\n style={{ border: \"none\", outline: \"none\", background: \"transparent\", flex: 1, minWidth: 0, fontFamily: \"var(--font-sans)\", fontSize: bare ? \"var(--text-base)\" : \"var(--text-sm)\", letterSpacing: bare ? \"var(--tracking-base)\" : 0, color: \"var(--text-primary)\", cursor: disabled ? \"not-allowed\" : \"text\" }} />\n {val && !disabled ? <button type=\"button\" onClick={() => set(\"\")} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"var(--text-muted)\", display: \"flex\", padding: 0 }}><Icon name=\"x\" size={13} /></button> : null}\n </div>\n );\n}\n","forms/SearchInput.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Bordered search field (r10) with leading search glyph; bare=true renders the borderless top-bar flavor. Shows a clear (×) button once there's a value.\ndefineProps({ modelValue: { type: String, default: '' }, placeholder: { type: String, default: 'Search...' }, bare: Boolean, disabled: Boolean, style: Object })\nconst emit = defineEmits(['update:modelValue'])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: bare ? 12 : 8, opacity: disabled ? .5 : 1, ...(bare ? null : { border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', padding: '6px 12px', background: 'var(--bg-surface)' }), ...style }\">\n <span style=\"color:var(--text-muted)\"><Icon name=\"search\" :size=\"bare ? 16 : 14\" /></span>\n <input :value=\"modelValue\" :disabled=\"disabled\" @input=\"emit('update:modelValue', $event.target.value)\" :placeholder=\"placeholder\"\n :style=\"{ border: 'none', outline: 'none', background: 'transparent', flex: 1, minWidth: 0, fontFamily: 'var(--font-sans)', fontSize: bare ? 'var(--text-base)' : 'var(--text-sm)', letterSpacing: bare ? 'var(--tracking-base)' : 0, color: 'var(--text-primary)', cursor: disabled ? 'not-allowed' : 'text' }\" />\n <button v-if=\"modelValue && !disabled\" type=\"button\" @click=\"emit('update:modelValue', '')\" style=\"border:none;background:transparent;cursor:pointer;color:var(--text-muted);display:flex;padding:0\"><Icon name=\"x\" :size=\"13\" /></button>\n </div>\n</template>\n","forms/Select.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Native select styled per Keybricks. size md 36/r8 · sm 25/r4 (table rows). disabled = #fafafa inset fill (matches Input readOnly), 50% opacity; error = red border. */\nexport function Select({ options = [], size = \"md\", value, onChange, placeholder, disabled, error, style }) {\n const sm = size === \"sm\";\n return (\n <div style={{ position: \"relative\", display: \"inline-flex\", ...style }}>\n <select value={value} onChange={onChange} disabled={disabled}\n style={{ appearance: \"none\", WebkitAppearance: \"none\", height: sm ? 25 : 36, borderRadius: sm ? \"var(--radius-sm)\" : \"var(--radius-md)\", border: `1px solid ${error ? \"var(--danger)\" : \"var(--border-control)\"}`, background: disabled ? \"var(--bg-inset)\" : \"var(--bg-surface)\", padding: sm ? \"0 26px 0 10px\" : \"0 32px 0 12px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: disabled ? \"var(--text-secondary)\" : value ? \"var(--text-primary)\" : \"var(--text-muted)\", outline: \"none\", cursor: disabled ? \"not-allowed\" : \"pointer\", width: \"100%\", opacity: disabled ? .5 : 1 }}>\n {placeholder ? <option value=\"\" disabled hidden>{placeholder}</option> : null}\n {options.map((o) => <option key={o} value={o}>{o}</option>)}\n </select>\n <span style={{ position: \"absolute\", right: sm ? 8 : 10, top: \"50%\", transform: \"translateY(-50%)\", pointerEvents: \"none\", color: \"var(--text-muted)\" }}><Icon name=\"chevron-down\" size={sm ? 12 : 14} /></span>\n </div>\n );\n}\n","forms/Select.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Native select styled per Keybricks. size md 36/r8 · sm 25/r4 (table rows). disabled = inset fill (matches Input readonly), 50% opacity; error = red border.\nconst props = defineProps({ modelValue: String, options: { type: Array, default: () => [] }, size: { type: String, default: 'md' }, placeholder: String, disabled: Boolean, error: Boolean, style: Object })\ndefineEmits(['update:modelValue'])\n</script>\n<template>\n <div :style=\"{ position: 'relative', display: 'inline-flex', ...style }\">\n <select :value=\"modelValue\" @change=\"$emit('update:modelValue', $event.target.value)\" :disabled=\"disabled\"\n :style=\"{ appearance: 'none', height: size === 'sm' ? 25 : 36, borderRadius: size === 'sm' ? 'var(--radius-sm)' : 'var(--radius-md)', border: `1px solid ${error ? 'var(--danger)' : 'var(--border-control)'}`, background: disabled ? 'var(--bg-inset)' : 'var(--bg-surface)', padding: size === 'sm' ? '0 26px 0 10px' : '0 32px 0 12px', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', color: disabled ? 'var(--text-secondary)' : modelValue ? 'var(--text-primary)' : 'var(--text-muted)', outline: 'none', cursor: disabled ? 'not-allowed' : 'pointer', width: '100%', opacity: disabled ? .5 : 1 }\">\n <option v-if=\"placeholder\" value=\"\" disabled hidden>{{ placeholder }}</option>\n <option v-for=\"o in options\" :key=\"o\" :value=\"o\">{{ o }}</option>\n </select>\n <span :style=\"{ position: 'absolute', right: size === 'sm' ? 8 : 10, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: 'var(--text-muted)' }\"><Icon name=\"chevron-down\" :size=\"size === 'sm' ? 12 : 14\" /></span>\n </div>\n</template>\n","forms/Slider.jsx":"import React from \"react\";\n\n/** Range slider: blue filled track, 16px knob, optional min/max labels and a value bubble while dragging (or always with showValue). */\nexport function Slider({ value = 50, min = 0, max = 100, step = 1, labels, showValue, unit = \"\", disabled, width, onChange, style }) {\n const [drag, setDrag] = React.useState(false);\n const pct = ((value - min) / (max - min)) * 100;\n const track = React.useRef(null);\n const set = (clientX) => { const r = track.current.getBoundingClientRect(); const p = Math.min(1, Math.max(0, (clientX - r.left) / r.width)); const raw = min + p * (max - min); const snapped = Math.round(raw / step) * step; onChange && onChange(Math.min(max, Math.max(min, snapped))); };\n React.useEffect(() => { if (!drag) return; const mv = (e) => set(e.clientX), up = () => setDrag(false); document.addEventListener(\"mousemove\", mv); document.addEventListener(\"mouseup\", up); return () => { document.removeEventListener(\"mousemove\", mv); document.removeEventListener(\"mouseup\", up); }; }, [drag]);\n return (\n <div style={{ width, opacity: disabled ? .5 : 1, ...style }}>\n <div ref={track} onMouseDown={disabled ? undefined : (e) => { setDrag(true); set(e.clientX); }} style={{ position: \"relative\", height: 20, display: \"flex\", alignItems: \"center\", cursor: disabled ? \"not-allowed\" : \"pointer\" }}>\n <div style={{ position: \"absolute\", left: 0, right: 0, height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--track)\" }}></div>\n <div style={{ position: \"absolute\", left: 0, width: pct + \"%\", height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--action-primary)\" }}></div>\n <span style={{ position: \"absolute\", left: pct + \"%\", transform: \"translateX(-50%)\", width: 16, height: 16, borderRadius: \"var(--radius-full)\", background: \"var(--bg-surface)\", border: \"2px solid var(--action-primary)\", boxSizing: \"border-box\", boxShadow: \"0 1px 2px rgba(0,0,0,.15)\" }}></span>\n {(drag || showValue) && <span style={{ position: \"absolute\", left: pct + \"%\", transform: \"translateX(-50%)\", bottom: 22, background: \"var(--tooltip-bg)\", color: \"var(--tooltip-fg)\", borderRadius: \"var(--radius-sm)\", padding: \"2px 6px\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-2xs)\", whiteSpace: \"nowrap\", pointerEvents: \"none\" }}>{value}{unit}</span>}\n </div>\n {labels && <div style={{ display: \"flex\", justifyContent: \"space-between\", paddingTop: 4, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", color: \"var(--text-muted)\" }}><span>{min}{unit}</span><span>{max}{unit}</span></div>}\n </div>\n );\n}\n","forms/Slider.vue":"<script setup>\nimport { ref, computed, onUnmounted, watch } from 'vue'\n// Range slider: blue filled track, 16px knob, drag value bubble, optional min/max labels.\nconst props = defineProps({ modelValue: { type: Number, default: 50 }, min: { type: Number, default: 0 }, max: { type: Number, default: 100 }, step: { type: Number, default: 1 }, labels: Boolean, showValue: Boolean, unit: { type: String, default: '' }, disabled: Boolean, width: [Number, String], style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst drag = ref(false), track = ref(null)\nconst pct = computed(() => ((props.modelValue - props.min) / (props.max - props.min)) * 100)\nfunction set(clientX) { const r = track.value.getBoundingClientRect(); const p = Math.min(1, Math.max(0, (clientX - r.left) / r.width)); const raw = props.min + p * (props.max - props.min); emit('update:modelValue', Math.min(props.max, Math.max(props.min, Math.round(raw / props.step) * props.step))) }\nconst mv = (e) => set(e.clientX), up = () => drag.value = false\nwatch(drag, (on) => { if (on) { document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up) } else { document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up) } })\nonUnmounted(up)\n</script>\n<template>\n <div :style=\"{ width: typeof width === 'number' ? width + 'px' : width, opacity: disabled ? .5 : 1, ...style }\">\n <div ref=\"track\" @mousedown=\"!disabled && (drag = true, set($event.clientX))\" :style=\"{ position: 'relative', height: '20px', display: 'flex', alignItems: 'center', cursor: disabled ? 'not-allowed' : 'pointer' }\">\n <div style=\"position:absolute;left:0;right:0;height:6px;border-radius:var(--radius-full);background:var(--track)\" />\n <div :style=\"{ position: 'absolute', left: 0, width: pct + '%', height: '6px', borderRadius: 'var(--radius-full)', background: 'var(--action-primary)' }\" />\n <span :style=\"{ position: 'absolute', left: pct + '%', transform: 'translateX(-50%)', width: '16px', height: '16px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: '2px solid var(--action-primary)', boxSizing: 'border-box', boxShadow: '0 1px 2px rgba(0,0,0,.15)' }\" />\n <span v-if=\"drag || showValue\" :style=\"{ position: 'absolute', left: pct + '%', transform: 'translateX(-50%)', bottom: '22px', background: 'var(--tooltip-bg)', color: 'var(--tooltip-fg)', borderRadius: 'var(--radius-sm)', padding: '2px 6px', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-2xs)', whiteSpace: 'nowrap', pointerEvents: 'none' }\">{{ modelValue }}{{ unit }}</span>\n </div>\n <div v-if=\"labels\" style=\"display:flex;justify-content:space-between;padding-top:4px;font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)\"><span>{{ min }}{{ unit }}</span><span>{{ max }}{{ unit }}</span></div>\n </div>\n</template>\n","forms/Switch.jsx":"import React from \"react\";\n\nconst switchDims = { sm: { w: 28, h: 16, knob: 12 }, md: { w: 34, h: 20, knob: 16 } };\n/** Switch — 34×20 (sm: 28×16) track, knob. Keybricks has no switch in the source file; this follows the button radii + action color. */\nexport function Switch({ checked, onChange, label, size = \"md\", disabled, style }) {\n const d = switchDims[size] || switchDims.md;\n return (\n <label style={{ display: \"inline-flex\", alignItems: \"center\", verticalAlign: \"middle\", lineHeight: \"var(--leading-sm)\", gap: 8, cursor: disabled ? \"default\" : \"pointer\", opacity: disabled ? .5 : 1, ...style }}\n onClick={() => !disabled && onChange && onChange(!checked)}>\n <span style={{ width: d.w, height: d.h, borderRadius: \"var(--radius-full)\", background: checked ? \"var(--action-primary)\" : \"var(--track)\", position: \"relative\", flexShrink: 0, transition: \"var(--t-hover)\" }}>\n <span style={{ position: \"absolute\", top: 2, left: checked ? d.w - d.knob - 2 : 2, width: d.knob, height: d.knob, borderRadius: \"var(--radius-full)\", background: \"var(--on-accent)\", boxShadow: \"0 1px 2px rgba(0,0,0,.2)\", transition: \"left var(--dur-fast) var(--ease-out)\" }} />\n </span>\n {label ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: size === \"sm\" ? \"var(--text-xs)\" : \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{label}</span> : null}\n </label>\n );\n}\n\nconst radioDims = { sm: { box: 13, dot: 6 }, md: { box: 16, dot: 8 } };\n/** Radio — 16px (sm: 13px) circle, 1.5px border, filled dot when selected. */\nexport function Radio({ checked, onChange, label, size = \"md\", disabled, style }) {\n const d = radioDims[size] || radioDims.md;\n return (\n <label style={{ display: \"inline-flex\", alignItems: \"center\", verticalAlign: \"middle\", lineHeight: \"var(--leading-sm)\", gap: 6, cursor: disabled ? \"default\" : \"pointer\", opacity: disabled ? .5 : 1, ...style }}\n onClick={() => !disabled && onChange && onChange(true)}>\n <span style={{ width: d.box, height: d.box, borderRadius: \"var(--radius-full)\", boxSizing: \"border-box\", border: `1.5px solid ${checked ? \"var(--action-primary)\" : \"var(--checkbox-border)\"}`, background: \"var(--bg-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", flexShrink: 0, transition: \"var(--t-hover)\" }}>\n {checked ? <span style={{ width: d.dot, height: d.dot, borderRadius: \"var(--radius-full)\", background: \"var(--action-primary)\" }} /> : null}\n </span>\n {label ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: size === \"sm\" ? \"var(--text-xs)\" : \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{label}</span> : null}\n </label>\n );\n}\n\n/** Vertical or horizontal group of Radios. */\nexport function RadioGroup({ options = [], value, onChange, row, size = \"md\", style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: row ? \"row\" : \"column\", gap: row ? 16 : 10, ...style }}>\n {options.map((o) => <Radio key={o} label={o} size={size} checked={value === o} onChange={() => onChange && onChange(o)} />)}\n </div>\n );\n}\n","forms/Switch.vue":"<script setup>\nimport { computed } from 'vue'\n// Switch — 34×20 (sm: 28×16) track, knob.\nconst dims = { sm: { w: 28, h: 16, knob: 12 }, md: { w: 34, h: 20, knob: 16 } }\nconst props = defineProps({ modelValue: Boolean, label: String, size: { type: String, default: 'md' }, disabled: Boolean, style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst d = computed(() => dims[props.size] || dims.md)\n</script>\n<template>\n <label :style=\"{ display: 'inline-flex', alignItems: 'center', verticalAlign: 'middle', lineHeight: 'var(--leading-sm)', gap: 8, cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .5 : 1, ...style }\" @click=\"!disabled && emit('update:modelValue', !modelValue)\">\n <span :style=\"{ width: d.w + 'px', height: d.h + 'px', borderRadius: 'var(--radius-full)', background: modelValue ? 'var(--action-primary)' : 'var(--track)', position: 'relative', flexShrink: 0, transition: 'var(--t-hover)' }\">\n <span :style=\"{ position: 'absolute', top: '2px', left: (modelValue ? d.w - d.knob - 2 : 2) + 'px', width: d.knob + 'px', height: d.knob + 'px', borderRadius: 'var(--radius-full)', background: 'var(--on-accent)', boxShadow: '0 1px 2px rgba(0,0,0,.2)', transition: 'left var(--dur-fast) var(--ease-out)' }\" />\n </span>\n <span v-if=\"label\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: size === 'sm' ? 'var(--text-xs)' : 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-body)' }\">{{ label }}</span>\n </label>\n</template>\n"});
@@ -0,0 +1 @@
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"icons/Icon.jsx":"import React from \"react\";\n// Keybricks icon set — the source file uses Lucide stroke glyphs (24-grid, stroke 2,\n// round caps/joins, currentColor) rendered at 14/16px. Path data below is from the\n// public Lucide set, matching the glyphs used across Keyfunc UI v8.\nexport const ICON_PATHS = {\n search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n bell: '<path d=\"M10.268 21a2 2 0 0 0 3.464 0\"/><path d=\"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\"/>',\n sparkles: '<path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"/><path d=\"M20 3v4\"/><path d=\"M22 5h-4\"/><path d=\"M4 17v2\"/><path d=\"M5 18H3\"/>',\n \"panel-left\": '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/>',\n house: '<path d=\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"/><path d=\"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/>',\n file: '<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/>',\n \"file-text\": '<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/><path d=\"M10 9H8\"/><path d=\"M16 13H8\"/><path d=\"M16 17H8\"/>',\n \"file-check\": '<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/><path d=\"m9 15 2 2 4-4\"/>',\n \"layout-grid\": '<rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/>',\n database: '<ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5V19A9 3 0 0 0 21 19V5\"/><path d=\"M3 12A9 3 0 0 0 21 12\"/>',\n scale: '<path d=\"m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z\"/><path d=\"m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z\"/><path d=\"M7 21h10\"/><path d=\"M12 3v18\"/><path d=\"M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2\"/>',\n network: '<rect x=\"16\" y=\"16\" width=\"6\" height=\"6\" rx=\"1\"/><rect x=\"2\" y=\"16\" width=\"6\" height=\"6\" rx=\"1\"/><rect x=\"9\" y=\"2\" width=\"6\" height=\"6\" rx=\"1\"/><path d=\"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3\"/><path d=\"M12 12V8\"/>',\n folder: '<path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>',\n \"folder-open\": '<path d=\"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\"/>',\n \"folder-cog\": '<path d=\"M10.5 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v3\"/><circle cx=\"18\" cy=\"18\" r=\"3\"/><path d=\"M18 14v1\"/><path d=\"M18 21v1\"/><path d=\"M22 18h-1\"/><path d=\"M15 18h-1\"/><path d=\"m21.2 16.2-.9.5\"/><path d=\"m15.7 19.3-.9.5\"/><path d=\"m21.2 19.8-.9-.5\"/><path d=\"m15.7 16.7-.9-.5\"/>',\n settings: '<path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n \"settings-2\": '<path d=\"M20 7h-9\"/><path d=\"M14 17H5\"/><circle cx=\"17\" cy=\"17\" r=\"3\"/><circle cx=\"7\" cy=\"7\" r=\"3\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n \"circle-help\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><path d=\"M12 17h.01\"/>',\n box: '<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>',\n workflow: '<rect width=\"8\" height=\"8\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M7 11v4a2 2 0 0 0 2 2h4\"/><rect width=\"8\" height=\"8\" x=\"13\" y=\"13\" rx=\"2\"/>',\n shield: '<path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\"/>',\n \"refresh-cw\": '<path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/>',\n \"chart-line\": '<path d=\"M3 3v16a2 2 0 0 0 2 2h16\"/><path d=\"m19 9-5 5-4-4-3 3\"/>',\n \"chart-column\": '<path d=\"M3 3v16a2 2 0 0 0 2 2h16\"/><path d=\"M18 17V9\"/><path d=\"M13 17V5\"/><path d=\"M8 17v-3\"/>',\n \"chart-no-axes-column\": '<line x1=\"18\" x2=\"18\" y1=\"20\" y2=\"10\"/><line x1=\"12\" x2=\"12\" y1=\"20\" y2=\"4\"/><line x1=\"6\" x2=\"6\" y1=\"20\" y2=\"14\"/>',\n \"list-todo\": '<rect x=\"3\" y=\"5\" width=\"6\" height=\"6\" rx=\"1\"/><path d=\"m3 17 2 2 4-4\"/><path d=\"M13 6h8\"/><path d=\"M13 12h8\"/><path d=\"M13 18h8\"/>',\n \"circle-check\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>',\n target: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/>',\n \"book-open\": '<path d=\"M12 7v14\"/><path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/>',\n \"chevron-down\": '<path d=\"m6 9 6 6 6-6\"/>',\n \"chevron-right\": '<path d=\"m9 18 6-6-6-6\"/>',\n \"chevron-left\": '<path d=\"m15 18-6-6 6-6\"/>',\n \"chevron-up\": '<path d=\"m18 15-6-6-6 6\"/>',\n \"arrow-left\": '<path d=\"m12 19-7-7 7-7\"/><path d=\"M19 12H5\"/>',\n \"arrow-right\": '<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>',\n \"arrow-right-left\": '<path d=\"m16 3 4 4-4 4\"/><path d=\"M20 7H4\"/><path d=\"m8 21-4-4 4-4\"/><path d=\"M4 17h16\"/>',\n table: '<path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/>',\n layers: '<path d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z\"/><path d=\"m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65\"/><path d=\"m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65\"/>',\n \"grid-3x3\": '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/>',\n \"text-search\": '<path d=\"M21 6H3\"/><path d=\"M10 12H3\"/><path d=\"M10 18H3\"/><circle cx=\"17\" cy=\"15\" r=\"3\"/><path d=\"m21 19-1.9-1.9\"/>',\n shuffle: '<path d=\"M2 18h1.4c1.3 0 2.5-.6 3.3-1.7l6.1-8.6c.7-1.1 2-1.7 3.3-1.7H22\"/><path d=\"m18 2 4 4-4 4\"/><path d=\"M2 6h1.9c1.5 0 2.9.9 3.6 2.2\"/><path d=\"M22 18h-5.9c-1.3 0-2.6-.7-3.3-1.8l-.5-.8\"/><path d=\"m18 14 4 4-4 4\"/>',\n split: '<path d=\"M16 3h5v5\"/><path d=\"M8 3H3v5\"/><path d=\"M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3\"/><path d=\"m15 9 6-6\"/>',\n menu: '<line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\"/><line x1=\"4\" x2=\"20\" y1=\"6\" y2=\"6\"/><line x1=\"4\" x2=\"20\" y1=\"18\" y2=\"18\"/>',\n list: '<line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\"/><line x1=\"3\" x2=\"3.01\" y1=\"6\" y2=\"6\"/><line x1=\"3\" x2=\"3.01\" y1=\"12\" y2=\"12\"/><line x1=\"3\" x2=\"3.01\" y1=\"18\" y2=\"18\"/>',\n funnel: '<polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"/>',\n \"sliders-horizontal\": '<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/><line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/><line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/><line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>',\n \"trending-up\": '<polyline points=\"22 7 13.5 15.5 8.5 10.5 2 17\"/><polyline points=\"16 7 22 7 22 13\"/>',\n code: '<path d=\"m16 18 6-6-6-6\"/><path d=\"m8 6-6 6 6 6\"/>',\n merge: '<path d=\"m8 6 4-4 4 4\"/><path d=\"M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22\"/><path d=\"m20 22-5-5\"/>',\n upload: '<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/>',\n download: '<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"3\"/>',\n check: '<path d=\"M20 6 9 17l-5-5\"/>',\n x: '<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>',\n plus: '<path d=\"M5 12h14\"/><path d=\"M12 5v14\"/>',\n minus: '<path d=\"M5 12h14\"/>',\n ellipsis: '<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>',\n play: '<polygon points=\"6 3 20 12 6 21 6 3\"/>',\n send: '<path d=\"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z\"/><path d=\"m21.854 2.147-10.94 10.939\"/>',\n eye: '<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n copy: '<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>',\n \"trash-2\": '<path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>',\n clock: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/>',\n calendar: '<path d=\"M8 2v4\"/><path d=\"M16 2v4\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\"/><path d=\"M3 10h18\"/>',\n \"undo-2\": '<path d=\"M9 14 4 9l5-5\"/><path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\"/>',\n \"redo-2\": '<path d=\"m15 14 5-5-5-5\"/><path d=\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\"/>',\n history: '<path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/><path d=\"M12 7v5l4 2\"/>',\n bug: '<path d=\"m8 2 1.88 1.88\"/><path d=\"M14.12 3.88 16 2\"/><path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\"/><path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\"/><path d=\"M12 20v-9\"/><path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\"/><path d=\"M6 13H2\"/><path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\"/><path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\"/><path d=\"M22 13h-4\"/><path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\"/>',\n info: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/>',\n \"triangle-alert\": '<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 20h16a2 2 0 0 0 1.73-2Z\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/>',\n calendar: '<path d=\"M8 2v4\"/><path d=\"M16 2v4\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\"/><path d=\"M3 10h18\"/>',\n};\n\nimport { FEATHER_PATHS } from \"./feather-paths.js\";\n\n/** Lucide-style stroke icon. name: key of ICON_PATHS (core set) or any Feather icon name (feather-paths.js fallback); size in px; strokeWidth defaults to 2 (24-grid). Browse both sets with keywords in the Icon Gallery card. */\nexport function Icon({ name, size = 16, strokeWidth = 2, color, style, className }) {\n const body = ICON_PATHS[name] || FEATHER_PATHS[name] || \"\";\n return (\n <svg\n width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth={strokeWidth} strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className={className}\n style={{ display: \"block\", flexShrink: 0, color, ...style }}\n dangerouslySetInnerHTML={{ __html: body }}\n />\n );\n}\n","icons/Icon.vue":"<script setup>\n// Keybricks icon set — Lucide stroke glyphs (24-grid, stroke 2, round caps/joins, currentColor), rendered at 14/16px.\n// Full path dictionary mirrors icons/Icon.jsx's ICON_PATHS (80+ entries) — see that file for the complete list.\nimport { VUE_ICON_PATHS } from './icon-paths.js'\nimport { FEATHER_PATHS } from './feather-paths.js'\nconst props = defineProps({ name: String, size: { type: Number, default: 16 }, strokeWidth: { type: Number, default: 2 }, color: String, style: Object })\nconst body = (n) => VUE_ICON_PATHS[n] || FEATHER_PATHS[n] || ''\n</script>\n<template>\n <svg :width=\"size\" :height=\"size\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" :stroke-width=\"strokeWidth\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n :style=\"{ display: 'block', flexShrink: 0, color, ...style }\" v-html=\"body(name)\" />\n</template>\n","icons/Unicon.jsx":"import React from \"react\";\n\nlet uniconsCss = false;\nfunction ensureUniconsCss() {\n if (uniconsCss || document.getElementById(\"kb-unicons-css\")) { uniconsCss = true; return; }\n const l = document.createElement(\"link\");\n l.id = \"kb-unicons-css\"; l.rel = \"stylesheet\";\n l.href = \"https://unicons.iconscout.com/release/v4.0.8/css/line.css\";\n document.head.appendChild(l); uniconsCss = true;\n}\n\n/** Unicons line icon (iconscout.com/unicons, Apache 2.0) — 1,206 glyphs loaded from the official Unicons CDN webfont. name is the Unicons id without the \"uil-\" prefix (e.g. \"chart-pie-alt\"). Browse names + keywords in the Unicons gallery card; keyword index lives in assets/icons/unicons-index.json. */\nexport function Unicon({ name, size = 16, color, style, className }) {\n React.useEffect(ensureUniconsCss, []);\n return <i aria-hidden=\"true\" className={\"uil uil-\" + name + (className ? \" \" + className : \"\")} style={{ fontSize: size, lineHeight: 1, display: \"inline-block\", flexShrink: 0, fontStyle: \"normal\", color, ...style }}></i>;\n}\n","icons/Unicon.vue":"<script setup>\nimport { onMounted } from 'vue'\n// Unicons line icon — 1,206 glyphs from the official Unicons CDN webfont. name = Unicons id without \"uil-\".\nconst props = defineProps({ name: String, size: { type: Number, default: 16 }, color: String, style: Object })\nonMounted(() => {\n if (document.getElementById('kb-unicons-css')) return\n const l = document.createElement('link')\n l.id = 'kb-unicons-css'; l.rel = 'stylesheet'\n l.href = 'https://unicons.iconscout.com/release/v4.0.8/css/line.css'\n document.head.appendChild(l)\n})\n</script>\n<template>\n <i aria-hidden=\"true\" :class=\"'uil uil-' + name\" :style=\"{ fontSize: size + 'px', lineHeight: 1, display: 'inline-block', flexShrink: 0, fontStyle: 'normal', color, ...style }\" />\n</template>\n"});
@@ -0,0 +1 @@
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"navigation/Breadcrumb.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nfunction Crumb({ label, last, hovered, onSelect }) {\n const [hover, setHover] = React.useState(false);\n const on = (hover || hovered) && !last;\n return (\n <span role={last ? undefined : \"button\"} tabIndex={last ? undefined : 0}\n onClick={() => !last && onSelect && onSelect(label)}\n onKeyDown={(e) => { if (!last && (e.key === \"Enter\" || e.key === \" \")) { e.preventDefault(); onSelect && onSelect(label); } }}\n onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: last || on ? \"var(--text-heading)\" : \"var(--text-secondary)\", fontWeight: last ? \"var(--weight-medium)\" : \"var(--weight-regular)\", cursor: last ? \"default\" : \"pointer\", borderRadius: \"var(--radius-sm)\", transition: \"var(--t-hover)\" }}>{label}</span>\n );\n}\n\nfunction Sep() {\n return <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\" }}><Icon name=\"chevron-right\" size={13} /></span>;\n}\n\n/** Overflow crumb: an ellipsis button that drops down the levels hidden by collapsing. */\nfunction Overflow({ items, onSelect }) {\n const [open, setOpen] = React.useState(false);\n const [hover, setHover] = React.useState(false);\n const root = React.useRef(null);\n React.useEffect(() => {\n if (!open) return;\n const h = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); };\n document.addEventListener(\"mousedown\", h);\n return () => document.removeEventListener(\"mousedown\", h);\n }, [open]);\n return (\n <span ref={root} style={{ position: \"relative\", display: \"inline-flex\" }}>\n <span role=\"button\" tabIndex={0} aria-label={`Show ${items.length} hidden levels`} aria-expanded={open}\n onClick={() => setOpen(!open)}\n onKeyDown={(e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); setOpen(!open); } if (e.key === \"Escape\") setOpen(false); }}\n onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", width: 20, height: 18, borderRadius: \"var(--radius-sm)\", cursor: \"pointer\", color: open || hover ? \"var(--text-heading)\" : \"var(--text-muted)\", background: open ? \"var(--bg-active)\" : hover ? \"var(--bg-hover)\" : \"transparent\", transition: \"var(--t-hover)\" }}>\n <Icon name=\"ellipsis\" size={14} />\n </span>\n {open ? (\n <span style={{ position: \"absolute\", top: \"calc(100% + 5px)\", left: 0, zIndex: \"var(--z-menu)\", minWidth: 168, maxWidth: 260, background: \"var(--bg-surface)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-md)\", boxShadow: \"var(--shadow-menu)\", padding: 4, display: \"flex\", flexDirection: \"column\" }}>\n {items.map((it, i) => <OverflowItem key={it} label={it} depth={i} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />)}\n </span>\n ) : null}\n </span>\n );\n}\n\nfunction OverflowItem({ label, depth, onSelect }) {\n const [hover, setHover] = React.useState(false);\n return (\n <span role=\"menuitem\" tabIndex={0} onClick={() => onSelect(label)}\n onKeyDown={(e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); onSelect(label); } }}\n onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ display: \"flex\", alignItems: \"center\", gap: 6, padding: \"6px 9px\", paddingLeft: 9 + depth * 12, borderRadius: \"var(--radius-sm)\", cursor: \"pointer\", background: hover ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-heading)\", whiteSpace: \"nowrap\", overflow: \"hidden\", textOverflow: \"ellipsis\", transition: \"var(--t-hover)\" }}>\n {depth > 0 ? <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", flexShrink: 0 }}><Icon name=\"chevron-right\" size={11} /></span> : null}\n {label}\n </span>\n );\n}\n\n/** Breadcrumb trail — 12px, chevron separators, last crumb in heading color. Clickable crumbs darken on hover and carry press + keyboard focus from the global interaction layer. Trails longer than `maxItems` collapse to first · … · last two, with the hidden levels in a dropdown on the ellipsis. */\nexport function Breadcrumb({ items = [], onSelect, maxItems = 4, hoveredIndex, style }) {\n const collapse = items.length > maxItems && maxItems >= 3;\n const tailCount = maxItems - 2;\n const head = collapse ? items.slice(0, 1) : items;\n const hidden = collapse ? items.slice(1, items.length - tailCount) : [];\n const tail = collapse ? items.slice(items.length - tailCount) : [];\n const lastLabel = items[items.length - 1];\n return (\n <nav style={{ display: \"flex\", alignItems: \"center\", gap: 4, ...style }}>\n {head.map((it, i) => (\n <React.Fragment key={it}>\n {i > 0 ? <Sep /> : null}\n <Crumb label={it} last={it === lastLabel} hovered={hoveredIndex === i} onSelect={onSelect} />\n </React.Fragment>\n ))}\n {collapse ? (\n <React.Fragment>\n <Sep />\n <Overflow items={hidden} onSelect={onSelect} />\n {tail.map((it) => (\n <React.Fragment key={it}>\n <Sep />\n <Crumb label={it} last={it === lastLabel} onSelect={onSelect} />\n </React.Fragment>\n ))}\n </React.Fragment>\n ) : null}\n </nav>\n );\n}\n\n/** Theme toggle — flips <html data-theme> and persists to localStorage(\"kb-theme\"). Pass `dark`+`onToggle` to control it externally. */\nexport function ThemeToggle({ size = 34, dark: darkProp, onToggle, style }) {\n const controlled = darkProp != null;\n const [darkState, setDark] = React.useState(() => (typeof document !== \"undefined\" && document.documentElement.dataset.theme === \"dark\"));\n const dark = controlled ? darkProp : darkState;\n React.useEffect(() => {\n if (controlled || typeof document === \"undefined\") return;\n document.documentElement.dataset.theme = dark ? \"dark\" : \"light\";\n try { localStorage.setItem(\"kb-theme\", dark ? \"dark\" : \"light\"); } catch (e) {}\n }, [dark, controlled]);\n const [hover, setHover] = React.useState(false);\n return (\n <button type=\"button\" aria-label={dark ? \"Switch to light mode\" : \"Switch to dark mode\"} onClick={() => (controlled ? onToggle && onToggle(!dark) : setDark(!dark))}\n onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ width: size, height: size, borderRadius: \"var(--radius-md)\", border: \"none\", cursor: \"pointer\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", background: hover ? \"var(--bg-hover)\" : \"transparent\", color: \"var(--text-body)\", padding: 0, transition: \"var(--t-hover)\", ...style }}>\n <Icon name={dark ? \"sparkles\" : \"moon\"} size={18} />\n </button>\n );\n}\n\n/** Reads the persisted theme and applies it before first paint. Call once at app start. */\nexport function applyStoredTheme() {\n if (typeof document === \"undefined\") return;\n let t = \"light\";\n try { t = localStorage.getItem(\"kb-theme\") || \"light\"; } catch (e) {}\n document.documentElement.dataset.theme = t;\n}\n","navigation/Breadcrumb.vue":"<script setup>\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Breadcrumb trail — 12px, chevron separators, last crumb in heading color.\n// Clickable crumbs darken on hover (no underline) and carry press + keyboard focus from the global interaction layer.\n// Trails longer than maxItems collapse to first · … · last two, with the hidden levels in a dropdown on the ellipsis.\nconst props = defineProps({ items: { type: Array, default: () => [] }, maxItems: { type: Number, default: 4 }, hoveredIndex: { type: Number, default: null }, style: Object })\nconst emit = defineEmits(['select'])\nconst hover = ref(-1)\nconst ovHover = ref(false)\nconst menuHover = ref(-1)\nconst open = ref(false)\nconst root = ref(null)\nconst collapse = computed(() => props.items.length > props.maxItems && props.maxItems >= 3)\nconst tailCount = computed(() => props.maxItems - 2)\nconst head = computed(() => collapse.value ? props.items.slice(0, 1) : props.items)\nconst hidden = computed(() => collapse.value ? props.items.slice(1, props.items.length - tailCount.value) : [])\nconst tail = computed(() => collapse.value ? props.items.slice(props.items.length - tailCount.value) : [])\nconst lastLabel = computed(() => props.items[props.items.length - 1])\nconst isLast = (it) => it === lastLabel.value\nconst on = (i, it) => (hover.value === i || props.hoveredIndex === i) && !isLast(it)\nfunction pick(it) { if (!isLast(it)) emit('select', it) }\nfunction pickHidden(it) { open.value = false; emit('select', it) }\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nwatch(open, (v) => { if (v) document.addEventListener('mousedown', onDoc); else document.removeEventListener('mousedown', onDoc) })\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\nconst crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: isLast(it) || on(i, it) ? 'var(--text-heading)' : 'var(--text-secondary)', fontWeight: isLast(it) ? 'var(--weight-medium)' : 'var(--weight-regular)', cursor: isLast(it) ? 'default' : 'pointer', borderRadius: 'var(--radius-sm)', transition: 'var(--t-hover)' })\n</script>\n<template>\n <nav :style=\"{ display: 'flex', alignItems: 'center', gap: 4, ...style }\">\n <template v-for=\"(it, i) in head\" :key=\"it\">\n <span v-if=\"i > 0\" style=\"color:var(--text-muted);display:inline-flex\"><Icon name=\"chevron-right\" :size=\"13\" /></span>\n <span :role=\"isLast(it) ? undefined : 'button'\" :tabindex=\"isLast(it) ? undefined : 0\"\n @click=\"pick(it)\" @keydown.enter.prevent=\"pick(it)\" @keydown.space.prevent=\"pick(it)\"\n @mouseenter=\"hover = i\" @mouseleave=\"hover = -1\" :style=\"crumbStyle(i, it)\">{{ it }}</span>\n </template>\n <template v-if=\"collapse\">\n <span style=\"color:var(--text-muted);display:inline-flex\"><Icon name=\"chevron-right\" :size=\"13\" /></span>\n <span ref=\"root\" style=\"position:relative;display:inline-flex\">\n <span role=\"button\" tabindex=\"0\" :aria-label=\"`Show ${hidden.length} hidden levels`\" :aria-expanded=\"open\"\n @click=\"open = !open\" @keydown.enter.prevent=\"open = !open\" @keydown.space.prevent=\"open = !open\" @keydown.esc=\"open = false\"\n @mouseenter=\"ovHover = true\" @mouseleave=\"ovHover = false\"\n :style=\"{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '20px', height: '18px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', color: open || ovHover ? 'var(--text-heading)' : 'var(--text-muted)', background: open ? 'var(--bg-active)' : ovHover ? 'var(--bg-hover)' : 'transparent', transition: 'var(--t-hover)' }\">\n <Icon name=\"ellipsis\" :size=\"14\" />\n </span>\n <span v-if=\"open\" style=\"position:absolute;top:calc(100% + 5px);left:0;z-index:var(--z-menu);min-width:168px;max-width:260px;background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;display:flex;flex-direction:column\">\n <span v-for=\"(h, hi) in hidden\" :key=\"h\" role=\"menuitem\" tabindex=\"0\"\n @click=\"pickHidden(h)\" @keydown.enter.prevent=\"pickHidden(h)\" @keydown.space.prevent=\"pickHidden(h)\"\n @mouseenter=\"menuHover = hi\" @mouseleave=\"menuHover = -1\"\n :style=\"{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }\">\n <span v-if=\"hi > 0\" style=\"color:var(--text-muted);display:inline-flex;flex-shrink:0\"><Icon name=\"chevron-right\" :size=\"11\" /></span>{{ h }}\n </span>\n </span>\n </span>\n <template v-for=\"it in tail\" :key=\"it\">\n <span style=\"color:var(--text-muted);display:inline-flex\"><Icon name=\"chevron-right\" :size=\"13\" /></span>\n <span :role=\"isLast(it) ? undefined : 'button'\" :tabindex=\"isLast(it) ? undefined : 0\"\n @click=\"pick(it)\" @keydown.enter.prevent=\"pick(it)\" @keydown.space.prevent=\"pick(it)\"\n :style=\"crumbStyle(-1, it)\">{{ it }}</span>\n </template>\n </template>\n </nav>\n</template>\n\n<!-- ThemeToggle — flips <html data-theme> and persists to localStorage(\"kb-theme\"). Pass `dark` (v-model) to control it externally. -->\n<!--\n<script setup name=\"ThemeToggle\">\nimport { ref, computed, watch, onMounted } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst props = defineProps({ size: { type: Number, default: 34 }, dark: { type: Boolean, default: null }, style: Object })\nconst emit = defineEmits(['update:dark'])\nconst darkState = ref(false)\nconst dark = computed(() => props.dark != null ? props.dark : darkState.value)\nconst hover = ref(false)\nonMounted(() => { darkState.value = document.documentElement.dataset.theme === 'dark' })\nwatch(dark, (v) => { if (props.dark != null) return; document.documentElement.dataset.theme = v ? 'dark' : 'light'; try { localStorage.setItem('kb-theme', v ? 'dark' : 'light') } catch (e) {} })\nfunction toggle() { if (props.dark != null) emit('update:dark', !dark.value); else darkState.value = !darkState.value }\n</script>\n<template>\n <button type=\"button\" :aria-label=\"dark ? 'Switch to light mode' : 'Switch to dark mode'\" @click=\"toggle\" @mouseenter=\"hover = true\" @mouseleave=\"hover = false\"\n :style=\"{ width: size, height: size, borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: hover ? 'var(--bg-hover)' : 'transparent', color: 'var(--text-body)', padding: 0, transition: 'var(--t-hover)', ...style }\">\n <Icon :name=\"dark ? 'sparkles' : 'moon'\" :size=\"18\" />\n </button>\n</template>\n-->\n\n<!-- applyStoredTheme() — reads the persisted theme and applies it before first paint. Call once at app start (main.js): -->\n<!--\nexport function applyStoredTheme() {\n let t = 'light'\n try { t = localStorage.getItem('kb-theme') || 'light' } catch (e) {}\n document.documentElement.dataset.theme = t\n}\n-->\n","navigation/DocTree.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\nimport { SearchInput } from \"../forms/SearchInput.jsx\";\n\nconst docHues = { orange: \"var(--cat-transform)\", green: \"var(--cat-source)\", purple: \"var(--cat-merge)\", blue: \"var(--info-fg)\", teal: \"var(--cat-output)\", gray: \"var(--text-muted)\" };\n\n/** Secondary documents panel — 240px: title header, search row, 30px tree items. items: [{label, kind:\"folder\"|\"doc\", hue, icon}]. */\nexport function DocTree({ title = \"Documents\", items = [], active, onSelect, style }) {\n return (\n <div style={{ width: \"var(--sidebar-w)\", background: \"var(--bg-surface)\", borderRight: \"1px solid var(--border-card)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", flexShrink: 0, ...style }}>\n <div style={{ height: \"var(--topbar-h)\", borderBottom: \"1px solid var(--border-card)\", display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", padding: \"0 12px 0 16px\", boxSizing: \"border-box\", flexShrink: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n <IconButton icon=\"chevron-right\" size=\"md\" muted />\n </div>\n <div style={{ padding: \"12px 16px 8px\", borderBottom: \"1px solid var(--border-subtle)\" }}>\n <SearchInput bare placeholder=\"Search...\" style={{ gap: 8 }} />\n </div>\n <div style={{ padding: 8, display: \"flex\", flexDirection: \"column\", gap: 2, overflowY: \"auto\" }}>\n {items.map((it) => {\n const is = active === it.label;\n return (\n <div key={it.label} role=\"treeitem\" tabIndex={0} onClick={() => onSelect && onSelect(it.label)}\n style={{ height: \"var(--h-tree-item)\", borderRadius: \"var(--radius-md)\", display: \"flex\", alignItems: \"center\", gap: 8, padding: \"6px 8px\", boxSizing: \"border-box\", cursor: \"pointer\", background: is ? \"var(--bg-active)\" : \"transparent\" }}\n onMouseEnter={(e) => { if (!is) e.currentTarget.style.background = \"var(--bg-hover)\"; }}\n onMouseLeave={(e) => { if (!is) e.currentTarget.style.background = \"transparent\"; }}>\n <span style={{ color: it.kind === \"folder\" ? \"var(--text-muted)\" : docHues[it.hue || \"gray\"], display: \"inline-flex\" }}>\n <Icon name={it.icon || (it.kind === \"folder\" ? \"folder\" : \"file-text\")} size={16} />\n </span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-heading)\", whiteSpace: \"nowrap\", overflow: \"hidden\", textOverflow: \"ellipsis\" }}>{it.label}</span>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","navigation/DocTree.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nimport SearchInput from '../forms/SearchInput.vue'\nconst docHues = { orange: 'var(--cat-transform)', green: 'var(--cat-source)', purple: 'var(--cat-merge)', blue: 'var(--info-fg)', teal: 'var(--cat-output)', gray: 'var(--text-muted)' }\n// Secondary documents panel — 240px: title header, search row, 30px tree items. items: [{label, kind:\"folder\"|\"doc\", hue, icon}].\ndefineProps({ title: { type: String, default: 'Documents' }, items: { type: Array, default: () => [] }, active: String, style: Object })\nconst emit = defineEmits(['select'])\n</script>\n<template>\n <div :style=\"{ width: 'var(--sidebar-w)', background: 'var(--bg-surface)', borderRight: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, ...style }\">\n <div style=\"height:var(--topbar-h);border-bottom:1px solid var(--border-card);display:flex;align-items:center;justify-content:space-between;padding:0 12px 0 16px;box-sizing:border-box;flex-shrink:0\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <IconButton icon=\"chevron-right\" size=\"md\" muted />\n </div>\n <div style=\"padding:12px 16px 8px;border-bottom:1px solid var(--border-subtle)\"><SearchInput bare placeholder=\"Search...\" style=\"gap:8px\" /></div>\n <div style=\"padding:8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto\">\n <div v-for=\"it in items\" :key=\"it.label\" @click=\"emit('select', it.label)\"\n :style=\"{ height: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: 8, padding: '6px 8px', boxSizing: 'border-box', cursor: 'pointer', background: active === it.label ? 'var(--bg-active)' : 'transparent' }\">\n <span :style=\"{ color: it.kind === 'folder' ? 'var(--text-muted)' : docHues[it.hue || 'gray'], display: 'inline-flex' }\"><Icon :name=\"it.icon || (it.kind === 'folder' ? 'folder' : 'file-text')\" :size=\"16\" /></span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ it.label }}</span>\n </div>\n </div>\n </div>\n</template>\n","navigation/Sidebar.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nfunction NavItem({ icon, label, active, indent, collapsed, badge, onClick }) {\n const [hover, setHover] = React.useState(false);\n return (\n <div role=\"button\" tabIndex={0} onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ height: indent ? \"var(--h-subnav-item)\" : \"var(--h-nav-item)\", borderRadius: \"var(--radius-md)\", display: \"flex\", alignItems: \"center\", justifyContent: collapsed ? \"center\" : \"flex-start\", gap: 10, padding: collapsed ? \"8px 10px\" : indent ? \"6px 10px\" : \"8px 10px\", boxSizing: \"border-box\", cursor: \"pointer\", background: active ? \"var(--bg-active)\" : hover ? \"var(--bg-hover)\" : \"transparent\", color: active ? \"var(--text-heading)\" : \"var(--text-body)\", transition: \"var(--t-hover)\" }}>\n <Icon name={icon} size={indent ? 14 : 16} />\n {!collapsed ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", whiteSpace: \"nowrap\", flex: 1 }}>{label}</span> : null}\n {!collapsed && badge ? badge : null}\n </div>\n );\n}\n\n/**\n * Keybricks app sidebar — 240px (collapsed: 64px icon rail). White, hairline right border.\n * items: [{icon,label,children:[{icon,label}]}]; active by label; footer items pinned.\n */\nexport function Sidebar({ items = [], footerItems = [{ icon: \"circle-help\", label: \"Help\" }, { icon: \"settings\", label: \"Settings\" }, { icon: \"moon\", label: \"Dark Mode\" }], active, onSelect, collapsed = false, onToggleCollapse, logo = \"assets/keybricks-logo.png\", brand = \"Keybricks\", height = \"100%\", style }) {\n const [openSections, setOpen] = React.useState(() => items.filter((i) => i.children && (i.open || (i.children.some((c) => c.label === active)))).map((i) => i.label));\n const toggleSection = (l) => setOpen(openSections.includes(l) ? openSections.filter((x) => x !== l) : [...openSections, l]);\n return (\n <aside style={{ width: collapsed ? \"var(--rail-w)\" : \"var(--sidebar-w)\", height, background: \"var(--bg-surface)\", borderRight: \"1px solid var(--border-card)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", flexShrink: 0, transition: \"width .15s ease\", ...style }}>\n <div style={{ height: \"var(--topbar-h)\", borderBottom: \"1px solid var(--border-card)\", display: \"flex\", alignItems: \"center\", gap: 10, padding: \"0 16px\", boxSizing: \"border-box\", justifyContent: collapsed ? \"center\" : \"flex-start\", flexShrink: 0 }}>\n <img src={logo} alt=\"\" style={{ width: 24, height: 24, objectFit: \"contain\" }} />\n {!collapsed ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-lg)\", lineHeight: \"var(--leading-lg)\", letterSpacing: \"-.609px\", color: \"var(--text-primary)\" }}>{brand}</span> : null}\n </div>\n <nav style={{ flex: 1, overflowY: \"auto\", padding: 8, display: \"flex\", flexDirection: \"column\", gap: 0 }}>\n {onToggleCollapse ? <NavItem icon=\"panel-left\" label=\"Collapse\" collapsed={collapsed} onClick={onToggleCollapse} /> : null}\n {items.map((it) => it.children ? (\n <div key={it.label}>\n <div onClick={() => toggleSection(it.label)} style={{ height: \"var(--h-subnav-item)\", display: \"flex\", alignItems: \"center\", gap: 10, padding: collapsed ? \"6px 10px\" : \"6px 10px\", justifyContent: collapsed ? \"center\" : \"flex-start\", cursor: \"pointer\", color: \"var(--text-body)\", borderRadius: \"var(--radius-md)\", boxSizing: \"border-box\" }}>\n <Icon name={it.icon} size={16} />\n {!collapsed ? <><span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", flex: 1 }}>{it.label}</span><Icon name={openSections.includes(it.label) ? \"chevron-up\" : \"chevron-down\"} size={14} /></> : null}\n </div>\n {!collapsed && openSections.includes(it.label) ? (\n <div style={{ marginLeft: 24, display: \"flex\", flexDirection: \"column\", gap: 2, paddingTop: 2 }}>\n {it.children.map((c) => <NavItem key={c.label} icon={c.icon} label={c.label} indent active={active === c.label} onClick={() => onSelect && onSelect(c.label)} />)}\n </div>\n ) : null}\n </div>\n ) : (\n <NavItem key={it.label} icon={it.icon} label={it.label} collapsed={collapsed} active={active === it.label} onClick={() => onSelect && onSelect(it.label)} />\n ))}\n </nav>\n <div style={{ borderTop: \"1px solid var(--border-card)\", padding: 8, display: \"flex\", flexDirection: \"column\", flexShrink: 0 }}>\n {footerItems.map((f) => <NavItem key={f.label} icon={f.icon} label={f.label} collapsed={collapsed} onClick={() => onSelect && onSelect(f.label)} />)}\n </div>\n </aside>\n );\n}\n\n/** Default Keybricks nav model (Home dashboard). */\nexport const KEYBRICKS_NAV = [\n { icon: \"house\", label: \"Home\" },\n { icon: \"file-text\", label: \"Documents\" },\n { icon: \"layout-grid\", label: \"Business Intelligence\" },\n { icon: \"database\", label: \"Data Integration\" },\n { icon: \"scale\", label: \"Reconciliation\" },\n { icon: \"network\", label: \"Consolidation\" },\n {\n icon: \"folder-cog\", label: \"Management\", children: [\n { icon: \"box\", label: \"Modeling\" },\n { icon: \"workflow\", label: \"Workflow\" },\n { icon: \"shield\", label: \"Security Management\" },\n { icon: \"refresh-cw\", label: \"System Migration\" },\n { icon: \"chart-column\", label: \"Business Logic\" },\n { icon: \"list-todo\", label: \"Tasks Management\" },\n { icon: \"circle-check\", label: \"Validation Rules\" },\n { icon: \"settings-2\", label: \"Control Center\" },\n { icon: \"network\", label: \"Reconciliation\" },\n ],\n },\n];\n","navigation/Sidebar.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n\n/**\n * Keybricks app sidebar — 240px (collapsed: 64px icon rail). White, hairline right border.\n * items: [{icon,label,children:[{icon,label}]}]; active by label; footer items pinned.\n */\nconst props = defineProps({\n items: { type: Array, default: () => [] },\n footerItems: { type: Array, default: () => [{ icon: 'circle-help', label: 'Help' }, { icon: 'settings', label: 'Settings' }, { icon: 'moon', label: 'Dark Mode' }] },\n active: String, collapsed: { type: Boolean, default: false },\n logo: { type: String, default: 'assets/keybricks-logo.png' }, brand: { type: String, default: 'Keybricks' },\n height: { type: [String, Number], default: '100%' }, style: Object, canCollapse: Boolean,\n})\nconst emit = defineEmits(['select', 'toggleCollapse'])\nconst openSections = ref(props.items.filter(i => i.children && (i.open || i.children.some(c => c.label === props.active))).map(i => i.label))\nfunction toggleSection(l) { openSections.value = openSections.value.includes(l) ? openSections.value.filter(x => x !== l) : [...openSections.value, l] }\nfunction itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-item)' : 'var(--h-nav-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: 10, padding: indent ? '6px 10px' : '8px 10px', boxSizing: 'border-box', cursor: 'pointer', background: active ? 'var(--bg-active)' : 'transparent', color: active ? 'var(--text-heading)' : 'var(--text-body)', transition: 'var(--t-hover)' } }\n</script>\n<template>\n <aside :style=\"{ width: collapsed ? 'var(--rail-w)' : 'var(--sidebar-w)', height, background: 'var(--bg-surface)', borderRight: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, transition: 'width .15s ease', ...style }\">\n <div :style=\"{ height: 'var(--topbar-h)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', gap: 10, padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }\">\n <img :src=\"logo\" alt=\"\" style=\"width:24px;height:24px;object-fit:contain\" />\n <span v-if=\"!collapsed\" style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:-.609px;color:var(--text-primary)\">{{ brand }}</span>\n </div>\n <nav style=\"flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:0\">\n <div v-if=\"canCollapse\" role=\"button\" tabindex=\"0\" @click=\"emit('toggleCollapse')\" :style=\"itemStyle(false, false)\"><Icon name=\"panel-left\" :size=\"16\" /><span v-if=\"!collapsed\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1\">Collapse</span></div>\n <template v-for=\"it in items\" :key=\"it.label\">\n <div v-if=\"it.children\">\n <div role=\"button\" tabindex=\"0\" @click=\"toggleSection(it.label)\" :style=\"{ height: 'var(--h-subnav-item)', display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', color: 'var(--text-body)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box' }\">\n <Icon :name=\"it.icon\" :size=\"16\" />\n <template v-if=\"!collapsed\"><span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);flex:1\">{{ it.label }}</span><Icon :name=\"openSections.includes(it.label) ? 'chevron-up' : 'chevron-down'\" :size=\"14\" /></template>\n </div>\n <div v-if=\"!collapsed && openSections.includes(it.label)\" style=\"margin-left:24px;display:flex;flex-direction:column;gap:2px;padding-top:2px\">\n <div v-for=\"c in it.children\" :key=\"c.label\" role=\"button\" tabindex=\"0\" @click=\"emit('select', c.label)\" :style=\"itemStyle(active === c.label, true)\"><Icon :name=\"c.icon\" :size=\"14\" /><span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1\">{{ c.label }}</span></div>\n </div>\n </div>\n <div v-else role=\"button\" tabindex=\"0\" @click=\"emit('select', it.label)\" :style=\"itemStyle(active === it.label, false)\"><Icon :name=\"it.icon\" :size=\"16\" /><span v-if=\"!collapsed\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1\">{{ it.label }}</span></div>\n </template>\n </nav>\n <div style=\"border-top:1px solid var(--border-card);padding:8px;display:flex;flex-direction:column;flex-shrink:0\">\n <div v-for=\"f in footerItems\" :key=\"f.label\" role=\"button\" tabindex=\"0\" @click=\"emit('select', f.label)\" :style=\"itemStyle(false, false)\"><Icon :name=\"f.icon\" :size=\"16\" /><span v-if=\"!collapsed\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1\">{{ f.label }}</span></div>\n </div>\n </aside>\n</template>\n\n<!-- KEYBRICKS_NAV — default Keybricks nav model (Home dashboard), same shape as Sidebar.jsx's export, for reference:\nexport const KEYBRICKS_NAV = [\n { icon: 'house', label: 'Home' }, { icon: 'file-text', label: 'Documents' },\n { icon: 'layout-grid', label: 'Business Intelligence' }, { icon: 'database', label: 'Data Integration' },\n { icon: 'scale', label: 'Reconciliation' }, { icon: 'network', label: 'Consolidation' },\n { icon: 'folder-cog', label: 'Management', children: [ ... ] },\n] -->\n","navigation/TopBar.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\nimport { SearchInput } from \"../forms/SearchInput.jsx\";\n\n/** 56px top bar: bare search left, bell + dark AI Assistant button right. */\nexport function TopBar({ placeholder = \"Search documents, workflows, data...\", actions, onSearch, style }) {\n return (\n <header style={{ height: \"var(--topbar-h)\", background: \"var(--bg-surface)\", borderBottom: \"1px solid var(--border-card)\", display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", padding: \"0 24px\", boxSizing: \"border-box\", flexShrink: 0, ...style }}>\n <SearchInput bare placeholder={placeholder} onChange={onSearch} style={{ width: 576, maxWidth: \"48%\" }} />\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n {actions || (<>\n <IconButton icon=\"bell\" size=\"lg\" />\n <Button variant=\"dark\" icon=\"sparkles\">AI Assistant</Button>\n </>)}\n </div>\n </header>\n );\n}\n\n/** Editor toolbar: back chevron, 14px/600 title, \"• Editing\" status, right action slot. */\nexport function PageToolbar({ title, status = \"• Editing\", actions, onBack, style }) {\n return (\n <div style={{ height: \"var(--topbar-h)\", background: \"var(--bg-surface)\", borderBottom: \"1px solid var(--border-card)\", display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", padding: \"0 16px\", boxSizing: \"border-box\", flexShrink: 0, ...style }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n {onBack ? <IconButton icon=\"chevron-left\" size=\"md\" onClick={onBack} /> : null}\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {status ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-muted)\", marginLeft: 6 }}>{status}</span> : null}\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>{actions}</div>\n </div>\n );\n}\n\n/** Page heading block: 15px/500 title + 13px muted subtitle (System Migration pages). */\nexport function PageHeading({ title, subtitle, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-lg)\", lineHeight: \"var(--leading-lg)\", letterSpacing: \"var(--tracking-lg)\", color: \"var(--text-primary)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n );\n}\n","navigation/TopBar.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\nimport Button from '../buttons/Button.vue'\nimport SearchInput from '../forms/SearchInput.vue'\n// 56px top bar: bare search left, bell + dark AI Assistant button right.\ndefineProps({ placeholder: { type: String, default: 'Search documents, workflows, data...' }, style: Object })\nconst emit = defineEmits(['search'])\n</script>\n<template>\n <header :style=\"{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', boxSizing: 'border-box', flexShrink: 0, ...style }\">\n <SearchInput bare :placeholder=\"placeholder\" @update:modelValue=\"v => emit('search', v)\" style=\"width:576px;max-width:48%\" />\n <div style=\"display:flex;align-items:center;gap:8px\">\n <slot name=\"actions\">\n <IconButton icon=\"bell\" size=\"lg\" />\n <Button variant=\"dark\" icon=\"sparkles\">AI Assistant</Button>\n </slot>\n </div>\n </header>\n</template>\n\n<!-- PageToolbar — editor toolbar: back chevron, 14px/600 title, \"• Editing\" status, right action slot. -->\n<!--\n<script setup name=\"PageToolbar\">\nimport IconButton from '../buttons/IconButton.vue'\ndefineProps({ title: String, status: { type: String, default: '• Editing' }, showBack: Boolean, style: Object })\nconst emit = defineEmits(['back'])\n</script>\n<template>\n <div :style=\"{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 16px', boxSizing: 'border-box', flexShrink: 0, ...style }\">\n <div style=\"display:flex;align-items:center;gap:8px\">\n <IconButton v-if=\"showBack\" icon=\"chevron-left\" size=\"md\" @click=\"emit('back')\" />\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"status\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-muted);margin-left:6px\">{{ status }}</span>\n </div>\n <div style=\"display:flex;align-items:center;gap:8px\"><slot name=\"actions\" /></div>\n </div>\n</template>\n-->\n\n<!-- PageHeading — page heading block: 15px/500 title + 13px muted subtitle. -->\n<!--\n<script setup name=\"PageHeading\">\ndefineProps({ title: String, subtitle: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: 4, ...style }\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n</template>\n-->\n","navigation/Tree.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { Checkbox } from \"../forms/Checkbox.jsx\";\n\nconst docHues = { orange: \"var(--cat-transform)\", green: \"var(--cat-source)\", purple: \"var(--cat-merge)\", blue: \"var(--info-fg)\", teal: \"var(--cat-output)\", gray: \"var(--text-muted)\" };\nconst INDENT = 18;\n\nconst keyOf = (n) => (n.key != null ? n.key : n.label);\nconst titleOf = (n) => (n.title != null ? n.title : n.label);\nconst kidsOf = (n) => n.children || [];\nconst isBranch = (n) => kidsOf(n).length > 0 || n.isLeaf === false;\n\nfunction walk(nodes, fn, parentKey = null, depth = 0) {\n nodes.forEach((n) => { fn(n, parentKey, depth); walk(kidsOf(n), fn, keyOf(n), depth + 1); });\n}\nfunction indexNodes(items) {\n const byKey = {}, parent = {}, all = [];\n walk(items, (n, p, d) => { const k = keyOf(n); byKey[k] = n; parent[k] = p; all.push({ node: n, key: k, depth: d }); });\n return { byKey, parent, all };\n}\nfunction descendants(node, out = []) {\n kidsOf(node).forEach((c) => { out.push(c); descendants(c, out); });\n return out;\n}\n/** Is `key` the dragged node itself, or anywhere inside its subtree? */\nfunction inSubtree(items, rootKey, key) {\n const { byKey } = indexNodes(items);\n const root = byKey[rootKey];\n if (!root) return false;\n return rootKey === key || descendants(root).some((d) => keyOf(d) === key);\n}\nfunction keysMatching(items, test) {\n const hit = [];\n const visit = (n, trail) => {\n const t = [...trail, keyOf(n)];\n if (test(n)) hit.push(...trail);\n kidsOf(n).forEach((c) => visit(c, t));\n };\n items.forEach((n) => visit(n, []));\n return [...new Set(hit)];\n}\n\n/* Cascading check: a parent is checked when every enabled descendant is checked,\n indeterminate when only some are. Disabled nodes never change state. */\nfunction cascade(items, key, next) {\n const { byKey, parent } = indexNodes(items);\n const set = new Set(next);\n const node = byKey[key];\n if (!node) return next;\n const checked = set.has(key);\n descendants(node).forEach((d) => {\n if (d.disabled || d.disableCheckbox) return;\n checked ? set.add(keyOf(d)) : set.delete(keyOf(d));\n });\n let p = parent[key];\n while (p != null) {\n const pn = byKey[p];\n const kids = kidsOf(pn).filter((c) => !c.disabled && !c.disableCheckbox);\n const on = kids.filter((c) => set.has(keyOf(c)));\n on.length && on.length === kids.length ? set.add(p) : set.delete(p);\n p = parent[p];\n }\n return [...set];\n}\n/* Display state for every branch: full when all enabled descendants are checked,\n half when only some. Keeps parents honest whatever the caller passed in. */\nfunction rollUp(items, checkedKeys) {\n const full = new Set(checkedKeys), half = new Set();\n const visit = (n) => {\n kidsOf(n).forEach(visit);\n if (!kidsOf(n).length) return;\n const ds = descendants(n).filter((d) => !d.disabled && !d.disableCheckbox);\n if (!ds.length) return;\n const on = ds.filter((d) => full.has(keyOf(d))).length;\n if (on === ds.length) full.add(keyOf(n));\n else { full.delete(keyOf(n)); if (on > 0) half.add(keyOf(n)); }\n };\n items.forEach(visit);\n return { full, half };\n}\nfunction halfKeys(items, checkedKeys) {\n return [...rollUp(items, checkedKeys).half];\n}\n\nfunction Highlight({ text, query }) {\n if (!query) return text;\n const i = String(text).toLowerCase().indexOf(query.toLowerCase());\n if (i < 0) return text;\n return <>{String(text).slice(0, i)}<mark style={{ background: \"var(--soft-bg)\", color: \"var(--info-fg)\", fontWeight: \"var(--weight-medium)\", padding: 0, borderRadius: 2 }}>{String(text).slice(i, i + query.length)}</mark>{String(text).slice(i + query.length)}</>;\n}\n\n/** Guide lines for showLine: one 18px column per ancestor level, then the elbow. */\nfunction Guides({ trail, isLast, hasKids }) {\n return (\n <>\n {trail.map((ancestorHasNext, i) => (\n <span key={i} style={{ width: INDENT, alignSelf: \"stretch\", flexShrink: 0, borderLeft: ancestorHasNext ? \"1px solid var(--border-subtle)\" : \"1px solid transparent\", marginLeft: i === 0 ? 6 : 0 }} />\n ))}\n <span style={{ width: INDENT, alignSelf: \"stretch\", flexShrink: 0, position: \"relative\", marginLeft: trail.length === 0 ? 6 : 0 }}>\n <span style={{ position: \"absolute\", left: 0, top: 0, height: isLast ? \"50%\" : \"100%\", borderLeft: \"1px solid var(--border-subtle)\" }} />\n <span style={{ position: \"absolute\", left: 0, top: \"50%\", width: hasKids ? 6 : INDENT - 4, borderTop: \"1px solid var(--border-subtle)\" }} />\n </span>\n </>\n );\n}\n\nfunction Row({ item, state, cfg, api }) {\n const { node, key, depth, trail, isLast } = item;\n const kids = kidsOf(node);\n const branch = isBranch(node);\n const open = state.expanded.includes(key);\n const selected = state.selected.includes(key);\n const checked = state.checked.has(key);\n const half = !checked && state.half.has(key);\n const loading = state.loading.includes(key);\n const focused = state.focus === key;\n const ringed = focused && state.focusMoved;\n const [hover, setHover] = React.useState(false);\n const disabled = !!node.disabled;\n const dropAt = state.drop && state.drop.key === key ? state.drop.pos : null;\n const selectable = cfg.selectable && node.selectable !== false;\n\n const chevron = branch ? (\n <span onClick={(e) => { e.stopPropagation(); api.toggle(key); }} style={{ width: 14, height: 14, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--text-muted)\", flexShrink: 0, cursor: \"pointer\", transform: open ? \"rotate(90deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}>\n {loading ? <Icon name=\"loader\" size={12} style={{ animation: \"kb-tree-spin 900ms linear infinite\" }} /> : <Icon name=\"chevron-right\" size={13} />}\n </span>\n ) : <span style={{ width: cfg.showLine ? 0 : 14, flexShrink: 0 }} />;\n\n return (\n <>\n <div role=\"treeitem\" tabIndex={focused ? 0 : -1} aria-expanded={branch ? open : undefined} aria-selected={selected} aria-disabled={disabled || undefined}\n ref={(el) => { if (el && focused && state.focusMoved) el.focus({ preventScroll: true }); }}\n draggable={cfg.draggable && !disabled}\n onDragStart={(e) => { e.dataTransfer.effectAllowed = \"move\"; api.dragStart(key); }}\n onDragOver={(e) => { if (!cfg.draggable) return; e.preventDefault(); const r = e.currentTarget.getBoundingClientRect(); api.dragOver(key, (e.clientY - r.top) / r.height); }}\n onDragLeave={() => api.dragOver(null)}\n onDrop={(e) => { e.preventDefault(); api.drop(key); }}\n onDragEnd={() => api.dragEnd()}\n onClick={() => { if (disabled) return; api.focus(key); if (selectable) api.select(key); if (branch) api.toggle(key); }}\n onContextMenu={(e) => { if (cfg.onContextMenu) { e.preventDefault(); cfg.onContextMenu(key, e); } }}\n onKeyDown={(e) => api.keyDown(e, item)}\n onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{\n minHeight: \"var(--h-tree-item)\", borderRadius: \"var(--radius-md)\", display: \"flex\", alignItems: \"center\", gap: 6,\n padding: cfg.showLine ? \"0 8px 0 2px\" : \"5px 8px\", paddingLeft: cfg.showLine ? 2 : 8 + depth * INDENT, boxSizing: \"border-box\",\n cursor: disabled ? \"not-allowed\" : \"pointer\", opacity: disabled ? 0.45 : 1,\n background: dropAt === \"inside\" ? \"var(--soft-bg)\" : selected ? \"var(--bg-active)\" : hover && !disabled ? \"var(--bg-hover)\" : \"transparent\",\n outline: dropAt === \"inside\" ? \"2px solid var(--action-primary)\" : ringed ? \"2px solid var(--focus-border)\" : \"none\", outlineOffset: -2,\n boxShadow: dropAt === \"before\" ? \"inset 0 2px 0 var(--action-primary)\" : dropAt === \"after\" ? \"inset 0 -2px 0 var(--action-primary)\" : \"none\",\n transition: \"var(--t-hover)\",\n }}>\n {cfg.showLine ? <Guides trail={trail} isLast={isLast} hasKids={branch} /> : null}\n {chevron}\n {cfg.checkable && node.checkable !== false ? (\n <span onClick={(e) => e.stopPropagation()} style={{ display: \"inline-flex\", flexShrink: 0 }}>\n <Checkbox size=\"sm\" checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />\n </span>\n ) : null}\n {cfg.showIcon ? (\n <span style={{ color: branch ? \"var(--text-muted)\" : docHues[node.hue || \"gray\"], display: \"inline-flex\", flexShrink: 0 }}>\n <Icon name={node.icon || (branch ? (open ? \"folder-open\" : \"folder\") : \"file-text\")} size={15} />\n </span>\n ) : null}\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", fontWeight: selected ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: disabled ? \"var(--text-muted)\" : \"var(--text-heading)\", whiteSpace: \"nowrap\", overflow: \"hidden\", textOverflow: \"ellipsis\" }}>\n <Highlight text={titleOf(node)} query={cfg.searchValue} />\n </span>\n {node.tag ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-2xs)\", lineHeight: 1, padding: \"2px 5px\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-inset)\", color: \"var(--text-secondary)\", flexShrink: 0 }}>{node.tag}</span> : null}\n {node.count != null ? <span style={{ marginLeft: \"auto\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-2xs)\", color: \"var(--text-muted)\", paddingLeft: 8 }}>{node.count}</span> : null}\n {cfg.rowActions && !disabled ? <span style={{ marginLeft: node.count != null ? 6 : \"auto\", display: \"flex\", gap: 2, opacity: hover ? 1 : 0, transition: \"var(--t-hover)\" }} onClick={(e) => e.stopPropagation()}>{cfg.rowActions(node)}</span> : null}\n </div>\n {open && loading && !kids.length ? (\n <div style={{ paddingLeft: 8 + (depth + 1) * INDENT + 14, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", color: \"var(--text-muted)\", height: \"var(--h-tree-item)\", display: \"flex\", alignItems: \"center\" }}>Loading…</div>\n ) : null}\n </>\n );\n}\n\n/**\n * Nested tree view. Expand/collapse, single or multiple selection, cascading checkboxes\n * (with indeterminate parents and `checkStrictly` opt-out), disabled nodes and disabled\n * checkboxes, connector lines, async child loading, search highlighting, drag to move,\n * and full keyboard navigation. Node shape: { key, title, icon, hue, count, tag, children,\n * disabled, disableCheckbox, selectable, checkable, isLeaf }.\n */\nexport function Tree({\n items = [], treeData,\n defaultExpanded = [], expandedKeys, onExpand,\n defaultActive, defaultSelected = [], selectedKeys, onSelect,\n checkable = false, checkStrictly = false, defaultChecked = [], checkedKeys, onCheck,\n multiple = false, selectable = true, showIcon = true, showLine = false, draggable = false,\n searchValue = \"\", autoExpandOnSearch = true, loadData, onDrop, onContextMenu, rowActions, style, reorder = false,\n}) {\n const raw = treeData || items;\n /* Callers usually pass an inline array literal, so compare by value —\n re-seeding on identity alone would wipe async-loaded children. */\n const rawKey = JSON.stringify(raw);\n const [data, setData] = React.useState(raw);\n const seenKey = React.useRef(rawKey);\n React.useEffect(() => { if (seenKey.current !== rawKey) { seenKey.current = rawKey; setData(raw); } }, [rawKey]);\n\n const seedExpanded = defaultExpanded.length ? defaultExpanded : defaultActive ? [] : [];\n const [expandedS, setExpandedS] = React.useState(seedExpanded);\n const [selectedS, setSelectedS] = React.useState(defaultActive ? [defaultActive] : defaultSelected);\n const [checkedS, setCheckedS] = React.useState(defaultChecked);\n const [loading, setLoading] = React.useState([]);\n const [loaded, setLoaded] = React.useState([]);\n const [focus, setFocus] = React.useState(null);\n const [focusMoved, setFocusMoved] = React.useState(false);\n const [dragKey, setDragKey] = React.useState(null);\n const [drop, setDrop] = React.useState(null);\n\n const expanded = expandedKeys || expandedS;\n const selected = selectedKeys || selectedS;\n const checked = checkedKeys || checkedS;\n\n const searchExpanded = React.useMemo(\n () => (searchValue && autoExpandOnSearch ? keysMatching(data, (n) => String(titleOf(n)).toLowerCase().includes(searchValue.toLowerCase())) : []),\n [data, searchValue, autoExpandOnSearch]\n );\n const openKeys = React.useMemo(() => [...new Set([...expanded, ...searchExpanded])], [expanded, searchExpanded]);\n const view = React.useMemo(\n () => (checkable && !checkStrictly ? rollUp(data, checked) : { full: new Set(checked), half: new Set() }),\n [checkable, checkStrictly, data, checked]\n );\n\n /* Visible rows, with the sibling map each row needs to draw its guide lines. */\n const rows = React.useMemo(() => {\n const out = [];\n const push = (nodes, depth, trail) => {\n nodes.forEach((n, i) => {\n const isLast = i === nodes.length - 1;\n const key = keyOf(n);\n out.push({ node: n, key, depth, trail, isLast });\n if (kidsOf(n).length && openKeys.includes(key)) push(kidsOf(n), depth + 1, [...trail, !isLast]);\n });\n };\n push(data, 0, []);\n return out;\n }, [data, openKeys]);\n\n const setExpanded = (next) => { if (!expandedKeys) setExpandedS(next); onExpand && onExpand(next); };\n\n const api = {\n focus: (k) => { setFocus(k); setFocusMoved(false); },\n toggle: (k) => {\n const open = openKeys.includes(k);\n if (!open && loadData && !loaded.includes(k) && !kidsOf(indexNodes(data).byKey[k]).length) {\n setLoading((l) => [...l, k]);\n setExpanded([...expanded, k]);\n Promise.resolve(loadData(indexNodes(data).byKey[k])).then((kids) => {\n setData((d) => {\n const clone = JSON.parse(JSON.stringify(d));\n const { byKey } = indexNodes(clone);\n if (byKey[k]) byKey[k].children = kids || [];\n return clone;\n });\n setLoaded((l) => [...l, k]);\n setLoading((l) => l.filter((x) => x !== k));\n });\n return;\n }\n setExpanded(open ? expanded.filter((x) => x !== k) : [...expanded, k]);\n },\n select: (k) => {\n const next = multiple ? (selected.includes(k) ? selected.filter((x) => x !== k) : [...selected, k]) : selected.includes(k) && multiple ? [] : [k];\n if (!selectedKeys) setSelectedS(next);\n onSelect && onSelect(multiple ? next : k, next);\n },\n check: (k) => {\n const on = view.full.has(k);\n let next = on ? [...view.full].filter((x) => x !== k) : [...view.full, k];\n if (!checkStrictly) next = cascade(data, k, next);\n if (!checkedKeys) setCheckedS(next);\n onCheck && onCheck(next, { key: k, checked: !on, half: checkStrictly ? [] : halfKeys(data, next) });\n },\n dragStart: (k) => setDragKey(k),\n /* Validity lives here, not in the row: only branches can be dropped *into*, and a node\n can never land inside its own subtree (that would detach the whole branch). */\n dragOver: (k, ratio) => {\n if (k == null) return setDrop(null);\n const node = indexNodes(data).byKey[k];\n /* Only folders are drop targets: a file can't contain anything, so hovering one shows\n nothing at all. With reorder, a folder's outer 20% becomes an insert rule instead. */\n if (!node || node.disabled || !dragKey || !isBranch(node) || inSubtree(data, dragKey, k)) return setDrop(null);\n const pos = reorder ? (ratio < 0.2 ? \"before\" : ratio > 0.8 ? \"after\" : \"inside\") : \"inside\";\n setDrop({ key: k, pos });\n },\n dragEnd: () => { setDragKey(null); setDrop(null); },\n drop: (k) => {\n const info = drop && drop.key === k ? drop : null;\n setDrop(null);\n if (!dragKey || !info || inSubtree(data, dragKey, k)) return setDragKey(null);\n /* Computed outside setData: React re-invokes updaters (StrictMode, concurrent\n rebasing), which would fire the consumer's onDrop twice for one gesture. */\n const clone = JSON.parse(JSON.stringify(data));\n let moved = null;\n const prune = (nodes) => nodes.filter((n) => {\n if (keyOf(n) === dragKey) { moved = n; return false; }\n if (n.children) n.children = prune(n.children);\n return true;\n });\n const rootA = prune(clone);\n if (!moved) return setDragKey(null);\n let inserted = false;\n const insert = (nodes) => {\n const out = [];\n nodes.forEach((n) => {\n if (keyOf(n) === k && info.pos === \"before\") { out.push(moved); inserted = true; }\n if (keyOf(n) === k && info.pos === \"inside\") { n.children = [...(n.children || []), moved]; inserted = true; }\n else if (n.children) n.children = insert(n.children);\n out.push(n);\n if (keyOf(n) === k && info.pos === \"after\") { out.push(moved); inserted = true; }\n });\n return out;\n };\n const next = insert(rootA);\n if (!inserted) return setDragKey(null);\n setData(next);\n /* Dropping into a collapsed folder would otherwise look like the node vanished. */\n if (info.pos === \"inside\" && !expanded.includes(k)) setExpanded([...expanded, k]);\n onDrop && onDrop({ dragKey, dropKey: k, position: info.pos, treeData: next });\n setDragKey(null);\n },\n keyDown: (e, item) => {\n const i = rows.findIndex((r) => r.key === item.key);\n const go = (n) => { const t = rows[n]; if (!t) return; setFocus(t.key); setFocusMoved(true); };\n if (e.key === \"ArrowDown\") { e.preventDefault(); go(i + 1); }\n else if (e.key === \"ArrowUp\") { e.preventDefault(); go(i - 1); }\n else if (e.key === \"ArrowRight\") { e.preventDefault(); if (isBranch(item.node) && !openKeys.includes(item.key)) api.toggle(item.key); else go(i + 1); }\n else if (e.key === \"ArrowLeft\") { e.preventDefault(); if (isBranch(item.node) && openKeys.includes(item.key)) api.toggle(item.key); else { const p = rows.slice(0, i).reverse().find((r) => r.depth === item.depth - 1); if (p) { setFocus(p.key); setFocusMoved(true); } } }\n else if (e.key === \"Enter\") { e.preventDefault(); if (!item.node.disabled) { if (selectable && item.node.selectable !== false) api.select(item.key); if (isBranch(item.node)) api.toggle(item.key); } }\n else if (e.key === \" \") { e.preventDefault(); if (checkable && !item.node.disabled && item.node.checkable !== false && !item.node.disableCheckbox) api.check(item.key); }\n },\n };\n\n const state = { expanded: openKeys, selected, checked: view.full, half: view.half, loading, focus: focus || (rows[0] && rows[0].key), focusMoved, drop };\n const cfg = { checkable, checkStrictly, multiple, selectable, showIcon, showLine, draggable, searchValue, onContextMenu, rowActions };\n\n return (\n <div role=\"tree\" aria-multiselectable={multiple || undefined} style={{ display: \"flex\", flexDirection: \"column\", gap: showLine ? 0 : 1, ...style }}>\n <style>{\"@keyframes kb-tree-spin{to{transform:rotate(360deg)}}\"}</style>\n {rows.length === 0 ? (\n <div style={{ padding: \"10px 8px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-muted)\" }}>No matching items</div>\n ) : rows.map((r) => <Row key={r.key} item={r} state={state} cfg={cfg} api={api} />)}\n </div>\n );\n}\n","navigation/Tree.vue":"<script setup>\nimport { ref, computed, watch } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport Checkbox from '../forms/Checkbox.vue'\n// Nested tree view: expand/collapse, single or multiple selection, cascading checkboxes with\n// indeterminate parents (checkStrictly opts out), disabled nodes, connector lines, async\n// loadData, search highlight, drag to move, keyboard navigation.\nconst docHues = { orange: 'var(--cat-transform)', green: 'var(--cat-source)', purple: 'var(--cat-merge)', blue: 'var(--info-fg)', teal: 'var(--cat-output)', gray: 'var(--text-muted)' }\nconst INDENT = 18\nconst props = defineProps({\n items: { type: Array, default: () => [] },\n defaultExpanded: { type: Array, default: () => [] },\n defaultActive: String,\n defaultSelected: { type: Array, default: () => [] },\n defaultChecked: { type: Array, default: () => [] },\n checkable: Boolean, checkStrictly: Boolean, multiple: Boolean,\n selectable: { type: Boolean, default: true },\n showIcon: { type: Boolean, default: true },\n showLine: Boolean, draggable: Boolean,\n searchValue: { type: String, default: '' },\n autoExpandOnSearch: { type: Boolean, default: true },\n reorder: Boolean,\n loadData: Function, rowActions: Function, style: Object,\n})\nconst emit = defineEmits(['select', 'check', 'expand', 'drop'])\n\nconst data = ref(JSON.parse(JSON.stringify(props.items)))\n// Compare by value: re-seeding on identity alone would wipe async-loaded children.\nwatch(() => JSON.stringify(props.items), (next, prev) => { if (next !== prev) data.value = JSON.parse(next) })\nconst expanded = ref([...props.defaultExpanded])\nconst selected = ref(props.defaultActive ? [props.defaultActive] : [...props.defaultSelected])\nconst checked = ref([...props.defaultChecked])\nconst loading = ref([]); const loaded = ref([]); const focus = ref(null)\nconst dragKey = ref(null); const drop = ref(null)\n\nconst keyOf = n => (n.key != null ? n.key : n.label)\nconst titleOf = n => (n.title != null ? n.title : n.label)\nconst kidsOf = n => n.children || []\nconst isBranch = n => kidsOf(n).length > 0 || n.isLeaf === false\nfunction descendants(n, out = []) { for (const c of kidsOf(n)) { out.push(c); descendants(c, out) } return out }\n/** Is `key` the dragged node itself, or anywhere inside its subtree? */\nfunction inSubtree(rootKey, key) {\n const root = index(data.value).byKey[rootKey]\n if (!root) return false\n return rootKey === key || descendants(root).some(d => keyOf(d) === key)\n}\nfunction index(nodes, parent = null, acc = { byKey: {}, parent: {} }) {\n for (const n of nodes) { acc.byKey[keyOf(n)] = n; acc.parent[keyOf(n)] = parent; index(kidsOf(n), keyOf(n), acc) }\n return acc\n}\nfunction ancestorsOfMatches(nodes, test, trail = [], hit = []) {\n for (const n of nodes) {\n if (test(n)) hit.push(...trail)\n ancestorsOfMatches(kidsOf(n), test, [...trail, keyOf(n)], hit)\n }\n return [...new Set(hit)]\n}\nconst searchExpanded = computed(() => props.searchValue && props.autoExpandOnSearch\n ? ancestorsOfMatches(data.value, n => String(titleOf(n)).toLowerCase().includes(props.searchValue.toLowerCase()))\n : [])\nconst openKeys = computed(() => [...new Set([...expanded.value, ...searchExpanded.value])])\n\nconst view = computed(() => {\n const full = new Set(checked.value); const half = new Set()\n if (!props.checkable || props.checkStrictly) return { full, half }\n const visit = n => {\n kidsOf(n).forEach(visit)\n if (!kidsOf(n).length) return\n const ds = descendants(n).filter(d => !d.disabled && !d.disableCheckbox)\n if (!ds.length) return\n const on = ds.filter(d => full.has(keyOf(d))).length\n if (on === ds.length) full.add(keyOf(n))\n else { full.delete(keyOf(n)); if (on > 0) half.add(keyOf(n)) }\n }\n data.value.forEach(visit)\n return { full, half }\n})\n\nconst rows = computed(() => {\n const out = []\n const push = (nodes, depth, trail) => {\n nodes.forEach((n, i) => {\n const isLast = i === nodes.length - 1\n out.push({ n, key: keyOf(n), depth, trail, isLast })\n if (kidsOf(n).length && openKeys.value.includes(keyOf(n))) push(kidsOf(n), depth + 1, [...trail, !isLast])\n })\n }\n push(data.value, 0, [])\n return out\n})\n\nfunction toggle(k) {\n const open = openKeys.value.includes(k)\n const { byKey } = index(data.value)\n if (!open && props.loadData && !loaded.value.includes(k) && !kidsOf(byKey[k]).length) {\n loading.value = [...loading.value, k]\n expanded.value = [...expanded.value, k]\n Promise.resolve(props.loadData(byKey[k])).then(kids => {\n const clone = JSON.parse(JSON.stringify(data.value))\n const t = index(clone).byKey[k]\n if (t) t.children = kids || []\n data.value = clone\n loaded.value = [...loaded.value, k]\n loading.value = loading.value.filter(x => x !== k)\n })\n return\n }\n expanded.value = open ? expanded.value.filter(x => x !== k) : [...expanded.value, k]\n emit('expand', expanded.value)\n}\nfunction select(k) {\n selected.value = props.multiple\n ? (selected.value.includes(k) ? selected.value.filter(x => x !== k) : [...selected.value, k])\n : [k]\n emit('select', props.multiple ? selected.value : k, selected.value)\n}\nfunction check(k) {\n const set = new Set(view.value.full)\n const on = set.has(k)\n on ? set.delete(k) : set.add(k)\n if (!props.checkStrictly) {\n const { byKey, parent } = index(data.value)\n descendants(byKey[k]).forEach(d => {\n if (d.disabled || d.disableCheckbox) return\n on ? set.delete(keyOf(d)) : set.add(keyOf(d))\n })\n let p = parent[k]\n while (p != null) {\n const kids = kidsOf(byKey[p]).filter(c => !c.disabled && !c.disableCheckbox)\n const hit = kids.filter(c => set.has(keyOf(c)))\n hit.length && hit.length === kids.length ? set.add(p) : set.delete(p)\n p = parent[p]\n }\n }\n checked.value = [...set]\n emit('check', checked.value, { key: k, checked: !on, half: [...view.value.half] })\n}\nfunction rowClick(n) {\n if (n.disabled) return\n focus.value = keyOf(n)\n if (props.selectable && n.selectable !== false) select(keyOf(n))\n if (isBranch(n)) toggle(keyOf(n))\n}\nfunction onDragOver(e, k) {\n if (!props.draggable) return\n e.preventDefault()\n if (!dragKey.value) return\n const n = index(data.value).byKey[k]\n // Only folders are drop targets: hovering a file shows nothing, since it can't contain anything.\n if (!n || n.disabled || !isBranch(n) || inSubtree(dragKey.value, k)) { drop.value = null; return }\n const r = e.currentTarget.getBoundingClientRect()\n const y = (e.clientY - r.top) / r.height\n drop.value = { key: k, pos: props.reorder ? (y < 0.2 ? 'before' : y > 0.8 ? 'after' : 'inside') : 'inside' }\n}\nfunction onDrop(k) {\n const info = drop.value && drop.value.key === k ? drop.value : null\n drop.value = null\n if (!dragKey.value || !info || inSubtree(dragKey.value, k)) { dragKey.value = null; return }\n const clone = JSON.parse(JSON.stringify(data.value))\n let moved = null\n const prune = nodes => nodes.filter(n => {\n if (keyOf(n) === dragKey.value) { moved = n; return false }\n if (n.children) n.children = prune(n.children)\n return true\n })\n const pruned = prune(clone)\n if (!moved) { dragKey.value = null; return }\n const insert = nodes => {\n const out = []\n for (const n of nodes) {\n if (keyOf(n) === k && info.pos === 'before') out.push(moved)\n if (keyOf(n) === k && info.pos === 'inside') n.children = [...(n.children || []), moved]\n else if (n.children) n.children = insert(n.children)\n out.push(n)\n if (keyOf(n) === k && info.pos === 'after') out.push(moved)\n }\n return out\n }\n data.value = insert(pruned)\n if (info.pos === 'inside' && !expanded.value.includes(k)) expanded.value = [...expanded.value, k]\n emit('drop', { dragKey: dragKey.value, dropKey: k, position: info.pos, treeData: data.value })\n dragKey.value = null\n}\nfunction keyDown(e, row) {\n const i = rows.value.findIndex(r => r.key === row.key)\n const go = n => { const t = rows.value[n]; if (t) focus.value = t.key }\n if (e.key === 'ArrowDown') { e.preventDefault(); go(i + 1) }\n else if (e.key === 'ArrowUp') { e.preventDefault(); go(i - 1) }\n else if (e.key === 'ArrowRight') { e.preventDefault(); isBranch(row.n) && !openKeys.value.includes(row.key) ? toggle(row.key) : go(i + 1) }\n else if (e.key === 'ArrowLeft') { e.preventDefault(); if (isBranch(row.n) && openKeys.value.includes(row.key)) toggle(row.key) }\n else if (e.key === 'Enter') { e.preventDefault(); rowClick(row.n) }\n else if (e.key === ' ') { e.preventDefault(); if (props.checkable && !row.n.disabled && row.n.checkable !== false && !row.n.disableCheckbox) check(row.key) }\n}\nfunction parts(text) {\n const q = props.searchValue\n const s = String(text)\n if (!q) return [{ t: s, hit: false }]\n const i = s.toLowerCase().indexOf(q.toLowerCase())\n if (i < 0) return [{ t: s, hit: false }]\n return [{ t: s.slice(0, i), hit: false }, { t: s.slice(i, i + q.length), hit: true }, { t: s.slice(i + q.length), hit: false }]\n}\nconst dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)\n</script>\n<template>\n <div role=\"tree\" :style=\"{ display: 'flex', flexDirection: 'column', gap: showLine ? 0 : '1px', ...style }\">\n <div v-for=\"row in rows\" :key=\"row.key\" role=\"treeitem\" class=\"kb-tree-row\"\n :tabindex=\"focus === row.key || (!focus && row === rows[0]) ? 0 : -1\"\n :aria-expanded=\"isBranch(row.n) ? openKeys.includes(row.key) : undefined\"\n :aria-selected=\"selected.includes(row.key)\" :aria-disabled=\"row.n.disabled || undefined\"\n :draggable=\"draggable && !row.n.disabled\"\n @dragstart=\"dragKey = row.key\" @dragover=\"onDragOver($event, row.key)\" @dragleave=\"drop = null\"\n @drop.prevent=\"onDrop(row.key)\" @dragend=\"dragKey = null; drop = null\"\n @click=\"rowClick(row.n)\" @keydown=\"keyDown($event, row)\"\n :class=\"{ 'is-disabled': row.n.disabled }\"\n :style=\"{\n minHeight: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '6px',\n padding: showLine ? '0 8px 0 2px' : '5px 8px', paddingLeft: showLine ? '2px' : (8 + row.depth * INDENT) + 'px', boxSizing: 'border-box',\n cursor: row.n.disabled ? 'not-allowed' : 'pointer', opacity: row.n.disabled ? 0.45 : 1,\n background: dropAt(row.key) === 'inside' ? 'var(--soft-bg)' : selected.includes(row.key) ? 'var(--bg-active)' : 'transparent',\n outline: dropAt(row.key) === 'inside' ? '2px solid var(--action-primary)' : 'none',\n outlineOffset: '-2px',\n boxShadow: dropAt(row.key) === 'before' ? 'inset 0 2px 0 var(--action-primary)' : dropAt(row.key) === 'after' ? 'inset 0 -2px 0 var(--action-primary)' : 'none',\n transition: 'var(--t-hover)' }\">\n <template v-if=\"showLine\">\n <span v-for=\"(hasNext, i) in row.trail\" :key=\"i\"\n :style=\"{ width: INDENT + 'px', alignSelf: 'stretch', flexShrink: 0, marginLeft: i === 0 ? '6px' : 0, borderLeft: hasNext ? '1px solid var(--border-subtle)' : '1px solid transparent' }\" />\n <span :style=\"{ width: INDENT + 'px', alignSelf: 'stretch', flexShrink: 0, position: 'relative', marginLeft: row.trail.length ? 0 : '6px' }\">\n <span :style=\"{ position: 'absolute', left: 0, top: 0, height: row.isLast ? '50%' : '100%', borderLeft: '1px solid var(--border-subtle)' }\" />\n <span :style=\"{ position: 'absolute', left: 0, top: '50%', width: (isBranch(row.n) ? 6 : INDENT - 4) + 'px', borderTop: '1px solid var(--border-subtle)' }\" />\n </span>\n </template>\n <span v-if=\"isBranch(row.n)\" @click.stop=\"toggle(row.key)\"\n :style=\"{ width: '14px', height: '14px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', flexShrink: 0, cursor: 'pointer', transform: openKeys.includes(row.key) ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\">\n <Icon :name=\"loading.includes(row.key) ? 'loader' : 'chevron-right'\" :size=\"13\" :class=\"loading.includes(row.key) ? 'kb-tree-spin' : ''\" />\n </span>\n <span v-else :style=\"{ width: showLine ? 0 : '14px', flexShrink: 0 }\" />\n <span v-if=\"checkable && row.n.checkable !== false\" @click.stop style=\"display:inline-flex;flex-shrink:0\">\n <Checkbox size=\"sm\" :checked=\"view.full.has(row.key)\" :indeterminate=\"view.half.has(row.key)\"\n :disabled=\"row.n.disabled || row.n.disableCheckbox\" @change=\"check(row.key)\" />\n </span>\n <span v-if=\"showIcon\" :style=\"{ color: isBranch(row.n) ? 'var(--text-muted)' : docHues[row.n.hue || 'gray'], display: 'inline-flex', flexShrink: 0 }\">\n <Icon :name=\"row.n.icon || (isBranch(row.n) ? (openKeys.includes(row.key) ? 'folder-open' : 'folder') : 'file-text')\" :size=\"15\" />\n </span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', fontWeight: selected.includes(row.key) ? 'var(--weight-medium)' : 'var(--weight-regular)', color: row.n.disabled ? 'var(--text-muted)' : 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }\">\n <template v-for=\"(p, i) in parts(titleOf(row.n))\" :key=\"i\"><mark v-if=\"p.hit\" style=\"background:var(--soft-bg);color:var(--info-fg);font-weight:var(--weight-medium);padding:0;border-radius:2px\">{{ p.t }}</mark><template v-else>{{ p.t }}</template></template>\n </span>\n <span v-if=\"row.n.tag\" style=\"font-family:var(--font-sans);font-size:var(--text-2xs);line-height:1;padding:2px 5px;border-radius:var(--radius-sm);background:var(--bg-inset);color:var(--text-secondary);flex-shrink:0\">{{ row.n.tag }}</span>\n <span v-if=\"row.n.count != null\" style=\"margin-left:auto;font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--text-muted);padding-left:8px\">{{ row.n.count }}</span>\n </div>\n <div v-if=\"!rows.length\" style=\"padding:10px 8px;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-muted)\">No matching items</div>\n </div>\n</template>\n<style scoped>\n.kb-tree-row:not(.is-disabled):hover{background:var(--bg-hover)}\n.kb-tree-row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}\n.kb-tree-spin{animation:kb-tree-spin 900ms linear infinite}\n@keyframes kb-tree-spin{to{transform:rotate(360deg)}}\n</style>\n"});