@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
package/src/testing.ts ADDED
@@ -0,0 +1,497 @@
1
+ // Browser-test helpers for a kit's components: load one target's preview page
2
+ // on its own and drive it over the messages the preview host sends it. The page
3
+ // object is typed structurally, so a Playwright `Page` fits without the preview
4
+ // depending on Playwright. The functions handed to `evaluate` and
5
+ // `waitForFunction` run in the browser, so they read only their argument and
6
+ // browser globals, never this module's bindings.
7
+
8
+ import { readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+
11
+ import type { ManifestComponent } from './emit/manifest.js';
12
+ import type { OptionValue } from './options.js';
13
+ import {
14
+ componentFrameUrl,
15
+ globalsMessage,
16
+ PREVIEW_MOUNT_ELEMENT_ID,
17
+ PREVIEW_MOUNTED_STORY_FLAG,
18
+ PREVIEW_OPTIONS_FLAG,
19
+ PREVIEW_READY_FLAG,
20
+ PREVIEW_RENDER_ERROR_FLAG,
21
+ PREVIEW_SCRIPTED_TARGETS,
22
+ PREVIEW_STORIES_FLAG,
23
+ propsMessage,
24
+ setStoryMessage,
25
+ type PreviewListedOption,
26
+ type PreviewScriptedTarget,
27
+ type PreviewMessage,
28
+ } from './protocol.js';
29
+
30
+ /** The id of the element a preview page mounts its story or view in. */
31
+ export { PREVIEW_MOUNT_ELEMENT_ID };
32
+
33
+ /** A component as the preview manifest lists it: the parts a browser test
34
+ * reads. */
35
+ export type PreviewManifestComponent = Pick<ManifestComponent, 'id' | 'name'> &
36
+ Partial<
37
+ Pick<
38
+ ManifestComponent,
39
+ 'defaultProps' | 'previewDefaults' | 'standalone' | 'targets' | 'bindings'
40
+ >
41
+ > & {
42
+ /** The story's scenarios, each with the props it sets over the defaults
43
+ * and, for one with slot content of its own, that content and the id
44
+ * its story mounts by. */
45
+ scenarios?: (Pick<ManifestComponent['scenarios'][number], 'id'> &
46
+ Partial<
47
+ Pick<
48
+ ManifestComponent['scenarios'][number],
49
+ 'label' | 'props' | 'content' | 'story'
50
+ >
51
+ >)[];
52
+ };
53
+
54
+ /**
55
+ * Read the components of a kit's preview manifest,
56
+ * `.preview-generated/manifest.json`, which the preview writes when it
57
+ * starts.
58
+ *
59
+ * @param kitRoot - The kit's root directory.
60
+ * @returns The components the manifest lists.
61
+ * @example
62
+ * ```ts
63
+ * for (const component of readPreviewManifest('.')) {
64
+ * test(component.id, async ({ page }) => {});
65
+ * }
66
+ * ```
67
+ */
68
+ export function readPreviewManifest(
69
+ kitRoot: string
70
+ ): PreviewManifestComponent[] {
71
+ const manifest = JSON.parse(
72
+ readFileSync(join(kitRoot, '.preview-generated', 'manifest.json'), 'utf8')
73
+ ) as { components: PreviewManifestComponent[] };
74
+ return manifest.components;
75
+ }
76
+
77
+ /** The window property a story page sets to what its story threw; see
78
+ * {@link readRenderError}. */
79
+ export { PREVIEW_RENDER_ERROR_FLAG };
80
+
81
+ /** A target whose preview page the helpers load: `react`, `vue` or `svelte`. */
82
+ export type { PreviewScriptedTarget };
83
+
84
+ /** Preview option values keyed by option id, such as `{ mode: 'dark' }`. */
85
+ export type PreviewOptionValues = Record<string, OptionValue>;
86
+
87
+ /**
88
+ * The part of a browser page the helpers use. A Playwright `Page` satisfies
89
+ * it, as does any object with these three methods.
90
+ */
91
+ export interface PreviewPage {
92
+ /** Navigate to `url`, resolving once the page has loaded. */
93
+ goto(url: string, options?: { waitUntil?: 'load' }): Promise<unknown>;
94
+ /** Resolve once `pageFunction`, run in the page with `argument`, returns a truthy value. */
95
+ waitForFunction<Argument>(
96
+ pageFunction: (argument: Argument) => unknown,
97
+ argument: Argument
98
+ ): Promise<unknown>;
99
+ /** Run `pageFunction` in the page with `argument` and resolve with its result. */
100
+ evaluate<Result, Argument>(
101
+ pageFunction: (argument: Argument) => Result | Promise<Result>,
102
+ argument: Argument
103
+ ): Promise<Result>;
104
+ }
105
+
106
+ /** Where the preview is served. */
107
+ export interface PreviewLocationOptions {
108
+ /** Prefixed to the preview page's path, such as `http://localhost:6100`.
109
+ * Omit it to navigate by path alone (resolved against the test runner's
110
+ * base URL, such as Playwright's `use.baseURL`). */
111
+ baseUrl?: string;
112
+ }
113
+
114
+ /** How long a loaded preview page has to start listening. The page's load
115
+ * has already waited for its modules, so only a page that failed to run
116
+ * them comes near this. */
117
+ const READY_TIMEOUT_MILLISECONDS = 15_000;
118
+
119
+ /** How long a story set on a loaded page has to finish mounting. Mounting
120
+ * needs no network, so only a page that has stopped comes near this. */
121
+ const STORY_TIMEOUT_MILLISECONDS = 15_000;
122
+
123
+ /** Wait for `wait`, or throw `message` past the deadline. A wait left past
124
+ * the deadline rejects when the page closes, which is ignored. */
125
+ async function withinDeadline(
126
+ wait: Promise<unknown>,
127
+ milliseconds: number,
128
+ message: string
129
+ ): Promise<void> {
130
+ wait.catch(() => {});
131
+ let timer: ReturnType<typeof setTimeout> | undefined;
132
+ const deadline = new Promise<never>((_, reject) => {
133
+ timer = setTimeout(() => reject(new Error(message)), milliseconds);
134
+ });
135
+ try {
136
+ await Promise.race([wait, deadline]);
137
+ } finally {
138
+ clearTimeout(timer);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Load a target's preview page and wait until it listens for messages. The
144
+ * page shows nothing until a story is set.
145
+ *
146
+ * @param page - The browser page.
147
+ * @param target - The target whose preview page to load.
148
+ * @param options - Where the preview is served.
149
+ * @throws When the target has no story page, the message naming those that
150
+ * do, or when the page never starts listening, such as after an import
151
+ * fails to load.
152
+ * @example
153
+ * ```ts
154
+ * test.beforeEach(async ({ page }) => {
155
+ * await gotoPreview(page, 'vue');
156
+ * });
157
+ * ```
158
+ */
159
+ export async function gotoPreview(
160
+ page: PreviewPage,
161
+ target: PreviewScriptedTarget,
162
+ options: PreviewLocationOptions = {}
163
+ ): Promise<void> {
164
+ if (!(PREVIEW_SCRIPTED_TARGETS as readonly string[]).includes(target)) {
165
+ throw new Error(
166
+ `'${target}' has no story page; the story pages are ${PREVIEW_SCRIPTED_TARGETS.join(', ')}`
167
+ );
168
+ }
169
+ const baseUrl = (options.baseUrl ?? '').replace(/\/+$/, '');
170
+ const url = `${baseUrl}${componentFrameUrl(target)}`;
171
+ await page.goto(url, { waitUntil: 'load' });
172
+ await withinDeadline(
173
+ page.waitForFunction(
174
+ (flag) => (window as unknown as Record<string, unknown>)[flag] === true,
175
+ PREVIEW_READY_FLAG
176
+ ),
177
+ READY_TIMEOUT_MILLISECONDS,
178
+ `gotoPreview: the preview page at ${url} never announced itself: look for a page error (an import that fails to load) in the browser console or the web server log.`
179
+ );
180
+ }
181
+
182
+ /** Post a preview message to the page's own window, where the preview listens. */
183
+ async function post(page: PreviewPage, message: PreviewMessage): Promise<void> {
184
+ await page.evaluate((posted) => window.postMessage(posted, '*'), message);
185
+ }
186
+
187
+ /**
188
+ * What a preview page lists under a window property. A message posted to a
189
+ * page that lists nothing would reach nothing, so that throws, saying whether
190
+ * the page is a preview page at all.
191
+ */
192
+ async function listedEntries<Entry>(
193
+ page: PreviewPage,
194
+ flag: string,
195
+ helper: string
196
+ ): Promise<Entry[]> {
197
+ const read = (name: string) =>
198
+ (window as unknown as Record<string, unknown>)[name];
199
+ const ids = await page.evaluate(read, flag);
200
+ if (!Array.isArray(ids)) {
201
+ throw new Error(
202
+ (await page.evaluate(read, PREVIEW_READY_FLAG)) === true
203
+ ? `${helper}: this is a preview page that mounts no story (its html or view page, or a preview older than these helpers).`
204
+ : `${helper} needs a loaded preview page, and this page is not one: load it with gotoPreview first.`
205
+ );
206
+ }
207
+ return ids as Entry[];
208
+ }
209
+
210
+ /**
211
+ * The ids of the stories a preview page can mount.
212
+ *
213
+ * @param page - A page {@link gotoPreview} loaded.
214
+ * @returns The story ids.
215
+ * @throws When the page is not a preview page that mounts stories.
216
+ */
217
+ export function readStoryIds(page: PreviewPage): Promise<string[]> {
218
+ return listedEntries<string>(page, PREVIEW_STORIES_FLAG, 'readStoryIds');
219
+ }
220
+
221
+ /**
222
+ * What the story mounted on a preview page threw as it rendered, as the pane
223
+ * reports it: only a throw that reaches the pane counts, so a story with its
224
+ * own error boundary, or the word "error" in its content, reads as rendering.
225
+ *
226
+ * @param page - A page {@link gotoPreview} loaded.
227
+ * @returns The error's message, which may be empty, or `undefined` when the
228
+ * story renders.
229
+ * @example
230
+ * ```ts
231
+ * await mountStory(page, 'components-button');
232
+ * await settle(page);
233
+ * expect(await readRenderError(page)).toBeUndefined();
234
+ * ```
235
+ */
236
+ export async function readRenderError(
237
+ page: PreviewPage
238
+ ): Promise<string | undefined> {
239
+ const message = await page.evaluate(
240
+ (flag) => (window as unknown as Record<string, unknown>)[flag],
241
+ PREVIEW_RENDER_ERROR_FLAG
242
+ );
243
+ return typeof message === 'string' ? message : undefined;
244
+ }
245
+
246
+ /** Throw unless the page can mount the story: a page shows nothing for an id
247
+ * it does not know, and every check on the empty mount would pass. */
248
+ async function requireStory(
249
+ page: PreviewPage,
250
+ id: string,
251
+ helper: string
252
+ ): Promise<void> {
253
+ const known = await listedEntries<string>(page, PREVIEW_STORIES_FLAG, helper);
254
+ if (!known.includes(id)) {
255
+ throw new Error(
256
+ `${helper}: this preview has no story with the id '${id}'. Its stories: ${known.join(', ') || 'none'}.`
257
+ );
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Mount a story by id with the given root props, replacing what the page
263
+ * shows. Setting the story already shown with other props keeps its component
264
+ * instance on some targets; use {@link mountStory} for a guaranteed fresh
265
+ * mount.
266
+ *
267
+ * @param page - A page {@link gotoPreview} loaded.
268
+ * @param id - The story's id.
269
+ * @param props - The story's root props.
270
+ * @throws When the page has no story with the id; the message lists those it
271
+ * has.
272
+ */
273
+ export async function setStory(
274
+ page: PreviewPage,
275
+ id: string,
276
+ props: Record<string, unknown> = {}
277
+ ): Promise<void> {
278
+ await requireStory(page, id, 'setStory');
279
+ await post(page, setStoryMessage(id, props));
280
+ }
281
+
282
+ /**
283
+ * Mount a story from nothing: blur the focused element, unmount the current
284
+ * story and wait for the mount point to empty, then set the story. The new
285
+ * story inherits neither focus nor component state from the one before, which
286
+ * a sweep over a component's states needs (a state read at mount, such as an
287
+ * initially open panel, only applies to a fresh instance).
288
+ *
289
+ * @param page - A page {@link gotoPreview} loaded.
290
+ * @param id - The story's id.
291
+ * @param props - The story's root props.
292
+ * @throws When the page has no story with the id, the message listing those
293
+ * it has, or when the mount is not empty within 15 seconds of the unmount.
294
+ */
295
+ export async function mountStory(
296
+ page: PreviewPage,
297
+ id: string,
298
+ props: Record<string, unknown> = {}
299
+ ): Promise<void> {
300
+ await requireStory(page, id, 'mountStory');
301
+ await page.evaluate(
302
+ () => (document.activeElement as HTMLElement | null)?.blur?.(),
303
+ undefined
304
+ );
305
+ // No story is registered under the empty id, so every target unmounts.
306
+ await post(page, setStoryMessage('', {}));
307
+ await withinDeadline(
308
+ page.waitForFunction(
309
+ (mountId) => document.getElementById(mountId)?.childElementCount === 0,
310
+ PREVIEW_MOUNT_ELEMENT_ID
311
+ ),
312
+ STORY_TIMEOUT_MILLISECONDS,
313
+ `mountStory: the page never cleared the mount ahead of the story '${id}': the story before it may have thrown as it unmounted; look for a page error in the browser console.`
314
+ );
315
+ // The page's last report is cleared here, not awaited from the page: the
316
+ // pane reports its own unmount, but after its paint in React, which a mount
317
+ // already empty may be read before, and a story set twice in a row would
318
+ // then pass {@link waitForStory} on its first mount's report.
319
+ await page.evaluate((flag) => {
320
+ (window as unknown as Record<string, unknown>)[flag] = '';
321
+ }, PREVIEW_MOUNTED_STORY_FLAG);
322
+ await post(page, setStoryMessage(id, props));
323
+ }
324
+
325
+ /**
326
+ * Wait until a story set with {@link mountStory} has finished mounting,
327
+ * whatever it rendered: a story can legitimately render nothing, which an
328
+ * element to wait for would never show, and a story that throws counts as
329
+ * mounted. It follows `mountStory`, which clears the page's last report;
330
+ * after {@link setStory} that report can be the same story's from before.
331
+ *
332
+ * @param page - A page {@link gotoPreview} loaded.
333
+ * @param id - The story's id.
334
+ * @throws When the story does not finish mounting within 15 seconds.
335
+ * @example
336
+ * ```ts
337
+ * await mountStory(page, 'components-banner', { visible: false });
338
+ * await waitForStory(page, 'components-banner');
339
+ * await expect(page.locator('#app > *')).toHaveCount(0);
340
+ * ```
341
+ */
342
+ export async function waitForStory(
343
+ page: PreviewPage,
344
+ id: string
345
+ ): Promise<void> {
346
+ await withinDeadline(
347
+ page.waitForFunction(
348
+ (argument) =>
349
+ (window as unknown as Record<string, unknown>)[argument.flag] ===
350
+ argument.id,
351
+ { id, flag: PREVIEW_MOUNTED_STORY_FLAG }
352
+ ),
353
+ STORY_TIMEOUT_MILLISECONDS,
354
+ `waitForStory: the story '${id}' never finished mounting: look for a page error in the browser console, or a page that has stopped responding.`
355
+ );
356
+ }
357
+
358
+ /**
359
+ * Replace the mounted story's root props, keeping its component instance (and
360
+ * any state machine driving it) alive.
361
+ *
362
+ * @param page - A page {@link gotoPreview} loaded.
363
+ * @param props - The story's new root props.
364
+ */
365
+ export async function setProps(
366
+ page: PreviewPage,
367
+ props: Record<string, unknown>
368
+ ): Promise<void> {
369
+ await post(page, propsMessage(props));
370
+ }
371
+
372
+ /**
373
+ * Apply preview option values (a color mode, a surface, a frame around the
374
+ * story) to the page, as the preview's controls do.
375
+ *
376
+ * @param page - A page {@link gotoPreview} loaded.
377
+ * @param options - Option values keyed by option id.
378
+ * @throws When an id is not one of the kit's options, or a value is not one
379
+ * the option takes, either of which the page would ignore; the message lists
380
+ * what it defines.
381
+ */
382
+ export async function setOptions(
383
+ page: PreviewPage,
384
+ options: PreviewOptionValues
385
+ ): Promise<void> {
386
+ const listed = await listedEntries<PreviewListedOption>(
387
+ page,
388
+ PREVIEW_OPTIONS_FLAG,
389
+ 'setOptions'
390
+ );
391
+ const known = listed.map((option) => option.id);
392
+ const unknown = Object.keys(options).filter((id) => !known.includes(id));
393
+ if (unknown.length > 0) {
394
+ throw new Error(
395
+ `setOptions: this preview defines no option ${unknown.map((id) => `'${id}'`).join(', ')}. Its options: ${known.join(', ') || 'none'}.`
396
+ );
397
+ }
398
+ for (const [id, value] of Object.entries(options)) {
399
+ const { values } = listed.find((option) => option.id === id)!;
400
+ if (
401
+ values ? !values.includes(value as string) : typeof value !== 'boolean'
402
+ ) {
403
+ throw new Error(
404
+ values
405
+ ? `setOptions: option '${id}' has no value ${JSON.stringify(value)}. Its values: ${values.join(', ')}.`
406
+ : `setOptions: option '${id}' takes true or false, not ${JSON.stringify(value)}.`
407
+ );
408
+ }
409
+ }
410
+ await post(page, globalsMessage(options));
411
+ }
412
+
413
+ /**
414
+ * Wait until the frame after the next paint. A component can show its final
415
+ * markup before the effects that attach its event handling have run; call this
416
+ * after asserting a change has rendered and before sending input, such as a
417
+ * key press, that would otherwise be dropped.
418
+ *
419
+ * @param page - The browser page.
420
+ */
421
+ export async function settle(page: PreviewPage): Promise<void> {
422
+ await page.evaluate(
423
+ () =>
424
+ new Promise<void>((resolve) => {
425
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
426
+ }),
427
+ undefined
428
+ );
429
+ }
430
+
431
+ /** What {@link openStory} loads and mounts. */
432
+ export interface OpenStoryOptions extends PreviewLocationOptions {
433
+ /** The target whose preview page to load. */
434
+ target: PreviewScriptedTarget;
435
+ /** The story's id, as the catalog and the address name it, such as
436
+ * `components-button` (`PreviewComponent.id`). */
437
+ id: string;
438
+ /** The story's root props. */
439
+ props?: Record<string, unknown>;
440
+ /** Preview option values applied before the story mounts. */
441
+ options?: PreviewOptionValues;
442
+ }
443
+
444
+ /** A story open in a page, with the page's controls bound to it. */
445
+ export interface PreviewStory<Page extends PreviewPage = PreviewPage> {
446
+ /** The page showing the story; the preview page is loaded at the top level,
447
+ * so locators and keyboard input go straight to it. */
448
+ page: Page;
449
+ /** The target the story renders in. */
450
+ target: PreviewScriptedTarget;
451
+ /** Replace the story's root props, keeping its component instance. */
452
+ setProps(props: Record<string, unknown>): Promise<void>;
453
+ /** Apply preview option values. */
454
+ setOptions(options: PreviewOptionValues): Promise<void>;
455
+ /** Show another story (see {@link setStory}). */
456
+ setStory(id: string, props?: Record<string, unknown>): Promise<void>;
457
+ /** Show another story from a fresh mount (see {@link mountStory}). */
458
+ mountStory(id: string, props?: Record<string, unknown>): Promise<void>;
459
+ /** Wait until the frame after the next paint (see {@link settle}). */
460
+ settle(): Promise<void>;
461
+ }
462
+
463
+ /**
464
+ * Load a target's preview page and mount one story in it.
465
+ *
466
+ * @param page - The browser page.
467
+ * @param options - The target, the story and its props, and option values.
468
+ * @returns The open story.
469
+ * @throws When the page has no story with the id, or an option id is not one
470
+ * of the kit's; the message lists those it has.
471
+ * @example
472
+ * ```ts
473
+ * const story = await openStory(page, { target: 'vue', id: 'components-button', props: { variant: 'solid' } });
474
+ * await story.setProps({ variant: 'ghost' });
475
+ * await story.setOptions({ mode: 'dark' });
476
+ * await expect(story.page.getByRole('button')).toHaveClass(/ghost/);
477
+ * ```
478
+ */
479
+ export async function openStory<Page extends PreviewPage>(
480
+ page: Page,
481
+ options: OpenStoryOptions
482
+ ): Promise<PreviewStory<Page>> {
483
+ const { target, id, props = {}, options: optionValues, baseUrl } = options;
484
+ await gotoPreview(page, target, { baseUrl });
485
+ if (optionValues) await setOptions(page, optionValues);
486
+ await requireStory(page, id, 'openStory');
487
+ await post(page, setStoryMessage(id, props));
488
+ return {
489
+ page,
490
+ target,
491
+ setProps: (next) => setProps(page, next),
492
+ setOptions: (next) => setOptions(page, next),
493
+ setStory: (nextId, nextProps) => setStory(page, nextId, nextProps),
494
+ mountStory: (nextId, nextProps) => mountStory(page, nextId, nextProps),
495
+ settle: () => settle(page),
496
+ };
497
+ }
package/src/views.ts ADDED
@@ -0,0 +1,183 @@
1
+ // The custom-view contract: what a kit's `viewsModule` exports. A view is a
2
+ // consumer-authored panel the preview lists beside the component stories and
3
+ // mounts in the dedicated view iframe (`?view=<id>`) under the live options
4
+ // context - tokens pages, galleries, composed demos. A view mounts itself into
5
+ // an element in any of the preview's frameworks, or in plain DOM; `reactView`
6
+ // adapts a React render function. Browser-safe.
7
+
8
+ import type { OptionValue } from './options.js';
9
+
10
+ export { reactView, type ReactViewRender } from './react-view.js';
11
+
12
+ /** One entry of the component index a view receives: a browseable story the
13
+ * view can cross-reference or select. */
14
+ export interface ViewContextComponent {
15
+ /** The component's selection id - pass to `selectComponent` to open its story. */
16
+ id: string;
17
+ /** The full title (source label prefix included). */
18
+ title: string;
19
+ /** The preview source (navigation section) the component belongs to. */
20
+ source: string;
21
+ }
22
+
23
+ /** A source as a view sees it - the kit's own source plus any other package whose components it shows. */
24
+ export interface ViewContextSource {
25
+ id: string;
26
+ label: string;
27
+ order: number;
28
+ }
29
+
30
+ /** What the preview passes a view. `options` is live (a new context arrives
31
+ * on every change, see `ViewContextUpdates`); the catalog fields are a
32
+ * read-only snapshot of the kit's catalog, so a view can cross-link into a
33
+ * component story. A view reads
34
+ * what it needs (a foundations view reads `options.theme` to pick its palette;
35
+ * a gallery resolves a component from `components` and deep-links via
36
+ * `selectComponent`). */
37
+ export interface ViewContext {
38
+ options: Record<string, OptionValue>;
39
+ /** The kit being previewed. Empty until the host handshake delivers the catalog. */
40
+ kitName: string;
41
+ /** The sources, ordered. Empty until the handshake delivers the catalog. */
42
+ sources: ViewContextSource[];
43
+ /** The browseable component stories (components that are not standalone and unlisted
44
+ * stories excluded; an unlisted story's id still selects it). Empty until
45
+ * the handshake delivers the catalog. */
46
+ components: ViewContextComponent[];
47
+ /**
48
+ * Ask the preview to select a component story, as choosing it in the
49
+ * preview's navigation does. With `props`, the story opens with those root props merged
50
+ * over the component's defaults (a gallery opens the clicked entry's variant);
51
+ * without, it opens on its baseline scenario. Unknown ids are ignored.
52
+ */
53
+ selectComponent: (id: string, props?: Record<string, unknown>) => void;
54
+ }
55
+
56
+ /** Fields common to every navigation entry a kit registers. */
57
+ interface PreviewEntryBase {
58
+ /** Reserved selection id - also the view iframe's `?view=` parameter. */
59
+ id: string;
60
+ /** The entry's name in the navigation. */
61
+ label: string;
62
+ /** The source whose entries list this one (a source id from
63
+ * `preview.sources`); defaults to the kit's own (first) source. */
64
+ source?: string;
65
+ /** Position among the source's entries - lower first. Views and the
66
+ * auto-generated component tree interleave by this. */
67
+ order?: number;
68
+ }
69
+
70
+ /** The framework a view mounts with. The view iframe loads the kit's setup
71
+ * module for that framework (`preview.setupModules`) before it
72
+ * mounts the view; `'dom'` loads none. */
73
+ export type ViewFramework = 'react' | 'vue' | 'svelte' | 'dom';
74
+
75
+ /** How a mounted view hears of a changed context: the options as the
76
+ * preview applies them, and the catalog once the handshake delivers it. */
77
+ export interface ViewContextUpdates {
78
+ /**
79
+ * Call `listener` with each new context.
80
+ *
81
+ * @returns A function that stops the calls.
82
+ */
83
+ subscribe(listener: (context: ViewContext) => void): () => void;
84
+ }
85
+
86
+ /**
87
+ * Mount a view into `element` with the context as it stands. The view stays
88
+ * mounted for the life of the view iframe and follows later changes through
89
+ * `updates`; a view that ignores them shows the context it mounted with.
90
+ *
91
+ * @param element - The element the view renders into.
92
+ * @param context - The context as it stands when the view mounts.
93
+ * @param updates - Delivers each new context.
94
+ * @returns A function that unmounts the view, or nothing.
95
+ */
96
+ export type ViewMount = (
97
+ element: HTMLElement,
98
+ context: ViewContext,
99
+ updates: ViewContextUpdates
100
+ ) => (() => void) | void;
101
+
102
+ /** A consumer-authored panel: the preview lists it beside the component stories
103
+ * and mounts it in the generic view iframe (`?view=<id>`) under the live
104
+ * options. */
105
+ export interface RenderedView extends PreviewEntryBase {
106
+ /** Discriminant - omit (or `false`) for a rendered view. */
107
+ componentTree?: false;
108
+ /** The framework `mount` renders with. Default `'dom'`. */
109
+ framework?: ViewFramework;
110
+ /** Mount the view inside the view iframe. */
111
+ mount: ViewMount;
112
+ }
113
+
114
+ /** The "magic" entry standing in for the source's auto-generated component tree (its
115
+ * stories) at a chosen position. It mounts no iframe, so it carries no `mount`. */
116
+ export interface ComponentTreeEntry extends PreviewEntryBase {
117
+ componentTree: true;
118
+ mount?: never;
119
+ }
120
+
121
+ /** A navigation entry a kit's `viewsModule` registers: either a rendered view or the
122
+ * component-tree marker. The two interleave by `order`. */
123
+ export type PreviewView = RenderedView | ComponentTreeEntry;
124
+
125
+ /** What the kit's `viewsModule` must export. */
126
+ export interface PreviewViewsModule {
127
+ views: PreviewView[];
128
+ viewById: (id: string) => PreviewView | undefined;
129
+ }
130
+
131
+ /** The language of a code block's source, as a highlighter names it: a
132
+ * scripted target's file (`tsx`, `vue`, `svelte`) or the html target's
133
+ * markup. */
134
+ export type CodeBlockLanguage = 'tsx' | 'vue' | 'svelte' | 'html';
135
+
136
+ /** What a code block shows: one target's source of the story, or, while it
137
+ * has none, nothing (it is being printed) or why there is none. */
138
+ export interface CodeBlockContent {
139
+ /** The target the source is for: a scripted target, or `html`. */
140
+ target: 'react' | 'vue' | 'svelte' | 'html';
141
+ language: CodeBlockLanguage;
142
+ /** The source, as a consumer writes it. */
143
+ source?: string;
144
+ /** Why the target has no source, when it has none to be had. */
145
+ reason?: string;
146
+ /** Whether the shell wraps its code panes' long lines: the block wraps
147
+ * them when true and scrolls sideways when false. A shell with no such
148
+ * choice sends false. */
149
+ wrap: boolean;
150
+ }
151
+
152
+ /** How a mounted code block hears of a new source: each time the story, its
153
+ * props or its content change, or the shell's wrapping does. */
154
+ export interface CodeBlockUpdates {
155
+ /**
156
+ * Call `listener` with each new content.
157
+ *
158
+ * @returns A function that stops the calls.
159
+ */
160
+ subscribe(listener: (content: CodeBlockContent) => void): () => void;
161
+ }
162
+
163
+ /**
164
+ * Mount a kit's code block into `element`, showing `content`, the default
165
+ * export of the module the kit names as its `codeBlock`. The block stays
166
+ * mounted for the life of its iframe, one per code pane, and follows later
167
+ * sources through `updates`. It wraps long lines while `content.wrap` is
168
+ * true and scrolls sideways while it is false, as the shell's own panes do.
169
+ * The iframe's page is the block's alone: its root carries
170
+ * `data-preview-frame="code"`, so the kit's stylesheet may style the frame's
171
+ * `html` and `body` by it, say in the block's background so an overscroll
172
+ * shows no other color.
173
+ *
174
+ * @param element - The element the block renders into.
175
+ * @param content - The source as it stands when the block mounts.
176
+ * @param updates - Delivers each new source.
177
+ * @returns A function that unmounts the block, or nothing.
178
+ */
179
+ export type CodeBlockMount = (
180
+ element: HTMLElement,
181
+ content: CodeBlockContent,
182
+ updates: CodeBlockUpdates
183
+ ) => (() => void) | void;