@scaffold-ui-kit/preview 0.0.0-stage → 2.3.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 (350) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1868 -2
  3. package/application/host/bootstrap.ts +43 -0
  4. package/application/host/main.ts +31 -0
  5. package/application/host/shell-host.ts +47 -0
  6. package/application/index.html +12 -0
  7. package/application/lib/base-url.ts +15 -0
  8. package/application/lib/no-code-block.ts +7 -0
  9. package/application/lib/no-setup.ts +3 -0
  10. package/application/lib/no-views.ts +6 -0
  11. package/application/preview/code/code.html +12 -0
  12. package/application/preview/code/main.ts +89 -0
  13. package/application/preview/globals.ts +254 -0
  14. package/application/preview/html/main.ts +93 -0
  15. package/application/preview/html/preview.html +12 -0
  16. package/application/preview/react/main.tsx +146 -0
  17. package/application/preview/react/preview.html +12 -0
  18. package/application/preview/svelte/Preview.svelte +104 -0
  19. package/application/preview/svelte/main.ts +13 -0
  20. package/application/preview/svelte/preview.html +12 -0
  21. package/application/preview/svelte/story-props.ts +172 -0
  22. package/application/preview/view/main.ts +124 -0
  23. package/application/preview/view/view.html +12 -0
  24. package/application/preview/vue/main.ts +145 -0
  25. package/application/preview/vue/preview.html +12 -0
  26. package/application/shells/charcell/components/Application/index.css +37 -0
  27. package/application/shells/charcell/components/Application/index.tsx +65 -0
  28. package/application/shells/charcell/components/Bar/index.css +68 -0
  29. package/application/shells/charcell/components/Bar/index.tsx +60 -0
  30. package/application/shells/charcell/components/CodePane/index.css +60 -0
  31. package/application/shells/charcell/components/CodePane/index.tsx +113 -0
  32. package/application/shells/charcell/components/ComponentStage/index.css +26 -0
  33. package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
  34. package/application/shells/charcell/components/Control/index.css +20 -0
  35. package/application/shells/charcell/components/Control/index.tsx +118 -0
  36. package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
  37. package/application/shells/charcell/components/Description/index.css +6 -0
  38. package/application/shells/charcell/components/Description/index.tsx +24 -0
  39. package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
  40. package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
  41. package/application/shells/charcell/components/EditorPanel/index.css +48 -0
  42. package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
  43. package/application/shells/charcell/components/EventsRow/index.css +42 -0
  44. package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
  45. package/application/shells/charcell/components/HeadingRow/index.css +11 -0
  46. package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
  47. package/application/shells/charcell/components/Hint/index.css +16 -0
  48. package/application/shells/charcell/components/Hint/index.tsx +81 -0
  49. package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
  50. package/application/shells/charcell/components/KeyHints/index.css +27 -0
  51. package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
  52. package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
  53. package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
  54. package/application/shells/charcell/components/Markdown/index.tsx +16 -0
  55. package/application/shells/charcell/components/Navigation/index.css +13 -0
  56. package/application/shells/charcell/components/Navigation/index.tsx +98 -0
  57. package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
  58. package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
  59. package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
  60. package/application/shells/charcell/components/PaneStatus/index.css +7 -0
  61. package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
  62. package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
  63. package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
  64. package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
  65. package/application/shells/charcell/components/PreviewPane/index.css +127 -0
  66. package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
  67. package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
  68. package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
  69. package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
  70. package/application/shells/charcell/components/Select/index.tsx +48 -0
  71. package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
  72. package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
  73. package/application/shells/charcell/components/StageBar/index.css +7 -0
  74. package/application/shells/charcell/components/StageBar/index.tsx +109 -0
  75. package/application/shells/charcell/components/StageSplit/index.css +11 -0
  76. package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
  77. package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
  78. package/application/shells/charcell/components/ViewControls/index.css +15 -0
  79. package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
  80. package/application/shells/charcell/components/ViewHeading/index.css +13 -0
  81. package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
  82. package/application/shells/charcell/components/ViewStage/index.css +9 -0
  83. package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
  84. package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
  85. package/application/shells/charcell/configuration.ts +45 -0
  86. package/application/shells/charcell/controller-context.ts +27 -0
  87. package/application/shells/charcell/fit-labels.ts +42 -0
  88. package/application/shells/charcell/html-warnings.ts +18 -0
  89. package/application/shells/charcell/index.tsx +82 -0
  90. package/application/shells/charcell/page-loading.html +57 -0
  91. package/application/shells/charcell/placement.ts +24 -0
  92. package/application/shells/charcell/shell-options.ts +23 -0
  93. package/application/shells/charcell/shell.css +37 -0
  94. package/application/shells/charcell/shell.tsx +25 -0
  95. package/application/shells/charcell/split-reset.ts +47 -0
  96. package/application/shells/charcell/stacked-rows.ts +117 -0
  97. package/application/shells/charcell/stage-toggles.ts +93 -0
  98. package/application/types/manifest.d.ts +6 -0
  99. package/application/types/preview-configuration.d.ts +10 -0
  100. package/application/types/preview-shell.d.ts +7 -0
  101. package/application/types/react-registry.d.ts +15 -0
  102. package/application/types/vue-registry.d.ts +6 -0
  103. package/dist/build.d.ts +24 -0
  104. package/dist/build.js +34 -0
  105. package/dist/check.d.ts +48 -0
  106. package/dist/check.js +781 -0
  107. package/dist/cli.d.ts +2 -0
  108. package/dist/cli.js +88 -0
  109. package/dist/context.d.ts +179 -0
  110. package/dist/context.js +783 -0
  111. package/dist/controller/catalog.d.ts +339 -0
  112. package/dist/controller/catalog.js +367 -0
  113. package/dist/controller/create-preview-controller.d.ts +532 -0
  114. package/dist/controller/create-preview-controller.js +1707 -0
  115. package/dist/controller/editor-tree.d.ts +161 -0
  116. package/dist/controller/editor-tree.js +373 -0
  117. package/dist/controller/html-source.d.ts +62 -0
  118. package/dist/controller/html-source.js +61 -0
  119. package/dist/controller/index.d.ts +9 -0
  120. package/dist/controller/index.js +12 -0
  121. package/dist/controller/persistence.d.ts +191 -0
  122. package/dist/controller/persistence.js +315 -0
  123. package/dist/controller/regeneration.d.ts +30 -0
  124. package/dist/controller/regeneration.js +27 -0
  125. package/dist/controller/source.d.ts +80 -0
  126. package/dist/controller/source.js +236 -0
  127. package/dist/controller/store.d.ts +27 -0
  128. package/dist/controller/store.js +40 -0
  129. package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
  130. package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
  131. package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
  132. package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
  133. package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
  134. package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
  135. package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
  136. package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
  137. package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
  138. package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
  139. package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
  140. package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
  141. package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
  142. package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
  143. package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
  144. package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
  145. package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
  146. package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
  147. package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
  148. package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
  149. package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
  150. package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
  151. package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
  152. package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
  153. package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
  154. package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
  155. package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
  156. package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
  157. package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
  158. package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
  159. package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
  160. package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
  161. package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
  162. package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
  163. package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
  164. package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
  165. package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
  166. package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
  167. package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
  168. package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
  169. package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
  170. package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
  171. package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
  172. package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
  173. package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
  174. package/dist/declarations/src/controller/catalog.d.ts +339 -0
  175. package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
  176. package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
  177. package/dist/declarations/src/controller/html-source.d.ts +62 -0
  178. package/dist/declarations/src/controller/index.d.ts +9 -0
  179. package/dist/declarations/src/controller/persistence.d.ts +191 -0
  180. package/dist/declarations/src/controller/regeneration.d.ts +30 -0
  181. package/dist/declarations/src/controller/source.d.ts +80 -0
  182. package/dist/declarations/src/controller/store.d.ts +27 -0
  183. package/dist/declarations/src/emit/common.d.ts +103 -0
  184. package/dist/declarations/src/emit/manifest.d.ts +138 -0
  185. package/dist/declarations/src/emit/source.d.ts +87 -0
  186. package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
  187. package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
  188. package/dist/declarations/src/lib/naming.d.ts +29 -0
  189. package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
  190. package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
  191. package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
  192. package/dist/declarations/src/lib/types.d.ts +741 -0
  193. package/dist/declarations/src/options.d.ts +224 -0
  194. package/dist/declarations/src/protocol.d.ts +541 -0
  195. package/dist/declarations/src/react-view.d.ts +22 -0
  196. package/dist/declarations/src/react.d.ts +17 -0
  197. package/dist/declarations/src/shell.d.ts +99 -0
  198. package/dist/declarations/src/views.d.ts +152 -0
  199. package/dist/development-server.d.ts +121 -0
  200. package/dist/development-server.js +331 -0
  201. package/dist/emit/common.d.ts +103 -0
  202. package/dist/emit/common.js +170 -0
  203. package/dist/emit/html-source.d.ts +46 -0
  204. package/dist/emit/html-source.js +322 -0
  205. package/dist/emit/manifest.d.ts +138 -0
  206. package/dist/emit/manifest.js +74 -0
  207. package/dist/emit/react.d.ts +16 -0
  208. package/dist/emit/react.js +210 -0
  209. package/dist/emit/scratch.d.ts +40 -0
  210. package/dist/emit/scratch.js +116 -0
  211. package/dist/emit/source.d.ts +87 -0
  212. package/dist/emit/source.js +789 -0
  213. package/dist/emit/svelte.d.ts +9 -0
  214. package/dist/emit/svelte.js +177 -0
  215. package/dist/emit/vue.d.ts +2 -0
  216. package/dist/emit/vue.js +152 -0
  217. package/dist/enumerate.d.ts +2 -0
  218. package/dist/enumerate.js +162 -0
  219. package/dist/generate.d.ts +43 -0
  220. package/dist/generate.js +302 -0
  221. package/dist/html-stylesheet.d.ts +43 -0
  222. package/dist/html-stylesheet.js +145 -0
  223. package/dist/index.d.ts +18 -0
  224. package/dist/index.js +21 -0
  225. package/dist/landing.d.ts +16 -0
  226. package/dist/landing.js +45 -0
  227. package/dist/lib/exported-types.d.ts +42 -0
  228. package/dist/lib/exported-types.js +86 -0
  229. package/dist/lib/html-attributes.d.ts +2 -0
  230. package/dist/lib/html-attributes.js +111 -0
  231. package/dist/lib/html-warnings.d.ts +19 -0
  232. package/dist/lib/html-warnings.js +79 -0
  233. package/dist/lib/jsx-roots.d.ts +22 -0
  234. package/dist/lib/jsx-roots.js +243 -0
  235. package/dist/lib/module-of-source.d.ts +18 -0
  236. package/dist/lib/module-of-source.js +24 -0
  237. package/dist/lib/naming.d.ts +29 -0
  238. package/dist/lib/naming.js +56 -0
  239. package/dist/lib/parse-type.d.ts +32 -0
  240. package/dist/lib/parse-type.js +103 -0
  241. package/dist/lib/react-spelling.d.ts +68 -0
  242. package/dist/lib/react-spelling.js +197 -0
  243. package/dist/lib/scenario-story.d.ts +41 -0
  244. package/dist/lib/scenario-story.js +84 -0
  245. package/dist/lib/slot-fill.d.ts +15 -0
  246. package/dist/lib/slot-fill.js +92 -0
  247. package/dist/lib/template-files.d.ts +14 -0
  248. package/dist/lib/template-files.js +25 -0
  249. package/dist/lib/types.d.ts +741 -0
  250. package/dist/lib/types.js +13 -0
  251. package/dist/lib/void-elements.d.ts +7 -0
  252. package/dist/lib/void-elements.js +13 -0
  253. package/dist/load.d.ts +37 -0
  254. package/dist/load.js +82 -0
  255. package/dist/model.d.ts +44 -0
  256. package/dist/model.js +464 -0
  257. package/dist/options.d.ts +224 -0
  258. package/dist/options.js +223 -0
  259. package/dist/prop-types.d.ts +26 -0
  260. package/dist/prop-types.js +102 -0
  261. package/dist/props-from.d.ts +84 -0
  262. package/dist/props-from.js +510 -0
  263. package/dist/protocol.d.ts +541 -0
  264. package/dist/protocol.js +819 -0
  265. package/dist/react-view.d.ts +22 -0
  266. package/dist/react-view.js +36 -0
  267. package/dist/react.d.ts +17 -0
  268. package/dist/react.js +23 -0
  269. package/dist/sass-importers.d.ts +26 -0
  270. package/dist/sass-importers.js +32 -0
  271. package/dist/shell-acceptance.d.ts +21 -0
  272. package/dist/shell-acceptance.js +37 -0
  273. package/dist/shell-page.d.ts +36 -0
  274. package/dist/shell-page.js +90 -0
  275. package/dist/shell-peers.d.ts +38 -0
  276. package/dist/shell-peers.js +61 -0
  277. package/dist/shell.d.ts +99 -0
  278. package/dist/shell.js +52 -0
  279. package/dist/shells/charcell/page.d.ts +8 -0
  280. package/dist/shells/charcell/page.js +69 -0
  281. package/dist/story-source.d.ts +113 -0
  282. package/dist/story-source.js +271 -0
  283. package/dist/story.d.ts +5 -0
  284. package/dist/story.js +28 -0
  285. package/dist/testing.d.ts +218 -0
  286. package/dist/testing.js +284 -0
  287. package/dist/views.d.ts +152 -0
  288. package/dist/views.js +7 -0
  289. package/dist/vite-configuration.d.ts +114 -0
  290. package/dist/vite-configuration.js +578 -0
  291. package/package.json +123 -3
  292. package/src/build.ts +51 -0
  293. package/src/check.ts +1062 -0
  294. package/src/cli.ts +123 -0
  295. package/src/context.ts +1168 -0
  296. package/src/controller/catalog.ts +719 -0
  297. package/src/controller/create-preview-controller.ts +2683 -0
  298. package/src/controller/editor-tree.ts +530 -0
  299. package/src/controller/html-source.ts +114 -0
  300. package/src/controller/index.ts +105 -0
  301. package/src/controller/persistence.ts +403 -0
  302. package/src/controller/regeneration.ts +55 -0
  303. package/src/controller/source.ts +333 -0
  304. package/src/controller/store.ts +55 -0
  305. package/src/development-server.ts +442 -0
  306. package/src/emit/common.ts +201 -0
  307. package/src/emit/html-source.ts +402 -0
  308. package/src/emit/manifest.ts +241 -0
  309. package/src/emit/react.ts +263 -0
  310. package/src/emit/scratch.ts +175 -0
  311. package/src/emit/source.ts +1094 -0
  312. package/src/emit/svelte.ts +230 -0
  313. package/src/emit/vue.ts +194 -0
  314. package/src/enumerate.ts +220 -0
  315. package/src/generate.ts +443 -0
  316. package/src/html-stylesheet.ts +173 -0
  317. package/src/index.ts +60 -0
  318. package/src/landing.ts +58 -0
  319. package/src/lib/exported-types.ts +114 -0
  320. package/src/lib/html-attributes.ts +113 -0
  321. package/src/lib/html-warnings.ts +81 -0
  322. package/src/lib/jsx-roots.ts +238 -0
  323. package/src/lib/module-of-source.ts +26 -0
  324. package/src/lib/naming.ts +65 -0
  325. package/src/lib/parse-type.ts +143 -0
  326. package/src/lib/react-spelling.ts +235 -0
  327. package/src/lib/scenario-story.ts +97 -0
  328. package/src/lib/slot-fill.ts +102 -0
  329. package/src/lib/template-files.ts +31 -0
  330. package/src/lib/types.ts +778 -0
  331. package/src/lib/void-elements.ts +15 -0
  332. package/src/load.ts +144 -0
  333. package/src/model.ts +568 -0
  334. package/src/options.ts +435 -0
  335. package/src/prop-types.ts +133 -0
  336. package/src/props-from.ts +678 -0
  337. package/src/protocol.ts +1135 -0
  338. package/src/react-view.ts +45 -0
  339. package/src/react.ts +34 -0
  340. package/src/sass-importers.ts +45 -0
  341. package/src/shell-acceptance.ts +45 -0
  342. package/src/shell-page.ts +133 -0
  343. package/src/shell-peers.ts +70 -0
  344. package/src/shell.ts +194 -0
  345. package/src/shells/charcell/page.ts +82 -0
  346. package/src/story-source.ts +391 -0
  347. package/src/story.ts +42 -0
  348. package/src/testing.ts +497 -0
  349. package/src/views.ts +183 -0
  350. package/src/vite-configuration.ts +675 -0
@@ -0,0 +1,789 @@
1
+ // Print a story as the source a consumer would write: the component with its
2
+ // props and slot content, importing from the kit's package entry per target.
3
+ // The registry emitters (react.ts, vue.ts, svelte.ts) print what the preview
4
+ // mounts - one line per story, props spread from the controls; this prints the
5
+ // same story as readable, standalone code for a docs page.
6
+ import { reactFill, reactProps, styleProperties, } from '../lib/react-spelling.js';
7
+ import { singleJsxRoot } from '../lib/jsx-roots.js';
8
+ import { hasFragileWhitespace, svelteText, vueJunction, vueSourceText, } from './common.js';
9
+ import { collectParts, collectSlotComponents } from '../lib/slot-fill.js';
10
+ const INDENT = ' ';
11
+ /** An opening tag longer than this puts each attribute on its own line. */
12
+ const MAXIMUM_WIDTH = 80;
13
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
14
+ /** Escapes a JavaScript single-quoted string literal needs: a line break or
15
+ * other control character inside one is a syntax error, and U+2028/U+2029
16
+ * read as line breaks in some parsers. */
17
+ const STRING_ESCAPES = {
18
+ '\\': '\\\\',
19
+ "'": "\\'",
20
+ '\n': '\\n',
21
+ '\r': '\\r',
22
+ '\t': '\\t',
23
+ };
24
+ function stringLiteral(value) {
25
+ const escaped = value.replace(/[\\'\u0000-\u001f\u2028\u2029]/g, (character) => STRING_ESCAPES[character] ??
26
+ `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
27
+ return `'${escaped}'`;
28
+ }
29
+ /** A JavaScript expression for a JSON value, with single-quoted strings. */
30
+ function expression(value) {
31
+ if (value === undefined)
32
+ return 'undefined';
33
+ if (value === null)
34
+ return 'null';
35
+ if (typeof value === 'string')
36
+ return stringLiteral(value);
37
+ if (Array.isArray(value))
38
+ return `[${value.map(expression).join(', ')}]`;
39
+ if (typeof value === 'object') {
40
+ const entries = Object.entries(value).map(([key, entry]) => `${IDENTIFIER.test(key) ? key : expression(key)}: ${expression(entry)}`);
41
+ return entries.length ? `{ ${entries.join(', ')} }` : '{}';
42
+ }
43
+ return String(value);
44
+ }
45
+ /** The narrowest a value is wrapped to, however deep its tag sits. */
46
+ const MINIMUM_VALUE_WIDTH = 40;
47
+ /**
48
+ * A JavaScript expression for a JSON value, as {@link expression} writes it
49
+ * while it fits in `width`; a longer array or object is broken over lines,
50
+ * one element or entry to a line with a trailing comma, each element broken
51
+ * in turn while it is still too long. An array of numbers, booleans or
52
+ * `null` fills each line instead.
53
+ */
54
+ function wrappedExpression(value, width) {
55
+ const single = expression(value);
56
+ if (single.length <= width || typeof value !== 'object' || value === null)
57
+ return single;
58
+ const [open, close] = Array.isArray(value) ? ['[', ']'] : ['{', '}'];
59
+ const inner = Math.max(width - INDENT.length, MINIMUM_VALUE_WIDTH);
60
+ if (Array.isArray(value) &&
61
+ value.length > 0 &&
62
+ value.every((element) => element === null ||
63
+ typeof element === 'number' ||
64
+ typeof element === 'boolean')) {
65
+ const lines = [];
66
+ for (const element of value.map((element) => `${expression(element)},`)) {
67
+ const last = lines.length - 1;
68
+ if (last >= 0 && lines[last].length + 1 + element.length <= inner)
69
+ lines[last] += ` ${element}`;
70
+ else
71
+ lines.push(element);
72
+ }
73
+ return [open, ...lines.map((line) => `${INDENT}${line}`), close].join('\n');
74
+ }
75
+ const items = Array.isArray(value)
76
+ ? value.map((element) => wrappedExpression(element, inner))
77
+ : Object.entries(value).map(([key, entry]) => {
78
+ const name = IDENTIFIER.test(key) ? key : expression(key);
79
+ return `${name}: ${wrappedExpression(entry, inner - name.length - 2)}`;
80
+ });
81
+ if (items.length === 0)
82
+ return single;
83
+ const body = items.map((item) => `${INDENT}${item.replace(/\n/g, `\n${INDENT}`)},`);
84
+ return [open, ...body, close].join('\n');
85
+ }
86
+ /**
87
+ * The width a prop's value has on its own line under an opening tag at
88
+ * `depth`: the line's width less the attribute's column and its name, and
89
+ * never narrower than {@link MINIMUM_VALUE_WIDTH}.
90
+ */
91
+ function valueWidth(attributeHead, depth) {
92
+ return Math.max(MAXIMUM_WIDTH - INDENT.length * (depth + 1) - attributeHead.length - 1, MINIMUM_VALUE_WIDTH);
93
+ }
94
+ /** A plain-CSS `style` string as a React style object. */
95
+ function styleObject(css) {
96
+ return expression(styleProperties(css));
97
+ }
98
+ function attributeList(dialect, props, html, depth, bound = new Map()) {
99
+ return Object.entries(props ?? {}).flatMap(([name, value]) => bound.get(name) ?? [dialect.attribute(name, value, html, depth)]);
100
+ }
101
+ /** An opening tag's lines: attributes on one line, or one per line past the
102
+ * width or when an attribute spans lines itself (a React prop holding JSX),
103
+ * each of its lines indented under the tag. */
104
+ function openTag(pad, name, attributes, selfClosing) {
105
+ const close = selfClosing ? ' />' : '>';
106
+ const spansLines = attributes.some((attribute) => attribute.includes('\n'));
107
+ const single = `${pad}<${name}${attributes.length ? ` ${attributes.join(' ')}` : ''}${close}`;
108
+ if (!spansLines &&
109
+ (single.length <= MAXIMUM_WIDTH || attributes.length === 0))
110
+ return [single];
111
+ return [
112
+ `${pad}<${name}`,
113
+ ...attributes.flatMap((attribute) => attribute.split('\n').map((line) => `${pad}${INDENT}${line}`)),
114
+ `${pad}${selfClosing ? '/>' : '>'}`,
115
+ ];
116
+ }
117
+ /** Lines for a slot fill at `depth`. */
118
+ function fillLines(dialect, fill, depth) {
119
+ const pad = INDENT.repeat(depth);
120
+ switch (fill.kind) {
121
+ case 'text':
122
+ return [`${pad}${dialect.text(fill.value)}`];
123
+ case 'raw':
124
+ return [`${pad}${dialect.raw(fill)}`];
125
+ case 'part':
126
+ return dialect.part(fill, depth);
127
+ case 'scoped':
128
+ // Scoped content belongs to a named slot; nested elsewhere it renders nothing.
129
+ return [];
130
+ case 'fragment':
131
+ return fragmentLines(dialect, fill, depth);
132
+ case 'html':
133
+ return elementLines(dialect, fill.tag, attributeList(dialect, fill.props, true, depth), fill.children, {}, depth);
134
+ case 'element':
135
+ return elementLines(dialect, fill.component, attributeList(dialect, fill.props, false, depth), fill.children, fill.slots ?? {}, depth);
136
+ }
137
+ }
138
+ /** A fragment's children, those of a fragment inside it in its place, and
139
+ * empty text and scoped content, which renders nothing there, left out. */
140
+ function flattenedChildren(fill) {
141
+ if (fill.kind === 'fragment')
142
+ return fill.children.flatMap(flattenedChildren);
143
+ if (fill.kind === 'scoped')
144
+ return [];
145
+ return fill.kind === 'text' && fill.value === '' ? [] : [fill];
146
+ }
147
+ /** Whether a sibling starts (`'start'`) or ends (`'end'`) with whitespace:
148
+ * text and raw content by their own, anything else never. */
149
+ function edgeSpace(dialect, fill, edge) {
150
+ const content = fill.kind === 'text'
151
+ ? fill.value
152
+ : fill.kind === 'raw'
153
+ ? dialect.raw(fill)
154
+ : undefined;
155
+ if (content === undefined)
156
+ return false;
157
+ return edge === 'start' ? /^\s/.test(content) : /\s$/.test(content);
158
+ }
159
+ /**
160
+ * Lines for a fragment's children at `depth`, one sibling after another,
161
+ * printed so the whitespace between text and the siblings beside it reads as
162
+ * the story gives it. Where the story has whitespace there, the text's edge
163
+ * loses it for the line break, which Vue and Svelte read as a space, and
164
+ * React keeps it as `{' '}`, as JSX drops whitespace at a line's edges.
165
+ * Where it has none, the two share a line in Vue and Svelte (and two texts
166
+ * in React), so no break adds a space. Whitespace alone between two
167
+ * siblings is `{' '}` in React, and one space between them on one line in
168
+ * Vue and Svelte.
169
+ */
170
+ function fragmentLines(dialect, fill, depth) {
171
+ const pad = INDENT.repeat(depth);
172
+ const children = flattenedChildren(fill);
173
+ const lines = [];
174
+ let glueNext = false;
175
+ children.forEach((child, index) => {
176
+ const previous = children[index - 1];
177
+ const next = children[index + 1];
178
+ let glue = glueNext;
179
+ glueNext = false;
180
+ // Siblings that touch share a line where a break would read as a space:
181
+ // text or raw content beside anything in Vue and Svelte, two elements
182
+ // or components in Svelte alone, and two texts in React.
183
+ const flowing = (sibling) => sibling.kind === 'text' || sibling.kind === 'raw';
184
+ if (previous !== undefined &&
185
+ !glue &&
186
+ !edgeSpace(dialect, previous, 'end') &&
187
+ !edgeSpace(dialect, child, 'start') &&
188
+ (dialect.lineBreakIsSpace
189
+ ? flowing(previous) || flowing(child) || dialect.elementBreakIsSpace
190
+ : previous.kind === 'text' && child.kind === 'text'))
191
+ glue = true;
192
+ let childLines;
193
+ if (child.kind === 'text') {
194
+ const before = previous !== undefined && /^\s/.test(child.value);
195
+ const after = next !== undefined && /\s$/.test(child.value);
196
+ const trimmed = child.value.slice(before ? child.value.length - child.value.trimStart().length : 0, after ? child.value.trimEnd().length : child.value.length);
197
+ if (trimmed === '' && (before || after)) {
198
+ // Whitespace alone between siblings; at an edge, Vue and Svelte
199
+ // drop it as they do whitespace at an element's edge.
200
+ if (!dialect.lineBreakIsSpace)
201
+ childLines = [`${pad}{' '}`];
202
+ else if (previous !== undefined && next !== undefined) {
203
+ childLines = [' '];
204
+ glue = true;
205
+ glueNext = true;
206
+ }
207
+ else
208
+ childLines = [];
209
+ }
210
+ else if (dialect.lineBreakIsSpace) {
211
+ childLines = [`${pad}${dialect.text(trimmed)}`];
212
+ }
213
+ else {
214
+ const space = "{' '}";
215
+ childLines = [
216
+ `${pad}${before ? space : ''}${dialect.text(trimmed)}${after ? space : ''}`,
217
+ ];
218
+ }
219
+ }
220
+ else
221
+ childLines = fillLines(dialect, child, depth);
222
+ if (childLines.length === 0)
223
+ return;
224
+ if (glue && lines.length > 0) {
225
+ const first = childLines[0] === ' ' ? ' ' : childLines[0].trimStart();
226
+ const last = dialect.junction(lines.pop(), first);
227
+ lines.push(`${last}${first}`, ...childLines.slice(1));
228
+ }
229
+ else
230
+ lines.push(...childLines);
231
+ });
232
+ return lines;
233
+ }
234
+ /** Lines for an element with default and named content. */
235
+ function elementLines(dialect, name, attributes, children, named, depth) {
236
+ const pad = INDENT.repeat(depth);
237
+ const allAttributes = [...attributes];
238
+ const namedLines = [];
239
+ // Scoped content for the default slot is printed as a scoped slot is: the
240
+ // function child, `#default`, the `children` snippet.
241
+ const scopedChildren = children?.kind === 'scoped' ? children : undefined;
242
+ const slotEntries = [
243
+ ...(scopedChildren
244
+ ? [['default', scopedChildren]]
245
+ : []),
246
+ ...Object.entries(named),
247
+ ];
248
+ for (const [slot, fill] of slotEntries) {
249
+ const rendered = dialect.namedSlot(slot, fill, depth + 1);
250
+ allAttributes.push(...(rendered.attributes ?? []));
251
+ namedLines.push(...(rendered.lines ?? []));
252
+ }
253
+ const childLines = children && !scopedChildren ? fillLines(dialect, children, depth + 1) : [];
254
+ const content = [...childLines, ...namedLines];
255
+ if (content.length === 0)
256
+ return openTag(pad, name, allAttributes, true);
257
+ const open = openTag(pad, name, allAttributes, false);
258
+ // A lone text child stays on a one-line element's line.
259
+ if (content.length === 1 &&
260
+ namedLines.length === 0 &&
261
+ children?.kind === 'text' &&
262
+ open.length === 1) {
263
+ const line = `${open[0]}${content[0].trim()}</${name}>`;
264
+ if (line.length <= MAXIMUM_WIDTH)
265
+ return [line];
266
+ }
267
+ return [...open, ...content, `${pad}</${name}>`];
268
+ }
269
+ const react = {
270
+ lineBreakIsSpace: false,
271
+ elementBreakIsSpace: false,
272
+ attribute(name, value, html, depth) {
273
+ const key = html && name === 'class' ? 'className' : name;
274
+ if (html && name === 'style' && typeof value === 'string')
275
+ return `style={${styleObject(value)}}`;
276
+ if (value === true)
277
+ return key;
278
+ // A quoted JSX attribute reads `&` as a character reference.
279
+ if (typeof value === 'string' && !/["\n&]/.test(value))
280
+ return `${key}="${value}"`;
281
+ return `${key}={${wrappedExpression(value, valueWidth(`${key}={`, depth))}}`;
282
+ },
283
+ // JSX reads braces and angle brackets as code, and `&` as a reference,
284
+ // and drops whitespace at the edge of a line of text.
285
+ text: (value) => /[{}<>&]/.test(value) || hasFragileWhitespace(value)
286
+ ? `{${expression(value)}}`
287
+ : value,
288
+ junction: (line) => line,
289
+ namedSlot(slot, fill, depth) {
290
+ if (fill.kind === 'scoped') {
291
+ if (!fill.react)
292
+ return {};
293
+ const head = fill.reactScope === 'positional'
294
+ ? `(${fill.parameters.join(', ')})`
295
+ : `({ ${fill.parameters.join(', ')} })`;
296
+ // Sibling roots or text are no arrow body until a fragment holds them.
297
+ const content = singleJsxRoot(fill.react);
298
+ const body = content.includes('\n') ? `(${content})` : content;
299
+ // A render function named `children` reads as the element's child.
300
+ if (fill.slot.react === 'children')
301
+ return { lines: [`${INDENT.repeat(depth)}{${head} => ${body}}`] };
302
+ return { attributes: [`${fill.slot.react}={${head} => ${body}}`] };
303
+ }
304
+ if (fill.kind === 'text')
305
+ return { attributes: [react.attribute(slot, fill.value, false, depth)] };
306
+ // A raw body in a React prop: sibling roots, or text, are one expression
307
+ // in a fragment.
308
+ if (fill.kind === 'raw') {
309
+ if (fill.react.trim() === '')
310
+ return {};
311
+ const content = singleJsxRoot(fill.react);
312
+ const body = content.includes('\n') ? `(${content})` : content;
313
+ return { attributes: [`${slot}={${body}}`] };
314
+ }
315
+ // Content in a React prop: text alone is a string, one line stays on the
316
+ // prop's line, and more becomes an indented block, its lines relative to
317
+ // the prop so the tag can indent them; several siblings, or text beside
318
+ // anything, are held in a fragment.
319
+ const flattened = flattenedChildren(fill);
320
+ if (flattened.length === 0)
321
+ return {};
322
+ if (flattened.length === 1 && flattened[0].kind === 'text')
323
+ return {
324
+ attributes: [react.attribute(slot, flattened[0].value, false, depth)],
325
+ };
326
+ const lines = fillLines(react, fill, 0);
327
+ const siblings = flattened.length > 1;
328
+ if (lines.length === 1)
329
+ return {
330
+ attributes: [
331
+ siblings
332
+ ? `${slot}={<>${lines[0].trim()}</>}`
333
+ : `${slot}={${lines[0].trim()}}`,
334
+ ],
335
+ };
336
+ const block = siblings
337
+ ? ['<>', ...lines.map((line) => `${INDENT}${line}`), '</>']
338
+ : lines;
339
+ return {
340
+ attributes: [
341
+ [`${slot}={`, ...block.map((line) => `${INDENT}${line}`), '}'].join('\n'),
342
+ ],
343
+ };
344
+ },
345
+ raw: (fill) => fill.react,
346
+ part: (fill, depth) => componentCall(react, fill, depth),
347
+ };
348
+ const vue = {
349
+ lineBreakIsSpace: true,
350
+ elementBreakIsSpace: false,
351
+ attribute(name, value, _html, depth) {
352
+ if (value === true)
353
+ return name;
354
+ if (typeof value === 'string' && !/["\n]/.test(value))
355
+ return `${name}="${value}"`;
356
+ return `:${name}="${wrappedExpression(value, valueWidth(`:${name}="`, depth)).replace(/"/g, '&quot;')}"`;
357
+ },
358
+ text: vueSourceText,
359
+ // A `{` before content starting with one would open an interpolation.
360
+ junction: vueJunction,
361
+ namedSlot(slot, fill, depth) {
362
+ const pad = INDENT.repeat(depth);
363
+ if (fill.kind === 'scoped') {
364
+ return fill.vue
365
+ ? {
366
+ lines: [
367
+ `${pad}<template #${fill.slot.vue}="{ ${fill.parameters.join(', ')} }">`,
368
+ `${pad}${INDENT}${fill.vue}`,
369
+ `${pad}</template>`,
370
+ ],
371
+ }
372
+ : {};
373
+ }
374
+ return {
375
+ lines: [
376
+ `${pad}<template #${slot}>`,
377
+ ...fillLines(vue, fill, depth + 1),
378
+ `${pad}</template>`,
379
+ ],
380
+ };
381
+ },
382
+ raw: (fill) => fill.vue,
383
+ part: (fill, depth) => componentCall(vue, fill, depth),
384
+ };
385
+ const svelte = {
386
+ lineBreakIsSpace: true,
387
+ elementBreakIsSpace: true,
388
+ attribute(name, value, _html, depth) {
389
+ if (value === true)
390
+ return name;
391
+ if (typeof value === 'string' && !/["\n{}]/.test(value))
392
+ return `${name}="${value}"`;
393
+ return `${name}={${wrappedExpression(value, valueWidth(`${name}={`, depth))}}`;
394
+ },
395
+ text: svelteText,
396
+ junction: (line) => line,
397
+ namedSlot(slot, fill, depth) {
398
+ const pad = INDENT.repeat(depth);
399
+ if (fill.kind === 'scoped') {
400
+ return fill.svelte
401
+ ? {
402
+ lines: [
403
+ `${pad}{#snippet ${fill.slot.svelte}({ ${fill.parameters.join(', ')} })}`,
404
+ `${pad}${INDENT}${fill.svelte}`,
405
+ `${pad}{/snippet}`,
406
+ ],
407
+ }
408
+ : {};
409
+ }
410
+ return {
411
+ lines: [
412
+ `${pad}{#snippet ${slot}()}`,
413
+ ...fillLines(svelte, fill, depth + 1),
414
+ `${pad}{/snippet}`,
415
+ ],
416
+ };
417
+ },
418
+ raw: (fill) => fill.svelte,
419
+ part: (fill, depth) => [
420
+ `${INDENT.repeat(depth)}{@render ${fill.name}(${expression(fill.props ?? {})})}`,
421
+ ],
422
+ };
423
+ /** A part called as a component, in React and Vue. */
424
+ function componentCall(dialect, fill, depth) {
425
+ return elementLines(dialect, fill.name, attributeList(dialect, fill.props, false, depth), undefined, {}, depth);
426
+ }
427
+ /** The parts the story's content calls, each declared once. */
428
+ function partsOf(story) {
429
+ return [
430
+ ...collectParts([
431
+ story.around?.before,
432
+ story.children,
433
+ ...Object.values(story.named),
434
+ story.around?.after,
435
+ ]).values(),
436
+ ];
437
+ }
438
+ /** Whether the story has content beside its component. */
439
+ function hasAround(story) {
440
+ return (story.around?.before !== undefined || story.around?.after !== undefined);
441
+ }
442
+ /** The component's lines between the lines of the content beside it, at
443
+ * `depth`. */
444
+ function besideLines(dialect, story, component, depth) {
445
+ const { before, after } = story.around ?? {};
446
+ return [
447
+ ...(before ? fillLines(dialect, before, depth) : []),
448
+ ...component,
449
+ ...(after ? fillLines(dialect, after, depth) : []),
450
+ ];
451
+ }
452
+ /** A part's destructured props with their type: each parameter's own, or `any`. */
453
+ function typedParameters(part) {
454
+ const types = part.parameters.map((name) => `${name}: ${part.parameterTypes?.[name] ?? 'any'}`);
455
+ return `{ ${part.parameters.join(', ')} }: { ${types.join('; ')} }`;
456
+ }
457
+ /** A body's lines at `depth`, each of its own lines indented. */
458
+ function bodyLines(body, depth) {
459
+ return body.split('\n').map((line) => `${INDENT.repeat(depth)}${line}`);
460
+ }
461
+ /** `import { A, B as C, type D } from 'module';` lines, one per module, in
462
+ * first-seen order: of every component the story names, or only of `only`,
463
+ * and of the exported types `types` names, each beside the components of its
464
+ * module or in an `import type` of its own. */
465
+ function importLines(story, only, types = []) {
466
+ const byModule = new Map();
467
+ const entry = (module) => {
468
+ const found = byModule.get(module) ?? { names: [], types: [] };
469
+ byModule.set(module, found);
470
+ return found;
471
+ };
472
+ for (const [local, { module, exported }] of story.imports) {
473
+ if (only && !only.has(local))
474
+ continue;
475
+ entry(module).names.push(exported === local ? local : `${exported} as ${local}`);
476
+ }
477
+ for (const type of types) {
478
+ const module = story.exportedTypes?.get(type);
479
+ if (module !== undefined)
480
+ entry(module).types.push(type);
481
+ }
482
+ return [...byModule].map(([module, { names, types: typeNames }]) => names.length === 0
483
+ ? `import type { ${typeNames.join(', ')} } from '${module}';`
484
+ : `import { ${[...names, ...typeNames.map((type) => `type ${type}`)].join(', ')} } from '${module}';`);
485
+ }
486
+ /** The words a type made of the language's own types alone is written in. */
487
+ const BUILT_IN_TYPE_WORDS = /\b(?:string|number|boolean|bigint|null|undefined|true|false|unknown|any|readonly|Array|ReadonlyArray)\b/g;
488
+ /**
489
+ * The names a type uses beyond the language's own types and literals, when
490
+ * each of them is one of `exported` (none for a type of the language's own
491
+ * alone), so a consumer's file can write it as it stands, importing those;
492
+ * `undefined` when it uses any other name or a shape written out in place.
493
+ */
494
+ function writableTypeNames(type, exported = new Map()) {
495
+ const names = [];
496
+ const rest = type
497
+ .replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '')
498
+ .replace(BUILT_IN_TYPE_WORDS, '')
499
+ .replace(/[A-Za-z_$][\w$]*/g, (name) => {
500
+ if (!exported.has(name))
501
+ return name;
502
+ if (!names.includes(name))
503
+ names.push(name);
504
+ return '';
505
+ })
506
+ .replace(/-?\d+(?:\.\d+)?/g, '');
507
+ return /^[\s|&()[\]<>,]*$/.test(rest) ? names : undefined;
508
+ }
509
+ /** The type a JSON value is inferred as where state starts from it, when it
510
+ * is a primitive's: `boolean`, `number` or `string`. */
511
+ function inferredType(value) {
512
+ return ['boolean', 'number', 'string'].includes(typeof value)
513
+ ? typeof value
514
+ : undefined;
515
+ }
516
+ /**
517
+ * The type argument the state holding a bound prop takes: none where the
518
+ * value reads as the declared type, or as a primitive with no declared type;
519
+ * the declared type where it names the language's own types alone; none for
520
+ * or types the kit's package entry exports, importing those (`typeImports`);
521
+ * none for a generic component, whose type parameters the value infers;
522
+ * else the component's own prop type, as `propsType` names it
523
+ * (`ComponentProps<typeof X>['open']`), so an array or `null` never starts
524
+ * the state narrower than the prop.
525
+ */
526
+ function stateType(binding, value, propsType, exportedTypes) {
527
+ const declared = binding.declaredType?.trim();
528
+ const inferred = inferredType(value);
529
+ if (declared ? declared === inferred : inferred !== undefined)
530
+ return {};
531
+ const named = declared
532
+ ? writableTypeNames(declared, exportedTypes)
533
+ : undefined;
534
+ if (declared && named)
535
+ return {
536
+ type: declared,
537
+ ...(named.length > 0 ? { typeImports: named } : {}),
538
+ };
539
+ if (binding.generic)
540
+ return {};
541
+ return { type: propsType(binding.prop), propsLookup: true };
542
+ }
543
+ /**
544
+ * The story as its source prints it for a target: a prop given `null` left
545
+ * out, as a scenario's `null` unsets it, unless the target binds it as a
546
+ * two-way prop, whose state then starts from `null`; and the default prop of
547
+ * each two-way prop the story gives left out, as the component's value
548
+ * starts from the given prop instead.
549
+ */
550
+ function printedStory(story, target) {
551
+ const bound = new Set(boundOf(story, target).map((binding) => binding.prop));
552
+ const props = {};
553
+ for (const [name, value] of Object.entries(story.props))
554
+ if (value !== null || bound.has(name))
555
+ props[name] = value;
556
+ for (const binding of story.bindings ?? [])
557
+ if (binding.defaultProp !== undefined && binding.prop in props)
558
+ delete props[binding.defaultProp];
559
+ // A name the story imports as a component is a value, never a type.
560
+ const exportedTypes = story.exportedTypes
561
+ ? new Map([...story.exportedTypes].filter(([name]) => !story.imports.has(name)))
562
+ : undefined;
563
+ return { ...story, props, ...(exportedTypes ? { exportedTypes } : {}) };
564
+ }
565
+ /** The story's bindings this target binds: those for props the story gives,
566
+ * named as a variable can be, and in React those with a change prop. */
567
+ function boundOf(story, target) {
568
+ return (story.bindings ?? []).filter((binding) => binding.prop in story.props &&
569
+ IDENTIFIER.test(binding.prop) &&
570
+ (target !== 'react' ||
571
+ (binding.changeProp !== undefined &&
572
+ IDENTIFIER.test(binding.changeProp))));
573
+ }
574
+ /** A setter's name for a state variable: `open` → `setOpen`. */
575
+ function setterName(name) {
576
+ return `set${name[0].toUpperCase()}${name.slice(1)}`;
577
+ }
578
+ /** The story as a React component module, the attributes it gives a
579
+ * component spelled as React takes them (see `reactAttributeNames`). */
580
+ export function reactSource(given) {
581
+ const printed = printedStory(given, 'react');
582
+ const names = printed.reactAttributeNames ?? {};
583
+ const declares = (component, name) => printed.imports.get(component)?.declaredAttributes?.includes(name) === true;
584
+ const story = {
585
+ ...printed,
586
+ props: reactProps(printed.props, (name) => declares(printed.component, name), names),
587
+ children: reactFill(printed.children, declares, names),
588
+ named: Object.fromEntries(Object.entries(printed.named).map(([slot, fill]) => [
589
+ slot,
590
+ reactFill(fill, declares, names),
591
+ ])),
592
+ ...(printed.around
593
+ ? {
594
+ around: {
595
+ ...(printed.around.before
596
+ ? { before: reactFill(printed.around.before, declares, names) }
597
+ : {}),
598
+ ...(printed.around.after
599
+ ? { after: reactFill(printed.around.after, declares, names) }
600
+ : {}),
601
+ },
602
+ }
603
+ : {}),
604
+ };
605
+ // A bound prop's state, and its value and setter passed to the component.
606
+ const bindings = boundOf(story, 'react');
607
+ let propsLookup = false;
608
+ const typeImports = new Set();
609
+ const states = bindings.map((binding) => {
610
+ const value = story.props[binding.prop];
611
+ const { type, propsLookup: looked, typeImports: named, } = stateType(binding, value, (prop) => `ComponentProps<typeof ${story.component}>['${prop}']`, story.exportedTypes);
612
+ propsLookup ||= looked === true;
613
+ for (const name of named ?? [])
614
+ typeImports.add(name);
615
+ return `${INDENT}const [${binding.prop}, ${setterName(binding.prop)}] = useState${type ? `<${type}>` : ''}(${expression(value)});`;
616
+ });
617
+ const bound = new Map(bindings.map((binding) => [
618
+ binding.prop,
619
+ [
620
+ `${binding.prop}={${binding.prop}}`,
621
+ `${binding.changeProp}={${setterName(binding.prop)}}`,
622
+ ],
623
+ ]));
624
+ // Content beside the component is returned in a fragment with it.
625
+ const beside = hasAround(story);
626
+ const depth = beside ? 3 : 2;
627
+ const component = elementLines(react, story.component, attributeList(react, story.props, false, depth, bound), story.children, story.named, depth);
628
+ const pad = INDENT.repeat(2);
629
+ const body = beside
630
+ ? [`${pad}<>`, ...besideLines(react, story, component, depth), `${pad}</>`]
631
+ : component;
632
+ const returned = body.length === 1
633
+ ? [`${INDENT}return ${body[0].trim()};`]
634
+ : [`${INDENT}return (`, ...body, `${INDENT});`];
635
+ // A part is a function component above the example.
636
+ const parts = partsOf(story).flatMap((part) => [
637
+ `function ${part.name}(${typedParameters(part)}) {`,
638
+ `${INDENT}return (`,
639
+ ...bodyLines(singleJsxRoot(part.react), 2),
640
+ `${INDENT});`,
641
+ '}',
642
+ '',
643
+ ]);
644
+ return [
645
+ ...(states.length
646
+ ? [
647
+ `import { useState${propsLookup ? ', type ComponentProps' : ''} } from 'react';`,
648
+ ]
649
+ : []),
650
+ ...importLines(story, undefined, [...typeImports]),
651
+ '',
652
+ ...parts,
653
+ `export function ${story.component}Example() {`,
654
+ ...(states.length ? [...states, ''] : []),
655
+ ...returned,
656
+ '}',
657
+ '',
658
+ ].join('\n');
659
+ }
660
+ /** The story as a Vue single-file component. */
661
+ export function vueSource(given) {
662
+ const story = printedStory(given, 'vue');
663
+ // A bound prop's ref, bound with `v-model`.
664
+ const bindings = boundOf(story, 'vue');
665
+ const typeImports = new Set();
666
+ const states = bindings.map((binding) => {
667
+ const value = story.props[binding.prop];
668
+ const { type, typeImports: named } = stateType(binding, value, (prop) => `InstanceType<typeof ${story.component}>['$props']['${prop}']`, story.exportedTypes);
669
+ for (const name of named ?? [])
670
+ typeImports.add(name);
671
+ return `const ${binding.prop} = ref${type ? `<${type}>` : ''}(${expression(value)});`;
672
+ });
673
+ const bound = new Map(bindings.map((binding) => [
674
+ binding.prop,
675
+ [
676
+ binding.vueModel
677
+ ? `v-model="${binding.prop}"`
678
+ : `v-model:${binding.prop}="${binding.prop}"`,
679
+ ],
680
+ ]));
681
+ const body = besideLines(vue, story, elementLines(vue, story.component, attributeList(vue, story.props, false, 1, bound), story.children, story.named, 1), 1);
682
+ // A part is a single-file component of its own, printed after the story's
683
+ // under a comment naming its file; each imports what it uses.
684
+ const parts = partsOf(story);
685
+ const used = new Set([story.component]);
686
+ collectSlotComponents(story.children, used, false);
687
+ for (const fill of [
688
+ ...Object.values(story.named),
689
+ story.around?.before,
690
+ story.around?.after,
691
+ ])
692
+ collectSlotComponents(fill, used, false);
693
+ return [
694
+ '<script setup lang="ts">',
695
+ ...(states.length ? ["import { ref } from 'vue';"] : []),
696
+ ...importLines(story, used, [...typeImports]),
697
+ ...parts.map((part) => `import ${part.name} from './${part.name}.vue';`),
698
+ ...(states.length ? ['', ...states] : []),
699
+ '</script>',
700
+ '',
701
+ '<template>',
702
+ ...body,
703
+ '</template>',
704
+ '',
705
+ ...parts.flatMap((part) => [
706
+ `<!-- ${part.name}.vue -->`,
707
+ '<script setup lang="ts">',
708
+ ...importLines(story, new Set(part.imports)),
709
+ ...(part.imports?.length ? [''] : []),
710
+ part.parameterTypes
711
+ ? `defineProps<{ ${part.parameters.map((name) => `${name}: ${part.parameterTypes?.[name] ?? 'any'}`).join('; ')} }>();`
712
+ : `defineProps([${part.parameters.map((name) => stringLiteral(name)).join(', ')}]);`,
713
+ '</script>',
714
+ '',
715
+ '<template>',
716
+ ...bodyLines(part.vue, 1),
717
+ '</template>',
718
+ '',
719
+ ]),
720
+ ].join('\n');
721
+ }
722
+ /** The story as a Svelte component. */
723
+ export function svelteSource(given) {
724
+ const story = printedStory(given, 'svelte');
725
+ // A bound prop's state, bound with `bind:`: `$state` in runes mode, a plain
726
+ // `let` in legacy mode.
727
+ const bindings = boundOf(story, 'svelte');
728
+ const runes = story.svelteMode === 'runes';
729
+ let propsLookup = false;
730
+ const typeImports = new Set();
731
+ const states = bindings.map((binding) => {
732
+ const value = story.props[binding.prop];
733
+ const { type, propsLookup: looked, typeImports: named, } = stateType(binding, value, (prop) => `ComponentProps<typeof ${story.component}>['${prop}']`, story.exportedTypes);
734
+ propsLookup ||= looked === true;
735
+ for (const name of named ?? [])
736
+ typeImports.add(name);
737
+ return runes
738
+ ? `${INDENT}let ${binding.prop} = $state${type ? `<${type}>` : ''}(${expression(value)});`
739
+ : `${INDENT}let ${binding.prop}${type ? `: ${type}` : ''} = ${expression(value)};`;
740
+ });
741
+ const bound = new Map(bindings.map((binding) => [binding.prop, [`bind:${binding.prop}`]]));
742
+ const body = besideLines(svelte, story, elementLines(svelte, story.component, attributeList(svelte, story.props, false, 0, bound), story.children, story.named, 0), 0);
743
+ // A part is a snippet at the top of the markup.
744
+ const parts = partsOf(story).flatMap((part) => [
745
+ `{#snippet ${part.name}(${typedParameters(part)})}`,
746
+ ...bodyLines(part.svelte, 1),
747
+ '{/snippet}',
748
+ '',
749
+ ]);
750
+ return [
751
+ '<script lang="ts">',
752
+ ...(propsLookup
753
+ ? [`${INDENT}import type { ComponentProps } from 'svelte';`]
754
+ : []),
755
+ ...importLines(story, undefined, [...typeImports]).map((line) => `${INDENT}${line}`),
756
+ ...(states.length ? ['', ...states] : []),
757
+ '</script>',
758
+ '',
759
+ ...parts,
760
+ ...body,
761
+ '',
762
+ ].join('\n');
763
+ }
764
+ /**
765
+ * A story's props in the order the component declares them, so the source
766
+ * prints them alike however they were gathered (the template's defaults, the
767
+ * story file's, a scenario's); a prop the component does not declare follows,
768
+ * in the order given.
769
+ *
770
+ * @param props - The props to print.
771
+ * @param declared - The component's prop names, in declaration order.
772
+ * @returns The same props, reordered.
773
+ */
774
+ export function inDeclarationOrder(props, declared) {
775
+ const ordered = {};
776
+ for (const name of declared)
777
+ if (name in props)
778
+ ordered[name] = props[name];
779
+ for (const [name, value] of Object.entries(props))
780
+ if (!(name in ordered))
781
+ ordered[name] = value;
782
+ return ordered;
783
+ }
784
+ /** The printer per target. */
785
+ export const SOURCE_PRINTERS = {
786
+ react: reactSource,
787
+ vue: vueSource,
788
+ svelte: svelteSource,
789
+ };