knobkit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/cell-renderer-CLTRlCa5-DIlwS99c.js +1 -0
  3. package/dist/assets/chart-D8ctp-_1.js +36 -0
  4. package/dist/assets/code-BMuLQBYq.js +2 -0
  5. package/dist/assets/column.service-C6hByxPy-XG9X0y3N.js +1 -0
  6. package/dist/assets/debounce-PCRWZliA-BjJpj_P7.js +32 -0
  7. package/dist/assets/dimension.helpers-CGKwSvw6-D_czicbS.js +1 -0
  8. package/dist/assets/dist-1hsZpGRf.js +23 -0
  9. package/dist/assets/dist-B-y4Etc5.js +1 -0
  10. package/dist/assets/dist-B8BXgMDk.js +10 -0
  11. package/dist/assets/dist-BJlXPLNt.js +1 -0
  12. package/dist/assets/dist-ByhR2UY_.js +1 -0
  13. package/dist/assets/dist-C0bxYHYH.js +2 -0
  14. package/dist/assets/dist-C8dagUDy.js +6 -0
  15. package/dist/assets/dist-CtLpohkg.js +1 -0
  16. package/dist/assets/dist-D00mNtIr.js +1 -0
  17. package/dist/assets/dist-Dh1Dvy3h.js +1 -0
  18. package/dist/assets/dist-DlwQ1Qqm.js +1 -0
  19. package/dist/assets/dist-DtZDI7jp.js +1 -0
  20. package/dist/assets/dist-thZFs69d.js +9 -0
  21. package/dist/assets/edit.utils-Dnnbd0xG-OAxDw8WC.js +1 -0
  22. package/dist/assets/events-BvSmBueA-4kqQ57iN.js +1 -0
  23. package/dist/assets/filter.button-BFwo1uvz-CyvQhOO5.js +1 -0
  24. package/dist/assets/header-cell-renderer-BMmXRsd_-BHbC7fao.js +1 -0
  25. package/dist/assets/index-Db3qZoW5-peeY7EGw.js +1 -0
  26. package/dist/assets/markdown-dDCgur7g.js +29 -0
  27. package/dist/assets/revo-grid.entry-CfI6s-uT.js +1 -0
  28. package/dist/assets/revogr-attribution_7.entry-6fUjzImt.js +1 -0
  29. package/dist/assets/revogr-clipboard_3.entry-DmI7LkER.js +2 -0
  30. package/dist/assets/revogr-data_4.entry-CYZIiXNw.js +1 -0
  31. package/dist/assets/revogr-filter-panel.entry-TmQHTQxw.js +1 -0
  32. package/dist/assets/table-Zn7rpfG-.js +1 -0
  33. package/dist/assets/text-editor-C3RUSwH5-DuDr9wKc.js +1 -0
  34. package/dist/assets/theme.service-BmnDvr6P-DftEgmbe.js +3 -0
  35. package/dist/assets/throttle-CaUDyxyU-Djj__DCp.js +1 -0
  36. package/dist/assets/viewport.helpers-CoCAvmZs-ByVRjjkF.js +1 -0
  37. package/dist/assets/viewport.store-_c579YyM-B_ZSqqka.js +1 -0
  38. package/dist/cli/config.d.ts +5 -0
  39. package/dist/cli/config.js +82 -0
  40. package/dist/cli/index.d.ts +2 -0
  41. package/dist/cli/index.js +77 -0
  42. package/dist/cli/mount.d.ts +2 -0
  43. package/dist/cli/mount.js +26 -0
  44. package/dist/cli/serve.d.ts +1 -0
  45. package/dist/cli/serve.js +21 -0
  46. package/dist/client/app.d.ts +8 -0
  47. package/dist/client/app.js +40 -0
  48. package/dist/client/context.d.ts +2 -0
  49. package/dist/client/context.js +16 -0
  50. package/dist/client/mount.d.ts +3 -0
  51. package/dist/client/mount.js +42 -0
  52. package/dist/client/runtime.d.ts +19 -0
  53. package/dist/client/runtime.js +88 -0
  54. package/dist/client/view.d.ts +12 -0
  55. package/dist/client/view.js +1 -0
  56. package/dist/client/widgets/accordion/index.d.ts +5 -0
  57. package/dist/client/widgets/accordion/index.js +8 -0
  58. package/dist/client/widgets/annotated-image/index.d.ts +8 -0
  59. package/dist/client/widgets/annotated-image/index.js +34 -0
  60. package/dist/client/widgets/audio/index.d.ts +5 -0
  61. package/dist/client/widgets/audio/index.js +5 -0
  62. package/dist/client/widgets/button/index.d.ts +4 -0
  63. package/dist/client/widgets/button/index.js +5 -0
  64. package/dist/client/widgets/chart/index.d.ts +5 -0
  65. package/dist/client/widgets/chart/index.js +23 -0
  66. package/dist/client/widgets/chart/lazy.d.ts +3 -0
  67. package/dist/client/widgets/chart/lazy.js +9 -0
  68. package/dist/client/widgets/chat/index.d.ts +6 -0
  69. package/dist/client/widgets/chat/index.js +77 -0
  70. package/dist/client/widgets/checkbox/index.d.ts +6 -0
  71. package/dist/client/widgets/checkbox/index.js +9 -0
  72. package/dist/client/widgets/checkbox-group/index.d.ts +6 -0
  73. package/dist/client/widgets/checkbox-group/index.js +12 -0
  74. package/dist/client/widgets/code/index.d.ts +5 -0
  75. package/dist/client/widgets/code/index.js +101 -0
  76. package/dist/client/widgets/code/lazy.d.ts +3 -0
  77. package/dist/client/widgets/code/lazy.js +10 -0
  78. package/dist/client/widgets/dropdown/index.d.ts +5 -0
  79. package/dist/client/widgets/dropdown/index.js +9 -0
  80. package/dist/client/widgets/file/index.d.ts +6 -0
  81. package/dist/client/widgets/file/index.js +7 -0
  82. package/dist/client/widgets/frame/index.d.ts +6 -0
  83. package/dist/client/widgets/frame/index.js +11 -0
  84. package/dist/client/widgets/gallery/index.d.ts +6 -0
  85. package/dist/client/widgets/gallery/index.js +8 -0
  86. package/dist/client/widgets/highlighted-text/index.d.ts +7 -0
  87. package/dist/client/widgets/highlighted-text/index.js +20 -0
  88. package/dist/client/widgets/html/index.d.ts +4 -0
  89. package/dist/client/widgets/html/index.js +8 -0
  90. package/dist/client/widgets/image/index.d.ts +4 -0
  91. package/dist/client/widgets/image/index.js +4 -0
  92. package/dist/client/widgets/json/index.d.ts +4 -0
  93. package/dist/client/widgets/json/index.js +4 -0
  94. package/dist/client/widgets/label/index.d.ts +7 -0
  95. package/dist/client/widgets/label/index.js +8 -0
  96. package/dist/client/widgets/layout/index.d.ts +4 -0
  97. package/dist/client/widgets/layout/index.js +7 -0
  98. package/dist/client/widgets/log/index.d.ts +4 -0
  99. package/dist/client/widgets/log/index.js +4 -0
  100. package/dist/client/widgets/mic/index.d.ts +6 -0
  101. package/dist/client/widgets/mic/index.js +70 -0
  102. package/dist/client/widgets/number/index.d.ts +5 -0
  103. package/dist/client/widgets/number/index.js +8 -0
  104. package/dist/client/widgets/output/index.d.ts +6 -0
  105. package/dist/client/widgets/output/index.js +13 -0
  106. package/dist/client/widgets/output/markdown.d.ts +3 -0
  107. package/dist/client/widgets/output/markdown.js +8 -0
  108. package/dist/client/widgets/progress/index.d.ts +6 -0
  109. package/dist/client/widgets/progress/index.js +6 -0
  110. package/dist/client/widgets/radio/index.d.ts +6 -0
  111. package/dist/client/widgets/radio/index.js +10 -0
  112. package/dist/client/widgets/registry.d.ts +2 -0
  113. package/dist/client/widgets/registry.js +69 -0
  114. package/dist/client/widgets/slider/index.d.ts +6 -0
  115. package/dist/client/widgets/slider/index.js +9 -0
  116. package/dist/client/widgets/table/index.d.ts +6 -0
  117. package/dist/client/widgets/table/index.js +72 -0
  118. package/dist/client/widgets/table/lazy.d.ts +3 -0
  119. package/dist/client/widgets/table/lazy.js +16 -0
  120. package/dist/client/widgets/tabs/index.d.ts +5 -0
  121. package/dist/client/widgets/tabs/index.js +10 -0
  122. package/dist/client/widgets/text/index.d.ts +6 -0
  123. package/dist/client/widgets/text/index.js +10 -0
  124. package/dist/client/widgets/upload/index.d.ts +6 -0
  125. package/dist/client/widgets/upload/index.js +16 -0
  126. package/dist/client/widgets/video/index.d.ts +5 -0
  127. package/dist/client/widgets/video/index.js +5 -0
  128. package/dist/client/widgets/webcam/index.d.ts +6 -0
  129. package/dist/client/widgets/webcam/index.js +62 -0
  130. package/dist/client.css +1 -0
  131. package/dist/client.js +11 -0
  132. package/dist/knobkit.browser.css +2 -0
  133. package/dist/knobkit.browser.js +151 -0
  134. package/dist/lib/bound.d.ts +12 -0
  135. package/dist/lib/bound.js +10 -0
  136. package/dist/lib/controls.d.ts +9 -0
  137. package/dist/lib/controls.js +35 -0
  138. package/dist/lib/ctx.d.ts +9 -0
  139. package/dist/lib/ctx.js +22 -0
  140. package/dist/lib/declare.d.ts +18 -0
  141. package/dist/lib/declare.js +43 -0
  142. package/dist/lib/event.d.ts +2 -0
  143. package/dist/lib/event.js +9 -0
  144. package/dist/lib/index.d.ts +10 -0
  145. package/dist/lib/index.js +6 -0
  146. package/dist/lib/knobkit.d.ts +19 -0
  147. package/dist/lib/knobkit.js +48 -0
  148. package/dist/lib/on.d.ts +2 -0
  149. package/dist/lib/on.js +1 -0
  150. package/dist/lib/stream.d.ts +1 -0
  151. package/dist/lib/stream.js +33 -0
  152. package/dist/lib/types.d.ts +48 -0
  153. package/dist/lib/types.js +1 -0
  154. package/dist/lib/widget.d.ts +7 -0
  155. package/dist/lib/widget.js +4 -0
  156. package/dist/lib/widgets/annotated-image.d.ts +14 -0
  157. package/dist/lib/widgets/annotated-image.js +16 -0
  158. package/dist/lib/widgets/audio.d.ts +10 -0
  159. package/dist/lib/widgets/audio.js +13 -0
  160. package/dist/lib/widgets/button.d.ts +11 -0
  161. package/dist/lib/widgets/button.js +17 -0
  162. package/dist/lib/widgets/chart.d.ts +20 -0
  163. package/dist/lib/widgets/chart.js +24 -0
  164. package/dist/lib/widgets/chat.d.ts +26 -0
  165. package/dist/lib/widgets/chat.js +24 -0
  166. package/dist/lib/widgets/checkbox-group.d.ts +4 -0
  167. package/dist/lib/widgets/checkbox-group.js +5 -0
  168. package/dist/lib/widgets/checkbox.d.ts +4 -0
  169. package/dist/lib/widgets/checkbox.js +4 -0
  170. package/dist/lib/widgets/code.d.ts +5 -0
  171. package/dist/lib/widgets/code.js +10 -0
  172. package/dist/lib/widgets/dropdown.d.ts +4 -0
  173. package/dist/lib/widgets/dropdown.js +4 -0
  174. package/dist/lib/widgets/embed.d.ts +5 -0
  175. package/dist/lib/widgets/embed.js +23 -0
  176. package/dist/lib/widgets/file.d.ts +11 -0
  177. package/dist/lib/widgets/file.js +15 -0
  178. package/dist/lib/widgets/frame.d.ts +18 -0
  179. package/dist/lib/widgets/frame.js +25 -0
  180. package/dist/lib/widgets/gallery.d.ts +12 -0
  181. package/dist/lib/widgets/gallery.js +17 -0
  182. package/dist/lib/widgets/highlighted-text.d.ts +12 -0
  183. package/dist/lib/widgets/highlighted-text.js +15 -0
  184. package/dist/lib/widgets/html.d.ts +9 -0
  185. package/dist/lib/widgets/html.js +12 -0
  186. package/dist/lib/widgets/image.d.ts +7 -0
  187. package/dist/lib/widgets/image.js +12 -0
  188. package/dist/lib/widgets/index.d.ts +31 -0
  189. package/dist/lib/widgets/index.js +31 -0
  190. package/dist/lib/widgets/json.d.ts +7 -0
  191. package/dist/lib/widgets/json.js +12 -0
  192. package/dist/lib/widgets/label.d.ts +15 -0
  193. package/dist/lib/widgets/label.js +18 -0
  194. package/dist/lib/widgets/layout.d.ts +21 -0
  195. package/dist/lib/widgets/layout.js +29 -0
  196. package/dist/lib/widgets/log.d.ts +8 -0
  197. package/dist/lib/widgets/log.js +15 -0
  198. package/dist/lib/widgets/mic.d.ts +19 -0
  199. package/dist/lib/widgets/mic.js +28 -0
  200. package/dist/lib/widgets/number.d.ts +5 -0
  201. package/dist/lib/widgets/number.js +4 -0
  202. package/dist/lib/widgets/output.d.ts +10 -0
  203. package/dist/lib/widgets/output.js +13 -0
  204. package/dist/lib/widgets/progress.d.ts +10 -0
  205. package/dist/lib/widgets/progress.js +15 -0
  206. package/dist/lib/widgets/radio.d.ts +4 -0
  207. package/dist/lib/widgets/radio.js +5 -0
  208. package/dist/lib/widgets/slider.d.ts +6 -0
  209. package/dist/lib/widgets/slider.js +6 -0
  210. package/dist/lib/widgets/table.d.ts +32 -0
  211. package/dist/lib/widgets/table.js +36 -0
  212. package/dist/lib/widgets/text.d.ts +4 -0
  213. package/dist/lib/widgets/text.js +4 -0
  214. package/dist/lib/widgets/upload.d.ts +3 -0
  215. package/dist/lib/widgets/upload.js +5 -0
  216. package/dist/lib/widgets/value.d.ts +9 -0
  217. package/dist/lib/widgets/value.js +20 -0
  218. package/dist/lib/widgets/video.d.ts +12 -0
  219. package/dist/lib/widgets/video.js +14 -0
  220. package/dist/lib/widgets/webcam.d.ts +21 -0
  221. package/dist/lib/widgets/webcam.js +29 -0
  222. package/dist/server/context.d.ts +2 -0
  223. package/dist/server/context.js +10 -0
  224. package/dist/server/serve.d.ts +5 -0
  225. package/dist/server/serve.js +131 -0
  226. package/package.json +71 -0
  227. package/src/cli/config.ts +83 -0
  228. package/src/cli/index.ts +82 -0
  229. package/src/cli/mount.ts +25 -0
  230. package/src/cli/serve.ts +22 -0
  231. package/src/client/app.test.tsx +70 -0
  232. package/src/client/app.tsx +62 -0
  233. package/src/client/browser-runtime.test.ts +22 -0
  234. package/src/client/browser.ts +3 -0
  235. package/src/client/context.ts +17 -0
  236. package/src/client/embed.test.tsx +58 -0
  237. package/src/client/entry.tsx +25 -0
  238. package/src/client/mount.test.tsx +36 -0
  239. package/src/client/mount.tsx +48 -0
  240. package/src/client/runtime.test.ts +64 -0
  241. package/src/client/runtime.ts +112 -0
  242. package/src/client/serve-stub.ts +3 -0
  243. package/src/client/styles.css +131 -0
  244. package/src/client/view.ts +16 -0
  245. package/src/client/widgets/accordion/accordion.css +35 -0
  246. package/src/client/widgets/accordion/index.tsx +17 -0
  247. package/src/client/widgets/annotated-image/annotated-image.css +62 -0
  248. package/src/client/widgets/annotated-image/index.tsx +73 -0
  249. package/src/client/widgets/audio/audio.css +6 -0
  250. package/src/client/widgets/audio/index.tsx +6 -0
  251. package/src/client/widgets/button/button.css +25 -0
  252. package/src/client/widgets/button/index.tsx +11 -0
  253. package/src/client/widgets/chart/chart.css +12 -0
  254. package/src/client/widgets/chart/index.tsx +63 -0
  255. package/src/client/widgets/chart/lazy.tsx +15 -0
  256. package/src/client/widgets/chat/chat.css +97 -0
  257. package/src/client/widgets/chat/index.tsx +121 -0
  258. package/src/client/widgets/checkbox/checkbox.css +15 -0
  259. package/src/client/widgets/checkbox/index.tsx +15 -0
  260. package/src/client/widgets/checkbox-group/checkbox-group.css +20 -0
  261. package/src/client/widgets/checkbox-group/index.tsx +22 -0
  262. package/src/client/widgets/code/code.css +31 -0
  263. package/src/client/widgets/code/index.tsx +108 -0
  264. package/src/client/widgets/code/lazy.tsx +16 -0
  265. package/src/client/widgets/dropdown/dropdown.css +0 -0
  266. package/src/client/widgets/dropdown/index.tsx +19 -0
  267. package/src/client/widgets/file/file.css +26 -0
  268. package/src/client/widgets/file/index.tsx +12 -0
  269. package/src/client/widgets/frame/frame.css +17 -0
  270. package/src/client/widgets/frame/index.tsx +15 -0
  271. package/src/client/widgets/gallery/gallery.css +26 -0
  272. package/src/client/widgets/gallery/index.tsx +18 -0
  273. package/src/client/widgets/highlighted-text/highlighted-text.css +21 -0
  274. package/src/client/widgets/highlighted-text/index.tsx +42 -0
  275. package/src/client/widgets/html/index.tsx +8 -0
  276. package/src/client/widgets/image/index.tsx +5 -0
  277. package/src/client/widgets/json/index.tsx +5 -0
  278. package/src/client/widgets/json/json.css +0 -0
  279. package/src/client/widgets/label/index.tsx +20 -0
  280. package/src/client/widgets/label/label.css +39 -0
  281. package/src/client/widgets/layout/index.tsx +14 -0
  282. package/src/client/widgets/log/index.tsx +5 -0
  283. package/src/client/widgets/log/log.css +0 -0
  284. package/src/client/widgets/mic/index.tsx +85 -0
  285. package/src/client/widgets/mic/mic.css +8 -0
  286. package/src/client/widgets/number/index.tsx +10 -0
  287. package/src/client/widgets/number/number.css +0 -0
  288. package/src/client/widgets/output/index.tsx +19 -0
  289. package/src/client/widgets/output/markdown.tsx +12 -0
  290. package/src/client/widgets/output/output.css +75 -0
  291. package/src/client/widgets/progress/index.tsx +14 -0
  292. package/src/client/widgets/progress/progress.css +26 -0
  293. package/src/client/widgets/radio/index.tsx +20 -0
  294. package/src/client/widgets/radio/radio.css +20 -0
  295. package/src/client/widgets/registry.tsx +71 -0
  296. package/src/client/widgets/slider/index.tsx +23 -0
  297. package/src/client/widgets/slider/slider.css +18 -0
  298. package/src/client/widgets/table/index.tsx +95 -0
  299. package/src/client/widgets/table/lazy.tsx +23 -0
  300. package/src/client/widgets/table/table.css +15 -0
  301. package/src/client/widgets/tabs/index.tsx +28 -0
  302. package/src/client/widgets/tabs/tabs.css +30 -0
  303. package/src/client/widgets/text/index.tsx +16 -0
  304. package/src/client/widgets/text/text.css +21 -0
  305. package/src/client/widgets/upload/index.tsx +30 -0
  306. package/src/client/widgets/upload/upload.css +30 -0
  307. package/src/client/widgets/video/index.tsx +10 -0
  308. package/src/client/widgets/video/video.css +8 -0
  309. package/src/client/widgets/webcam/index.tsx +81 -0
  310. package/src/client/widgets/webcam/webcam.css +46 -0
  311. package/src/css.d.ts +1 -0
  312. package/src/env.d.ts +1 -0
  313. package/src/lib/bound.ts +30 -0
  314. package/src/lib/controls.ts +36 -0
  315. package/src/lib/ctx.ts +31 -0
  316. package/src/lib/declare.test.ts +46 -0
  317. package/src/lib/declare.ts +74 -0
  318. package/src/lib/event.ts +12 -0
  319. package/src/lib/index.ts +21 -0
  320. package/src/lib/knobkit.ts +57 -0
  321. package/src/lib/on.ts +3 -0
  322. package/src/lib/stream.ts +38 -0
  323. package/src/lib/types.ts +63 -0
  324. package/src/lib/widget.ts +11 -0
  325. package/src/lib/widgets/annotated-image.ts +34 -0
  326. package/src/lib/widgets/audio.ts +20 -0
  327. package/src/lib/widgets/button.ts +27 -0
  328. package/src/lib/widgets/chart.ts +44 -0
  329. package/src/lib/widgets/chat.ts +43 -0
  330. package/src/lib/widgets/checkbox-group.ts +6 -0
  331. package/src/lib/widgets/checkbox.ts +5 -0
  332. package/src/lib/widgets/code.ts +11 -0
  333. package/src/lib/widgets/dropdown.ts +5 -0
  334. package/src/lib/widgets/embed.ts +26 -0
  335. package/src/lib/widgets/file.ts +23 -0
  336. package/src/lib/widgets/frame.ts +36 -0
  337. package/src/lib/widgets/gallery.ts +29 -0
  338. package/src/lib/widgets/highlighted-text.ts +29 -0
  339. package/src/lib/widgets/html.ts +18 -0
  340. package/src/lib/widgets/image.ts +18 -0
  341. package/src/lib/widgets/index.ts +31 -0
  342. package/src/lib/widgets/json.ts +18 -0
  343. package/src/lib/widgets/label.ts +29 -0
  344. package/src/lib/widgets/layout.ts +47 -0
  345. package/src/lib/widgets/log.ts +22 -0
  346. package/src/lib/widgets/mic.ts +42 -0
  347. package/src/lib/widgets/number.ts +5 -0
  348. package/src/lib/widgets/output.ts +20 -0
  349. package/src/lib/widgets/progress.ts +21 -0
  350. package/src/lib/widgets/radio.ts +6 -0
  351. package/src/lib/widgets/slider.ts +7 -0
  352. package/src/lib/widgets/table.ts +58 -0
  353. package/src/lib/widgets/text.ts +5 -0
  354. package/src/lib/widgets/upload.ts +6 -0
  355. package/src/lib/widgets/value.ts +28 -0
  356. package/src/lib/widgets/video.ts +22 -0
  357. package/src/lib/widgets/webcam.ts +46 -0
  358. package/src/server/context.ts +12 -0
  359. package/src/server/serve.test.ts +121 -0
  360. package/src/server/serve.ts +130 -0
  361. package/tsconfig.base.json +14 -0
@@ -0,0 +1,108 @@
1
+ // note: code.css is imported by ./lazy.tsx (the static entry wrapper), not here, so it lands in
2
+ // client.css rather than a split css chunk serve() wouldn't route.
3
+ import { useEffect, useRef } from "react";
4
+ import { EditorState, Compartment, Annotation, type Extension } from "@codemirror/state";
5
+ import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter } from "@codemirror/view";
6
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
7
+ import { syntaxHighlighting, defaultHighlightStyle, indentOnInput, bracketMatching } from "@codemirror/language";
8
+ import type { ViewProps } from "../../view.js";
9
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
10
+
11
+ // Each grammar is a dynamic import() so Vite splits it into its own chunk under /assets, served on
12
+ // demand — only the languages an app actually uses are ever sent to the browser. (js/ts/jsx/tsx share
13
+ // one chunk since they're the same package.)
14
+ const LANGS: Record<string, () => Promise<Extension>> = {
15
+ javascript: () => import("@codemirror/lang-javascript").then((m) => m.javascript()),
16
+ typescript: () => import("@codemirror/lang-javascript").then((m) => m.javascript({ typescript: true })),
17
+ jsx: () => import("@codemirror/lang-javascript").then((m) => m.javascript({ jsx: true })),
18
+ tsx: () => import("@codemirror/lang-javascript").then((m) => m.javascript({ jsx: true, typescript: true })),
19
+ python: () => import("@codemirror/lang-python").then((m) => m.python()),
20
+ html: () => import("@codemirror/lang-html").then((m) => m.html()),
21
+ css: () => import("@codemirror/lang-css").then((m) => m.css()),
22
+ json: () => import("@codemirror/lang-json").then((m) => m.json()),
23
+ markdown: () => import("@codemirror/lang-markdown").then((m) => m.markdown()),
24
+ sql: () => import("@codemirror/lang-sql").then((m) => m.sql()),
25
+ rust: () => import("@codemirror/lang-rust").then((m) => m.rust()),
26
+ cpp: () => import("@codemirror/lang-cpp").then((m) => m.cpp()),
27
+ xml: () => import("@codemirror/lang-xml").then((m) => m.xml()),
28
+ };
29
+
30
+ // marks our own doc-replacing dispatches so the update listener doesn't echo them back as edits
31
+ const External = Annotation.define<boolean>();
32
+
33
+ const langOf = (name: string): Promise<Extension> => LANGS[name]?.() ?? Promise.resolve([]);
34
+ const editOf = (editable: boolean): Extension => [EditorView.editable.of(editable), EditorState.readOnly.of(!editable)];
35
+
36
+ export function CodeView({ widget, state, emit, set }: ViewProps<ValueWidget<string>, { value: string }>) {
37
+ const host = useRef<HTMLDivElement>(null);
38
+ const view = useRef<EditorView | null>(null);
39
+ const lang = useRef(new Compartment());
40
+ const edit = useRef(new Compartment());
41
+ // the editor is built once; route edits through a ref so it always sees the current props
42
+ const onChange = useRef<(v: string) => void>(() => {});
43
+ onChange.current = (v: string) => {
44
+ set(["value"], v); // local, so reads and the controlled doc reflect typing with no round-trip
45
+ emit(widget.changed(v));
46
+ };
47
+
48
+ const language = (widget.language as string) ?? "";
49
+ const editable = (widget.editable as boolean) ?? true;
50
+
51
+ useEffect(() => {
52
+ const v = new EditorView({
53
+ parent: host.current!,
54
+ state: EditorState.create({
55
+ doc: state.value,
56
+ extensions: [
57
+ lineNumbers(),
58
+ highlightActiveLineGutter(),
59
+ highlightActiveLine(),
60
+ history(),
61
+ indentOnInput(),
62
+ bracketMatching(),
63
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
64
+ keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
65
+ lang.current.of([]), // grammar loads async via the [language] effect below
66
+ edit.current.of(editOf(editable)),
67
+ EditorView.updateListener.of((u) => {
68
+ if (!u.docChanged) return;
69
+ if (u.transactions.some((tr) => tr.annotation(External))) return; // our own sync, not a user edit
70
+ onChange.current(u.state.doc.toString());
71
+ }),
72
+ ],
73
+ }),
74
+ });
75
+ view.current = v;
76
+ return () => v.destroy();
77
+ // mount once; external state / language / editable are synced by the effects below
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, []);
80
+
81
+ // push programmatic value changes (set()/server edit) into the doc without echoing back an edit
82
+ useEffect(() => {
83
+ const v = view.current;
84
+ if (!v) return;
85
+ const cur = v.state.doc.toString();
86
+ if (state.value !== cur) {
87
+ v.dispatch({ changes: { from: 0, to: cur.length, insert: state.value }, annotations: External.of(true) });
88
+ }
89
+ }, [state.value]);
90
+
91
+ // load the grammar chunk, then swap it into the language compartment; guard against a stale load
92
+ // resolving after the language changed again or the editor unmounted
93
+ useEffect(() => {
94
+ let cancelled = false;
95
+ void langOf(language).then((ext) => {
96
+ if (!cancelled) view.current?.dispatch({ effects: lang.current.reconfigure(ext) });
97
+ });
98
+ return () => {
99
+ cancelled = true;
100
+ };
101
+ }, [language]);
102
+
103
+ useEffect(() => {
104
+ view.current?.dispatch({ effects: edit.current.reconfigure(editOf(editable)) });
105
+ }, [editable]);
106
+
107
+ return <div ref={host} className={`pu-code${editable ? "" : " pu-code-ro"}`} />;
108
+ }
@@ -0,0 +1,16 @@
1
+ import "./code.css"; // kept in the entry bundle (client.css) so no /assets css chunk to serve
2
+ import { lazy, Suspense, type ComponentType } from "react";
3
+ import type { ViewProps } from "../../view.js";
4
+
5
+ // CodeMirror (~300KB core + grammar chunks) loads only when an app actually renders a code widget.
6
+ // The registry points at this thin wrapper; the real view + CodeMirror live in a separate chunk.
7
+ // CodeView narrows ViewProps internally; the registry hands it the generic shape, so widen here.
8
+ const Impl = lazy(async () => ({ default: (await import("./index.js")).CodeView as unknown as ComponentType<ViewProps> }));
9
+
10
+ export function CodeView(props: ViewProps) {
11
+ return (
12
+ <Suspense fallback={<div className="pu-code" />}>
13
+ <Impl {...props} />
14
+ </Suspense>
15
+ );
16
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ import type { ViewProps } from "../../view.js";
2
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
3
+
4
+ export function DropdownView({ widget, state, emit, set }: ViewProps<ValueWidget<string>, { value: string }>) {
5
+ const choices = (widget.choices as string[]) ?? [];
6
+ const update = (v: string) => {
7
+ set(["value"], v);
8
+ emit(widget.changed(v));
9
+ };
10
+ return (
11
+ <select className="pu-input" value={state.value} onChange={(e) => update(e.currentTarget.value)}>
12
+ {choices.map((c) => (
13
+ <option key={c} value={c}>
14
+ {c}
15
+ </option>
16
+ ))}
17
+ </select>
18
+ );
19
+ }
@@ -0,0 +1,26 @@
1
+ @layer components {
2
+ .pu-file {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: 8px;
6
+ padding: 9px 14px;
7
+ border: 1px solid var(--pu-border, #e3e6ea);
8
+ border-radius: 8px;
9
+ background: #fafbfc;
10
+ color: var(--pu-text, #1c1f23);
11
+ text-decoration: none;
12
+ cursor: pointer;
13
+ transition: border-color 0.12s, background 0.12s, color 0.12s;
14
+ }
15
+ .pu-file:hover {
16
+ border-color: var(--pu-accent, #2563eb);
17
+ color: var(--pu-accent, #2563eb);
18
+ background: rgba(37, 99, 235, 0.04);
19
+ }
20
+ .pu-file-icon {
21
+ font-size: 16px;
22
+ }
23
+ .pu-file-name {
24
+ font-weight: 500;
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ import "./file.css";
2
+ import type { ViewProps } from "../../view.js";
3
+
4
+ export function FileView({ state }: ViewProps<any, { name: string; url: string }>) {
5
+ if (!state.url) return <div className="pu-output">—</div>;
6
+ return (
7
+ <a className="pu-file" href={state.url} download={state.name || true}>
8
+ <span className="pu-file-icon">⭳</span>
9
+ <span className="pu-file-name">{state.name || "Download"}</span>
10
+ </a>
11
+ );
12
+ }
@@ -0,0 +1,17 @@
1
+ @layer components {
2
+ .pu-frame {
3
+ width: 100%;
4
+ min-height: 360px;
5
+ height: 100%;
6
+ display: block;
7
+ border: 1px solid var(--pu-border);
8
+ border-radius: 8px;
9
+ background: var(--pu-panel);
10
+ }
11
+
12
+ .pu-frame-empty {
13
+ display: grid;
14
+ place-items: center;
15
+ color: var(--pu-muted);
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import "./frame.css";
2
+ import type { ViewProps } from "../../view.js";
3
+
4
+ export function FrameView({ widget, state, emit }: ViewProps<any, { src: string; doc: string }>) {
5
+ const { src, doc } = state;
6
+ if (!src && !doc) return <div className="pu-frame pu-frame-empty">—</div>;
7
+ const onLoad = () => emit((widget.loaded as () => { type: string; payload: unknown })());
8
+ const sandbox = widget.sandbox as string | undefined;
9
+ const title = widget.title as string;
10
+ return doc ? (
11
+ <iframe className="pu-frame" title={title} sandbox={sandbox} srcDoc={doc} onLoad={onLoad} />
12
+ ) : (
13
+ <iframe className="pu-frame" title={title} sandbox={sandbox} src={src} onLoad={onLoad} />
14
+ );
15
+ }
@@ -0,0 +1,26 @@
1
+ @layer components {
2
+ .pu-gallery {
3
+ display: grid;
4
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
5
+ gap: 10px;
6
+ }
7
+ .pu-gallery-cell {
8
+ margin: 0;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 4px;
12
+ }
13
+ .pu-gallery-cell img {
14
+ width: 100%;
15
+ aspect-ratio: 1;
16
+ object-fit: cover;
17
+ border: 1px solid var(--pu-border, #e3e6ea);
18
+ border-radius: 8px;
19
+ display: block;
20
+ }
21
+ .pu-gallery-cell figcaption {
22
+ font-size: 12px;
23
+ color: var(--pu-muted, #6b7280);
24
+ text-align: center;
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ import "./gallery.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { GalleryItem } from "../../../lib/widgets/gallery.js";
4
+
5
+ export function GalleryView({ state }: ViewProps<any, { items: GalleryItem[] }>) {
6
+ const items = state.items ?? [];
7
+ if (items.length === 0) return <div className="pu-output">—</div>;
8
+ return (
9
+ <div className="pu-gallery">
10
+ {items.map((it, i) => (
11
+ <figure key={i} className="pu-gallery-cell">
12
+ <img src={it.src} alt={it.caption ?? ""} />
13
+ {it.caption && <figcaption>{it.caption}</figcaption>}
14
+ </figure>
15
+ ))}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,21 @@
1
+ @layer components {
2
+ .pu-hltext {
3
+ line-height: 2;
4
+ white-space: pre-wrap;
5
+ word-break: break-word;
6
+ }
7
+ .pu-hltext-span {
8
+ border: 1px solid;
9
+ border-radius: 6px;
10
+ padding: 1px 4px;
11
+ margin: 0 1px;
12
+ }
13
+ .pu-hltext-label {
14
+ font-size: 11px;
15
+ font-weight: 650;
16
+ text-transform: uppercase;
17
+ letter-spacing: 0.02em;
18
+ margin-left: 4px;
19
+ vertical-align: 1px;
20
+ }
21
+ }
@@ -0,0 +1,42 @@
1
+ import "./highlighted-text.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { HighlightSpan } from "../../../lib/widgets/highlighted-text.js";
4
+
5
+ // A fixed palette; a label with no entry in `colorMap` gets a stable color by hashing its name, so
6
+ // the same label keeps the same color across renders without the author having to pick one.
7
+ const PALETTE = ["#2563eb", "#16a34a", "#dc2626", "#d97706", "#7c3aed", "#0891b2", "#db2777", "#65a30d"];
8
+
9
+ function colorFor(label: string, colorMap: Record<string, string>): string {
10
+ if (colorMap[label]) return colorMap[label];
11
+ let h = 0;
12
+ for (let i = 0; i < label.length; i++) h = (h * 31 + label.charCodeAt(i)) | 0;
13
+ return PALETTE[Math.abs(h) % PALETTE.length];
14
+ }
15
+
16
+ export function HighlightedTextView({
17
+ state,
18
+ }: ViewProps<any, { value: HighlightSpan[]; colorMap: Record<string, string> }>) {
19
+ const spans = state.value ?? [];
20
+ const colorMap = state.colorMap ?? {};
21
+ if (spans.length === 0) return <div className="pu-output">—</div>;
22
+ return (
23
+ <div className="pu-hltext">
24
+ {spans.map((s, i) =>
25
+ s.label ? (
26
+ <span
27
+ key={i}
28
+ className="pu-hltext-span"
29
+ style={{ background: `${colorFor(s.label, colorMap)}22`, borderColor: colorFor(s.label, colorMap) }}
30
+ >
31
+ {s.text}
32
+ <span className="pu-hltext-label" style={{ color: colorFor(s.label, colorMap) }}>
33
+ {s.label}
34
+ </span>
35
+ </span>
36
+ ) : (
37
+ <span key={i}>{s.text}</span>
38
+ ),
39
+ )}
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,8 @@
1
+ import type { ViewProps } from "../../view.js";
2
+
3
+ // The deliberate escape hatch: render author-supplied markup as-is. Trust is on the author (the same
4
+ // code that runs handlers), so this is no broader than what they already control.
5
+ export function HtmlView({ state }: ViewProps<any, { value: string }>) {
6
+ if (!state.value) return <div className="pu-output">—</div>;
7
+ return <div className="pu-html" dangerouslySetInnerHTML={{ __html: state.value }} />;
8
+ }
@@ -0,0 +1,5 @@
1
+ import type { ViewProps } from "../../view.js";
2
+
3
+ export function ImageView({ state }: ViewProps<any, { src: string }>) {
4
+ return state.src ? <img className="pu-image" src={state.src} alt="" /> : <div className="pu-output">—</div>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { ViewProps } from "../../view.js";
2
+
3
+ export function JsonView({ state }: ViewProps<any, { value: unknown }>) {
4
+ return <pre className="pu-output">{JSON.stringify(state.value, null, 2)}</pre>;
5
+ }
File without changes
@@ -0,0 +1,20 @@
1
+ import "./label.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { LabelClass } from "../../../lib/widgets/label.js";
4
+
5
+ export function LabelView({ state }: ViewProps<any, { label: string; confidences: LabelClass[] }>) {
6
+ const confidences = [...(state.confidences ?? [])].sort((a, b) => b.score - a.score);
7
+ if (!state.label && confidences.length === 0) return <div className="pu-output">—</div>;
8
+ return (
9
+ <div className="pu-label">
10
+ {state.label && <div className="pu-label-top">{state.label}</div>}
11
+ {confidences.map((c) => (
12
+ <div key={c.label} className="pu-label-row">
13
+ <div className="pu-label-bar" style={{ width: `${Math.round(Math.max(0, Math.min(1, c.score)) * 100)}%` }} />
14
+ <span className="pu-label-name">{c.label}</span>
15
+ <span className="pu-label-score">{(c.score * 100).toFixed(1)}%</span>
16
+ </div>
17
+ ))}
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,39 @@
1
+ @layer components {
2
+ .pu-label {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 6px;
6
+ }
7
+ .pu-label-top {
8
+ font-size: 18px;
9
+ font-weight: 650;
10
+ letter-spacing: -0.01em;
11
+ }
12
+ .pu-label-row {
13
+ position: relative;
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 8px;
17
+ padding: 6px 10px;
18
+ border: 1px solid var(--pu-border, #e3e6ea);
19
+ border-radius: 8px;
20
+ overflow: hidden;
21
+ }
22
+ .pu-label-bar {
23
+ position: absolute;
24
+ inset: 0 auto 0 0;
25
+ background: rgba(37, 99, 235, 0.12);
26
+ z-index: 0;
27
+ }
28
+ .pu-label-name {
29
+ position: relative;
30
+ z-index: 1;
31
+ flex: 1;
32
+ }
33
+ .pu-label-score {
34
+ position: relative;
35
+ z-index: 1;
36
+ color: var(--pu-muted, #6b7280);
37
+ font-variant-numeric: tabular-nums;
38
+ }
39
+ }
@@ -0,0 +1,14 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ViewProps } from "../../view.js";
3
+
4
+ export function LayoutView({ widget, state, slot }: ViewProps<any, { items: string[] }>) {
5
+ const dir = (widget.type as string) ?? "col";
6
+ const items = state.items ?? [];
7
+ const style: CSSProperties | undefined =
8
+ dir === "grid" ? { gridTemplateColumns: `repeat(${(widget.cols as number) ?? 2}, minmax(0, 1fr))` } : undefined;
9
+ return (
10
+ <div className={`pu-layout pu-layout-${dir}`} style={style}>
11
+ {items.map((key) => slot(key))}
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { ViewProps } from "../../view.js";
2
+
3
+ export function LogView({ state }: ViewProps<any, { lines: string[] }>) {
4
+ return <pre className="pu-output">{state.lines.join("\n")}</pre>;
5
+ }
File without changes
@@ -0,0 +1,85 @@
1
+ import "./mic.css";
2
+ import { useEffect, useRef } from "react";
3
+ import type { ViewProps } from "../../view.js";
4
+ import type { MicWidget } from "../../../lib/widgets/mic.js";
5
+
6
+ export function MicView({ widget, state, enabled, emit, set }: ViewProps<MicWidget, { live: boolean }>) {
7
+ const streamRef = useRef<MediaStream | null>(null);
8
+ const recRef = useRef<MediaRecorder | null>(null);
9
+ const startingRef = useRef(false);
10
+
11
+ // capture follows `live`; the view sets `live` locally so it starts/stops without a round-trip
12
+ const toggle = (live: boolean): void => {
13
+ set(["live"], live);
14
+ emit(widget.toggled(live));
15
+ };
16
+
17
+ useEffect(() => {
18
+ let cancelled = false;
19
+
20
+ if (state.live && enabled && !streamRef.current && !startingRef.current && navigator.mediaDevices?.getUserMedia) {
21
+ startingRef.current = true;
22
+ void (async () => {
23
+ try {
24
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
25
+ if (cancelled) return stream.getTracks().forEach((t) => t.stop());
26
+ streamRef.current = stream;
27
+ const ac = new AudioContext({ sampleRate: 16000 });
28
+ const record = (): void => {
29
+ if (streamRef.current !== stream) return;
30
+ const chunks: Blob[] = [];
31
+ const rec = new MediaRecorder(stream);
32
+ recRef.current = rec;
33
+ rec.ondataavailable = (e) => chunks.push(e.data);
34
+ rec.onstop = async () => {
35
+ const a = await ac.decodeAudioData(await new Blob(chunks).arrayBuffer());
36
+ emit(widget.clip(a.getChannelData(0)));
37
+ if (widget.every > 0 && streamRef.current === stream) record();
38
+ };
39
+ rec.start();
40
+ if (widget.every > 0) setTimeout(() => rec.stop(), widget.every);
41
+ };
42
+ record();
43
+ } catch (err) {
44
+ console.error(err);
45
+ } finally {
46
+ startingRef.current = false;
47
+ }
48
+ })();
49
+ } else if ((!state.live || !enabled) && streamRef.current) {
50
+ if (recRef.current?.state === "recording") recRef.current.stop();
51
+ streamRef.current.getTracks().forEach((t) => t.stop());
52
+ streamRef.current = null;
53
+ recRef.current = null;
54
+ }
55
+
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [state.live, enabled]);
60
+
61
+ if (!widget.control) return null;
62
+
63
+ const streaming = widget.every > 0;
64
+ const cls = `pu-submit${state.live ? " pu-rec" : ""}`;
65
+
66
+ if (!streaming && widget.hold) {
67
+ return (
68
+ <button
69
+ className={cls}
70
+ disabled={!enabled}
71
+ onPointerDown={() => toggle(true)}
72
+ onPointerUp={() => toggle(false)}
73
+ onPointerLeave={() => state.live && toggle(false)}
74
+ >
75
+ {state.live ? "● Recording…" : "Hold to talk"}
76
+ </button>
77
+ );
78
+ }
79
+
80
+ return (
81
+ <button className={cls} disabled={!enabled} onClick={() => toggle(!state.live)}>
82
+ {state.live ? (streaming ? "● Live" : "● Recording…") : streaming ? "Go live" : "Record"}
83
+ </button>
84
+ );
85
+ }
@@ -0,0 +1,8 @@
1
+ @layer components {
2
+ .pu-submit.pu-rec {
3
+ background: #dc2626;
4
+ }
5
+ .pu-submit.pu-rec:hover {
6
+ background: #b91c1c;
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { ViewProps } from "../../view.js";
2
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
3
+
4
+ export function NumberView({ widget, state, emit, set }: ViewProps<ValueWidget<number>, { value: number }>) {
5
+ const update = (v: number) => {
6
+ set(["value"], v);
7
+ emit(widget.changed(v));
8
+ };
9
+ return <input className="pu-input" type="number" min={widget.min as number} max={widget.max as number} value={state.value} onChange={(e) => update(e.currentTarget.valueAsNumber)} />;
10
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ import "./output.css";
2
+ import { lazy, Suspense } from "react";
3
+ import type { ViewProps } from "../../view.js";
4
+ import type { OutputWidget } from "../../../lib/widgets/output.js";
5
+
6
+ // The markdown renderer (react-markdown + remark-gfm) loads as its own /assets chunk, only when an
7
+ // output actually uses format="markdown" — plain text output stays weightless in the entry bundle.
8
+ const Markdown = lazy(() => import("./markdown.js"));
9
+
10
+ export function OutputView({ widget, state }: ViewProps<OutputWidget, { value: string }>) {
11
+ const format = (widget.format as string) ?? "text";
12
+ const plain = <div className="pu-output">{state.value || "—"}</div>;
13
+ if (format !== "markdown") return plain;
14
+ return (
15
+ <Suspense fallback={plain}>
16
+ <Markdown value={state.value} />
17
+ </Suspense>
18
+ );
19
+ }
@@ -0,0 +1,12 @@
1
+ import ReactMarkdown from "react-markdown";
2
+ import remarkGfm from "remark-gfm";
3
+
4
+ // Lazy-loaded render path for output({ format: "markdown" }). react-markdown escapes raw HTML by
5
+ // default, so this is XSS-safe; an html format would be a separate, deliberate opt-in.
6
+ export default function Markdown({ value }: { value: string }) {
7
+ return (
8
+ <div className="pu-md">
9
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{value}</ReactMarkdown>
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,75 @@
1
+ @layer components {
2
+ .pu-output {
3
+ min-height: 42px;
4
+ margin: 0;
5
+ padding: 10px 12px;
6
+ border: 1px solid var(--pu-border, #e3e6ea);
7
+ border-radius: 8px;
8
+ background: #fafbfc;
9
+ color: var(--pu-text, #1c1f23);
10
+ white-space: pre-wrap;
11
+ word-break: break-word;
12
+ font: inherit;
13
+ }
14
+ pre.pu-output {
15
+ overflow-x: auto;
16
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
17
+ font-size: 13px;
18
+ line-height: 1.5;
19
+ }
20
+
21
+ /* rendered markdown (output format="markdown") — same box chrome as .pu-output, prose inside */
22
+ .pu-md {
23
+ min-height: 42px;
24
+ padding: 2px 12px;
25
+ border: 1px solid var(--pu-border, #e3e6ea);
26
+ border-radius: 8px;
27
+ background: #fafbfc;
28
+ color: var(--pu-text, #1c1f23);
29
+ }
30
+ .pu-md > :first-child {
31
+ margin-top: 0;
32
+ }
33
+ .pu-md > :last-child {
34
+ margin-bottom: 0;
35
+ }
36
+ .pu-md h1,
37
+ .pu-md h2,
38
+ .pu-md h3 {
39
+ line-height: 1.25;
40
+ margin: 1em 0 0.4em;
41
+ }
42
+ .pu-md p,
43
+ .pu-md ul,
44
+ .pu-md ol,
45
+ .pu-md blockquote,
46
+ .pu-md pre,
47
+ .pu-md table {
48
+ margin: 0.6em 0;
49
+ }
50
+ .pu-md code {
51
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
52
+ font-size: 0.9em;
53
+ background: rgba(0, 0, 0, 0.05);
54
+ padding: 0.1em 0.35em;
55
+ border-radius: 4px;
56
+ }
57
+ .pu-md pre {
58
+ background: rgba(0, 0, 0, 0.05);
59
+ padding: 10px 12px;
60
+ border-radius: 8px;
61
+ overflow-x: auto;
62
+ }
63
+ .pu-md pre code {
64
+ background: none;
65
+ padding: 0;
66
+ }
67
+ .pu-md a {
68
+ color: var(--pu-accent, #2563eb);
69
+ }
70
+ .pu-md th,
71
+ .pu-md td {
72
+ border: 1px solid var(--pu-border, #e3e6ea);
73
+ padding: 4px 8px;
74
+ }
75
+ }