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,14 @@
1
+ import "./progress.css";
2
+ import type { ViewProps } from "../../view.js";
3
+
4
+ export function ProgressView({ state }: ViewProps<any, { value: number; label: string }>) {
5
+ const pct = Math.round(Math.max(0, Math.min(1, state.value ?? 0)) * 100);
6
+ return (
7
+ <div className="pu-progress">
8
+ <div className="pu-progress-track">
9
+ <div className="pu-progress-fill" style={{ width: `${pct}%` }} />
10
+ </div>
11
+ <span className="pu-progress-label">{state.label || `${pct}%`}</span>
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,26 @@
1
+ @layer components {
2
+ .pu-progress {
3
+ display: flex;
4
+ align-items: center;
5
+ gap: 10px;
6
+ }
7
+ .pu-progress-track {
8
+ flex: 1;
9
+ height: 8px;
10
+ border-radius: 999px;
11
+ background: var(--pu-border, #e3e6ea);
12
+ overflow: hidden;
13
+ }
14
+ .pu-progress-fill {
15
+ height: 100%;
16
+ background: var(--pu-accent, #2563eb);
17
+ border-radius: 999px;
18
+ transition: width 0.2s ease;
19
+ }
20
+ .pu-progress-label {
21
+ color: var(--pu-muted, #6b7280);
22
+ font-variant-numeric: tabular-nums;
23
+ min-width: 3ch;
24
+ text-align: right;
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ import "./radio.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
4
+
5
+ export function RadioView({ widget, state, emit, set }: ViewProps<ValueWidget<string>, { value: string }>) {
6
+ const choices = (widget.choices as string[]) ?? [];
7
+ const update = (v: string) => {
8
+ set(["value"], v);
9
+ emit(widget.changed(v));
10
+ };
11
+ return (
12
+ <div className="pu-radio">
13
+ {choices.map((c) => (
14
+ <label key={c} className="pu-radio-opt">
15
+ <input type="radio" checked={state.value === c} onChange={() => update(c)} /> {c}
16
+ </label>
17
+ ))}
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
1
+ @layer components {
2
+ .pu-radio {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 8px;
6
+ }
7
+ .pu-radio-opt {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: 8px;
11
+ cursor: pointer;
12
+ user-select: none;
13
+ }
14
+ .pu-radio-opt input {
15
+ width: 16px;
16
+ height: 16px;
17
+ accent-color: var(--pu-accent, #2563eb);
18
+ cursor: pointer;
19
+ }
20
+ }
@@ -0,0 +1,71 @@
1
+ import type { WidgetView } from "../view.js";
2
+ import { TextView } from "./text/index.js";
3
+ import { NumberView } from "./number/index.js";
4
+ import { SliderView } from "./slider/index.js";
5
+ import { CodeView } from "./code/lazy.js";
6
+ import { TableView } from "./table/lazy.js";
7
+ import { ChartView } from "./chart/lazy.js";
8
+ import { DropdownView } from "./dropdown/index.js";
9
+ import { CheckboxView } from "./checkbox/index.js";
10
+ import { CheckboxGroupView } from "./checkbox-group/index.js";
11
+ import { RadioView } from "./radio/index.js";
12
+ import { GalleryView } from "./gallery/index.js";
13
+ import { VideoView } from "./video/index.js";
14
+ import { LabelView } from "./label/index.js";
15
+ import { HighlightedTextView } from "./highlighted-text/index.js";
16
+ import { AnnotatedImageView } from "./annotated-image/index.js";
17
+ import { FileView } from "./file/index.js";
18
+ import { ProgressView } from "./progress/index.js";
19
+ import { HtmlView } from "./html/index.js";
20
+ import { FrameView } from "./frame/index.js";
21
+ import { UploadView } from "./upload/index.js";
22
+ import { ImageView } from "./image/index.js";
23
+ import { ButtonView } from "./button/index.js";
24
+ import { MicView } from "./mic/index.js";
25
+ import { ChatView } from "./chat/index.js";
26
+ import { OutputView } from "./output/index.js";
27
+ import { JsonView } from "./json/index.js";
28
+ import { LogView } from "./log/index.js";
29
+ import { AudioView } from "./audio/index.js";
30
+ import { WebcamView } from "./webcam/index.js";
31
+ import { LayoutView } from "./layout/index.js";
32
+ import { TabsView } from "./tabs/index.js";
33
+ import { AccordionView } from "./accordion/index.js";
34
+
35
+ // the only place that maps a widget's `type` to its React view
36
+ export const VIEWS: Record<string, WidgetView> = {
37
+ text: TextView,
38
+ number: NumberView,
39
+ slider: SliderView,
40
+ code: CodeView,
41
+ table: TableView,
42
+ chart: ChartView,
43
+ dropdown: DropdownView,
44
+ checkbox: CheckboxView,
45
+ checkboxGroup: CheckboxGroupView,
46
+ radio: RadioView,
47
+ gallery: GalleryView,
48
+ video: VideoView,
49
+ label: LabelView,
50
+ highlightedText: HighlightedTextView,
51
+ annotatedImage: AnnotatedImageView,
52
+ file: FileView,
53
+ progress: ProgressView,
54
+ html: HtmlView,
55
+ frame: FrameView,
56
+ upload: UploadView,
57
+ image: ImageView,
58
+ button: ButtonView,
59
+ mic: MicView,
60
+ chat: ChatView,
61
+ output: OutputView,
62
+ json: JsonView,
63
+ log: LogView,
64
+ audio: AudioView,
65
+ webcam: WebcamView,
66
+ row: LayoutView,
67
+ col: LayoutView,
68
+ grid: LayoutView,
69
+ tabs: TabsView,
70
+ accordion: AccordionView,
71
+ };
@@ -0,0 +1,23 @@
1
+ import "./slider.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
4
+
5
+ export function SliderView({ widget, state, emit, set }: ViewProps<ValueWidget<number>, { value: number }>) {
6
+ const update = (v: number) => {
7
+ set(["value"], v); // local, so the track and read-back reflect the drag with no round-trip
8
+ emit(widget.changed(v));
9
+ };
10
+ return (
11
+ <div className="pu-slider">
12
+ <input
13
+ type="range"
14
+ min={widget.min as number}
15
+ max={widget.max as number}
16
+ step={widget.step as number}
17
+ value={state.value}
18
+ onChange={(e) => update(e.currentTarget.valueAsNumber)}
19
+ />
20
+ <output className="pu-slider-val">{state.value}</output>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,18 @@
1
+ @layer components {
2
+ .pu-slider {
3
+ display: flex;
4
+ align-items: center;
5
+ gap: 12px;
6
+ }
7
+ .pu-slider input[type="range"] {
8
+ flex: 1;
9
+ accent-color: var(--pu-accent, #2563eb);
10
+ cursor: pointer;
11
+ }
12
+ .pu-slider-val {
13
+ min-width: 3ch;
14
+ text-align: right;
15
+ font-variant-numeric: tabular-nums;
16
+ color: var(--pu-muted, #6b7280);
17
+ }
18
+ }
@@ -0,0 +1,95 @@
1
+ // table.css is imported by ./lazy.tsx (the static entry wrapper) so it lands in client.css, not a
2
+ // split css chunk serve() wouldn't route.
3
+ import { useMemo } from "react";
4
+ import { RevoGrid, type AfterEditEvent, type ColumnRegular, type DataType } from "@revolist/react-datagrid";
5
+ import type { ViewProps } from "../../view.js";
6
+ import type { TableWidget, Column, Row } from "../../../lib/widgets/table.js";
7
+
8
+ // "compact" theme metrics (from revo-grid-style.css): header row 45px, data row 32px. Used to fit the
9
+ // grid to its content up to maxHeight, then scroll — matching Gradio's max_height behavior.
10
+ const HEADER_PX = 45;
11
+ const ROW_PX = 32;
12
+
13
+ // A column with no explicit width defaults to one that fits its *header* — the header is usually the
14
+ // wider constraint for compact tables (e.g. "Δ from mean" over numeric cells), and unlike content-based
15
+ // auto-size it stays stable as data changes. Measured with canvas for proportional-font accuracy.
16
+ const HEADER_FONT = "600 14px Helvetica, Arial, sans-serif"; // compact theme header
17
+ const COL_PAD = 34; // header cell padding + room for the sort arrow
18
+ const MIN_COL = 80;
19
+ const MAX_COL = 240;
20
+ let measureCtx: CanvasRenderingContext2D | null | undefined;
21
+ function headerWidth(label: string): number {
22
+ if (measureCtx === undefined) measureCtx = document.createElement("canvas").getContext("2d");
23
+ let w: number;
24
+ if (measureCtx) {
25
+ measureCtx.font = HEADER_FONT;
26
+ w = measureCtx.measureText(label).width;
27
+ } else {
28
+ w = label.length * 8; // canvas unavailable: rough fallback
29
+ }
30
+ return Math.max(MIN_COL, Math.min(MAX_COL, Math.ceil(w) + COL_PAD));
31
+ }
32
+
33
+ export function TableView({ widget, state, emit, set }: ViewProps<TableWidget, { columns: Column[]; rows: Row[] }>) {
34
+ const editable = (widget.editable as boolean) ?? false;
35
+ const maxHeight = (widget.maxHeight as number) ?? 500;
36
+ // fit to content (header + rows), capped at maxHeight; +2 leaves room for a horizontal scrollbar so
37
+ // a fitting grid doesn't sprout a vertical one
38
+ const height = Math.min(maxHeight, HEADER_PX + state.rows.length * ROW_PX + 2);
39
+
40
+ // RevoGrid warns that columns/source need stable references; the store hands us a fresh state object
41
+ // only on a real edit (edits are immutable), so memoizing on those references is both stable and correct.
42
+ const columns = useMemo<ColumnRegular[]>(
43
+ () =>
44
+ state.columns.map((c) => ({
45
+ prop: c.key,
46
+ name: c.label ?? c.key,
47
+ sortable: true,
48
+ readonly: !editable,
49
+ size: c.width ?? headerWidth(c.label ?? c.key),
50
+ })),
51
+ [state.columns, editable],
52
+ );
53
+ // clone each row: RevoGrid mutates its own source in place on edit — we must not let it touch the
54
+ // store's state objects. The real change is applied through the store via onAfteredit below.
55
+ const source = useMemo<DataType[]>(() => state.rows.map((r) => ({ ...r })), [state.rows]);
56
+
57
+ const apply = (row: number, key: string, value: unknown): void => {
58
+ // a range/paste commit reports every cell in the selection; skip the ones that didn't change so
59
+ // handlers don't see no-op edits
60
+ if (state.rows[row]?.[key] === value) return;
61
+ set(["rows", row, key], value); // local edit by path; reads reflect it immediately
62
+ emit(widget.edited({ row, key, value }));
63
+ };
64
+
65
+ const onAfteredit = (e: CustomEvent<AfterEditEvent>): void => {
66
+ const d = e.detail as Record<string, unknown>;
67
+ if (d && "data" in d) {
68
+ // range / paste edit: { data: { [rowIndex]: changedRowModel } }
69
+ for (const [ri, model] of Object.entries(d.data as Record<string, Record<string, unknown>>)) {
70
+ for (const [key, value] of Object.entries(model)) apply(Number(ri), key, value);
71
+ }
72
+ } else if (d) {
73
+ // single cell: val is the editor's new value, not yet in the model
74
+ apply(d.rowIndex as number, d.prop as string, d.val);
75
+ }
76
+ };
77
+
78
+ return (
79
+ <div className="pu-table">
80
+ <RevoGrid
81
+ columns={columns}
82
+ source={source}
83
+ readonly={!editable}
84
+ range={true}
85
+ resize={true}
86
+ rowHeaders={true}
87
+ theme="compact"
88
+ stretch={true}
89
+ hideAttribution={true}
90
+ style={{ height }}
91
+ onAfteredit={onAfteredit}
92
+ />
93
+ </div>
94
+ );
95
+ }
@@ -0,0 +1,23 @@
1
+ import "./table.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
+ // RevoGrid is a heavy web-component grid; load it only when an app actually renders a table widget.
6
+ // TableView narrows ViewProps internally; the registry hands it the generic shape, so widen here.
7
+ const Impl = lazy(async () => ({ default: (await import("./index.js")).TableView as unknown as ComponentType<ViewProps> }));
8
+
9
+ // mirror index.tsx's fit math so the loading placeholder is the grid's real height — no layout jump
10
+ // when the chunk resolves (can't import the constants from index.js without pulling it into the entry)
11
+ const fitHeight = (props: ViewProps): number => {
12
+ const rows = (props.state as { rows?: unknown[] }).rows ?? [];
13
+ const maxHeight = (props.widget as { maxHeight?: number }).maxHeight ?? 500;
14
+ return Math.min(maxHeight, 45 + rows.length * 32 + 2);
15
+ };
16
+
17
+ export function TableView(props: ViewProps) {
18
+ return (
19
+ <Suspense fallback={<div className="pu-table" style={{ height: fitHeight(props) }} />}>
20
+ <Impl {...props} />
21
+ </Suspense>
22
+ );
23
+ }
@@ -0,0 +1,15 @@
1
+ @layer components {
2
+ .pu-table {
3
+ border: 1px solid var(--pu-border, #e3e6ea);
4
+ border-radius: 8px;
5
+ overflow: hidden;
6
+ }
7
+ .pu-table revo-grid {
8
+ display: block;
9
+ width: 100%;
10
+ /* RevoGrid floors the host at min-height:300px; we drive an exact fit height inline, so drop the
11
+ floor. !important is needed because RevoGrid injects that rule unlayered, which outranks our
12
+ @layer styles regardless of specificity. */
13
+ min-height: 0 !important;
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ import "./tabs.css";
2
+ import { useState } from "react";
3
+ import type { ViewProps } from "../../view.js";
4
+
5
+ export function TabsView({ widget, state, slot }: ViewProps<any, { items: string[] }>) {
6
+ const items = state.items ?? [];
7
+ const labels = (widget.labels as string[]) ?? [];
8
+ const [active, setActive] = useState(0);
9
+ const current = Math.min(active, Math.max(0, items.length - 1));
10
+ return (
11
+ <div className="pu-tabs">
12
+ <div className="pu-tabs-bar" role="tablist">
13
+ {items.map((_key, i) => (
14
+ <button
15
+ key={i}
16
+ role="tab"
17
+ aria-selected={i === current}
18
+ className={`pu-tab${i === current ? " pu-tab-active" : ""}`}
19
+ onClick={() => setActive(i)}
20
+ >
21
+ {labels[i] ?? `Tab ${i + 1}`}
22
+ </button>
23
+ ))}
24
+ </div>
25
+ <div className="pu-tabs-panel">{items[current] != null ? slot(items[current]) : null}</div>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,30 @@
1
+ @layer components {
2
+ .pu-tabs {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 14px;
6
+ }
7
+ .pu-tabs-bar {
8
+ display: flex;
9
+ gap: 4px;
10
+ border-bottom: 1px solid var(--pu-border, #e3e6ea);
11
+ }
12
+ .pu-tab {
13
+ appearance: none;
14
+ background: none;
15
+ border: none;
16
+ padding: 8px 14px;
17
+ margin-bottom: -1px;
18
+ font: inherit;
19
+ color: var(--pu-muted, #6b7280);
20
+ border-bottom: 2px solid transparent;
21
+ cursor: pointer;
22
+ }
23
+ .pu-tab:hover {
24
+ color: var(--pu-text, #1c1f23);
25
+ }
26
+ .pu-tab-active {
27
+ color: var(--pu-accent, #2563eb);
28
+ border-bottom-color: var(--pu-accent, #2563eb);
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ import "./text.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
4
+
5
+ export function TextView({ widget, state, emit, set }: ViewProps<ValueWidget<string>, { value: string }>) {
6
+ const lines = (widget.lines as number) ?? 1;
7
+ const update = (v: string) => {
8
+ set(["value"], v); // local, so the controlled input reflects typing and reads see it
9
+ emit(widget.changed(v));
10
+ };
11
+ return lines > 1 ? (
12
+ <textarea className="pu-input" rows={lines} placeholder={widget.placeholder as string} value={state.value} onChange={(e) => update(e.currentTarget.value)} />
13
+ ) : (
14
+ <input className="pu-input" placeholder={widget.placeholder as string} value={state.value} onChange={(e) => update(e.currentTarget.value)} />
15
+ );
16
+ }
@@ -0,0 +1,21 @@
1
+ @layer components {
2
+ .pu-input {
3
+ width: 100%;
4
+ padding: 9px 11px;
5
+ border: 1px solid var(--pu-border, #e3e6ea);
6
+ border-radius: 8px;
7
+ font: inherit;
8
+ color: inherit;
9
+ background: #fff;
10
+ outline: none;
11
+ transition: border-color 0.12s, box-shadow 0.12s;
12
+ }
13
+ .pu-input:focus {
14
+ border-color: var(--pu-accent, #2563eb);
15
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
16
+ }
17
+ textarea.pu-input {
18
+ resize: vertical;
19
+ line-height: 1.5;
20
+ }
21
+ }
@@ -0,0 +1,30 @@
1
+ import "./upload.css";
2
+ import type { ViewProps } from "../../view.js";
3
+ import type { ValueWidget } from "../../../lib/widgets/value.js";
4
+
5
+ export function UploadView({ widget, state, emit, set }: ViewProps<ValueWidget<string | null>, { value: string | null }>) {
6
+ return (
7
+ <div className="pu-upload">
8
+ <label className="pu-upload-drop">
9
+ <input
10
+ type="file"
11
+ hidden
12
+ accept={widget.accept as string}
13
+ onChange={(e) => {
14
+ const f = e.currentTarget.files?.[0];
15
+ if (!f) return;
16
+ const r = new FileReader();
17
+ r.onload = () => {
18
+ const url = String(r.result);
19
+ set(["value"], url);
20
+ emit(widget.changed(url));
21
+ };
22
+ r.readAsDataURL(f);
23
+ }}
24
+ />
25
+ <span>{state.value ? "Choose another image" : "Choose an image…"}</span>
26
+ </label>
27
+ {state.value && <img className="pu-image" src={state.value} alt="" />}
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,30 @@
1
+ @layer components {
2
+ .pu-upload {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 12px;
6
+ }
7
+ .pu-image {
8
+ display: block;
9
+ max-width: 100%;
10
+ border: 1px solid var(--pu-border, #e3e6ea);
11
+ border-radius: 8px;
12
+ }
13
+ .pu-upload-drop {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ padding: 22px 14px;
18
+ border: 1.5px dashed var(--pu-border, #e3e6ea);
19
+ border-radius: 8px;
20
+ color: var(--pu-muted, #6b7280);
21
+ background: #fafbfc;
22
+ cursor: pointer;
23
+ transition: border-color 0.12s, background 0.12s, color 0.12s;
24
+ }
25
+ .pu-upload-drop:hover {
26
+ border-color: var(--pu-accent, #2563eb);
27
+ color: var(--pu-accent, #2563eb);
28
+ background: rgba(37, 99, 235, 0.04);
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ import "./video.css";
2
+ import type { ViewProps } from "../../view.js";
3
+
4
+ export function VideoView({ widget, state }: ViewProps<any, { src: string }>) {
5
+ return state.src ? (
6
+ <video src={state.src} controls autoPlay={Boolean(widget.autoplay)} loop={Boolean(widget.loop)} muted={Boolean(widget.autoplay)} />
7
+ ) : (
8
+ <div className="pu-output">—</div>
9
+ );
10
+ }
@@ -0,0 +1,8 @@
1
+ @layer components {
2
+ video {
3
+ width: 100%;
4
+ display: block;
5
+ border-radius: 8px;
6
+ background: #000;
7
+ }
8
+ }
@@ -0,0 +1,81 @@
1
+ import "./webcam.css";
2
+ import { useEffect, useRef } from "react";
3
+ import type { ViewProps } from "../../view.js";
4
+ import type { WebcamWidget } from "../../../lib/widgets/webcam.js";
5
+
6
+ export function WebcamView({ widget, state, enabled, emit, set }: ViewProps<WebcamWidget, { live: boolean }>) {
7
+ const streamRef = useRef<MediaStream | null>(null);
8
+ const videoRef = useRef<HTMLVideoElement | null>(null);
9
+ const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
10
+ const startingRef = useRef(false);
11
+
12
+ // capture follows `live`; the view sets `live` locally so it starts/stops without a round-trip
13
+ const toggle = (live: boolean): void => {
14
+ set(["live"], live);
15
+ emit(widget.toggled(live));
16
+ };
17
+
18
+ useEffect(() => {
19
+ let cancelled = false;
20
+
21
+ if (state.live && enabled && !streamRef.current && !startingRef.current && navigator.mediaDevices?.getUserMedia) {
22
+ startingRef.current = true;
23
+ void (async () => {
24
+ try {
25
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: widget.facing } });
26
+ if (cancelled) return stream.getTracks().forEach((t) => t.stop());
27
+ streamRef.current = stream;
28
+ const video = videoRef.current!;
29
+ video.srcObject = stream;
30
+ await video.play();
31
+
32
+ if (widget.every > 0) {
33
+ const canvas = document.createElement("canvas");
34
+ timerRef.current = setInterval(() => {
35
+ if (streamRef.current !== stream || video.readyState < 2 || !video.videoWidth) return;
36
+ canvas.width = video.videoWidth;
37
+ canvas.height = video.videoHeight;
38
+ canvas.getContext("2d")?.drawImage(video, 0, 0);
39
+ emit(widget.frame(canvas.toDataURL("image/jpeg", 0.8)));
40
+ }, widget.every);
41
+ }
42
+ } catch (err) {
43
+ console.error(err);
44
+ } finally {
45
+ startingRef.current = false;
46
+ }
47
+ })();
48
+ } else if ((!state.live || !enabled) && streamRef.current) {
49
+ if (timerRef.current) clearInterval(timerRef.current);
50
+ timerRef.current = null;
51
+ streamRef.current.getTracks().forEach((t) => t.stop());
52
+ streamRef.current = null;
53
+ if (videoRef.current) videoRef.current.srcObject = null;
54
+ }
55
+
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [state.live, enabled]);
60
+
61
+ const mirror = widget.facing === "user" ? " pu-webcam-mirror" : "";
62
+
63
+ return (
64
+ <div className="pu-webcam">
65
+ {widget.preview ? (
66
+ <div className="pu-webcam-stage">
67
+ <video ref={videoRef} className={`pu-webcam-video${mirror}`} muted playsInline />
68
+ {!state.live && <div className="pu-webcam-placeholder">Camera off</div>}
69
+ </div>
70
+ ) : (
71
+ // kept alive off-screen so capture still has a frame source, but nothing renders
72
+ <video ref={videoRef} className="pu-webcam-offscreen" muted playsInline />
73
+ )}
74
+ {widget.control && (
75
+ <button className={`pu-submit${state.live ? " pu-rec" : ""}`} disabled={!enabled} onClick={() => toggle(!state.live)}>
76
+ {state.live ? "● Stop camera" : "Go live"}
77
+ </button>
78
+ )}
79
+ </div>
80
+ );
81
+ }
@@ -0,0 +1,46 @@
1
+ @layer components {
2
+ .pu-webcam {
3
+ display: flex;
4
+ flex-direction: column;
5
+ align-items: flex-start;
6
+ gap: 8px;
7
+ }
8
+ .pu-webcam-stage {
9
+ position: relative;
10
+ width: 480px;
11
+ max-width: 100%;
12
+ aspect-ratio: 4 / 3;
13
+ background: #111418;
14
+ border-radius: 12px;
15
+ overflow: hidden;
16
+ }
17
+ .pu-webcam-video {
18
+ position: absolute;
19
+ inset: 0;
20
+ width: 100%;
21
+ height: 100%;
22
+ object-fit: cover;
23
+ }
24
+ .pu-webcam-mirror {
25
+ transform: scaleX(-1);
26
+ }
27
+ .pu-webcam-offscreen {
28
+ position: fixed;
29
+ top: 0;
30
+ left: 0;
31
+ width: 1px;
32
+ height: 1px;
33
+ opacity: 0;
34
+ pointer-events: none;
35
+ }
36
+ .pu-webcam-placeholder {
37
+ position: absolute;
38
+ inset: 0;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ color: #6b7280;
43
+ font-size: 14px;
44
+ letter-spacing: 0.02em;
45
+ }
46
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";
package/src/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";