@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,719 @@
1
+ // The catalog a preview controller is created with: everything static about
2
+ // the kit being previewed, in the shape a shell reads (a navigation tree, short
3
+ // titles, the Empty scenario in place). The preview's bootstrap builds it from
4
+ // the generated manifest with `catalogFromManifest`; another producer can build
5
+ // one by hand. Browser-safe: no node imports, type-only imports.
6
+
7
+ import type { Manifest } from '../emit/manifest.js';
8
+ import type {
9
+ PreviewBinding,
10
+ PreviewPaneWidth,
11
+ PreviewPropType,
12
+ StoryAround,
13
+ } from '../lib/types.js';
14
+ import { EMPTY_SCENARIO_ID } from '../lib/naming.js';
15
+ import type { OptionValue } from '../options.js';
16
+ import {
17
+ PREVIEW_SCRIPTED_TARGETS,
18
+ PREVIEW_TARGETS,
19
+ type PreviewScriptedTarget,
20
+ type PreviewTarget,
21
+ } from '../protocol.js';
22
+ import type { PreviewView, RenderedView, ViewFramework } from '../views.js';
23
+ import {
24
+ DEFAULT_SLOT,
25
+ HTML_TAGS,
26
+ isContainer,
27
+ type EditorNode,
28
+ type EditorState,
29
+ type SlotPlan,
30
+ } from './editor-tree.js';
31
+
32
+ export type { PreviewBinding, PreviewPropType } from '../lib/types.js';
33
+
34
+ /** One scenario of a component: a named set of root props, and the option
35
+ * values it shows them under. */
36
+ export interface PreviewScenario {
37
+ id: string;
38
+ /** The picker label. */
39
+ label: string;
40
+ /** What the scenario shows, from the component's story file; markdown allowed.
41
+ * Absent when the story file gives none. */
42
+ description?: string;
43
+ /** The props the scenario lays over the component's defaults. */
44
+ props: Record<string, unknown>;
45
+ /** The option values picking the scenario sets, by option id: the setting
46
+ * it shows the component in, such as a color mode or a surface. Absent
47
+ * when it sets none. */
48
+ options?: Record<string, OptionValue>;
49
+ /** The component's baseline: selected first and shown as the default. */
50
+ default?: boolean;
51
+ /** The preview's own Empty scenario: the story's props with no slot content.
52
+ * Only a component that accepts slot content has one. */
53
+ empty?: boolean;
54
+ /** Generated from one enum value or boolean, listed after the curated ones. */
55
+ generated?: boolean;
56
+ /** The slot content the scenario shows, when its story file gives it
57
+ * content of its own: its fills over the story's, slot by slot. Absent
58
+ * when it shows the story's content. */
59
+ content?: SlotPlan;
60
+ /** The id the panes mount the scenario by, when it has content of its
61
+ * own. The preview's to use: a shell names a scenario by its `id`. */
62
+ story?: string;
63
+ }
64
+
65
+ /** A component of the kit, with its stories' data. */
66
+ export interface PreviewComponent {
67
+ /** The story's id, unique across the kit: its title in lower case with
68
+ * every run of other characters a hyphen, `components-layout-card` for
69
+ * `Components/Layout/Card`. The selection, the address (`?component=`) and
70
+ * the testing helpers name the story by it. */
71
+ id: string;
72
+ /** The component's export name, as the editor's tree refers to it. */
73
+ name: string;
74
+ /** What the component is and when to reach for it, from its template's
75
+ * `description`; markdown allowed. Absent when the template gives none. */
76
+ description?: string;
77
+ /** The full title, the source label included: `Components/Layout/Card`. */
78
+ title: string;
79
+ /** The title without its source label: `Layout/Card`. */
80
+ shortTitle: string;
81
+ /** The source the component belongs to (`PreviewSource.id`). */
82
+ source: string;
83
+ /** Whether the component has a story of its own. One that is not (a
84
+ * compound's part, a shared building block) is offered to the editor,
85
+ * not browseable as a story. */
86
+ standalone: boolean;
87
+ /** A story left out of what a reader browses - the navigation, an
88
+ * editor's component picker, the component count - but generated and
89
+ * reachable as any other: by its id, at its address, and through the
90
+ * testing helpers. A shell that lists components leaves these out. */
91
+ unlisted: boolean;
92
+ /** Each prop's control and declaration. */
93
+ propTypes: PreviewPropType[];
94
+ /** The component's events: the props it declares as functions that
95
+ * return nothing, such as `onSelect: (value: string) => void`, which it
96
+ * calls as things happen. A function prop that returns something, such as
97
+ * a `highlight` or a `getPageUrl`, is not one, and is left as given.
98
+ * A story's panes record each call (`state.events`). */
99
+ events: string[];
100
+ /** The component's two-way props, in prop order. A story that gives one
101
+ * takes each value its change prop reports, so a click in any pane
102
+ * changes the story's props (`state.props`), as `actions.setProps`
103
+ * does. A story that leaves it out lets the component keep its own. */
104
+ bindings: PreviewBinding[];
105
+ /** The props the story starts from: the component's declared defaults,
106
+ * with the kit's hints and the story file's `defaultProps` over them. */
107
+ defaultProps: Record<string, unknown>;
108
+ /** The values in `defaultProps` the preview starts from on its own, from
109
+ * the kit's control hints, which neither the component declares nor its
110
+ * story file sets. A consumer's source leaves a prop out while it holds this
111
+ * value and the scenario does not set it. */
112
+ previewDefaults: Record<string, unknown>;
113
+ scenarios: PreviewScenario[];
114
+ /** The slots the component accepts content into, and those among them
115
+ * (`default` for the default slot) whose content must take their scope. */
116
+ slots: { default: boolean; named: string[]; scopeRequired?: string[] };
117
+ /** The curated slot content a story renders with; the editor seeds its tree from it. */
118
+ content?: SlotPlan;
119
+ /** The content the story renders beside the component, as its siblings:
120
+ * `before` and `after` it. The editor holds it unchanged, outside its root. */
121
+ around?: StoryAround;
122
+ /** Story option defaults, keyed by option id; `false` marks the option unavailable. */
123
+ options?: Record<string, OptionValue | false>;
124
+ /** The targets the component renders in, within the catalog's
125
+ * `targets`. Html is not listed: where the kit builds html, every
126
+ * story has an html pane, which says so when the story has no markup. */
127
+ targets: PreviewScriptedTarget[];
128
+ }
129
+
130
+ /** A source: the kit's own components or another package's, in `order`. */
131
+ export interface PreviewSource {
132
+ id: string;
133
+ label: string;
134
+ order: number;
135
+ /** The package the source's components are imported from, as
136
+ * `<moduleBase>/<target>`, or `<moduleBase>` with `{target}` replaced by
137
+ * the target when it names one, when known. */
138
+ moduleBase?: string;
139
+ }
140
+
141
+ /** How a consumer imports one of the kit's components. */
142
+ export interface PreviewImport {
143
+ /** The name the component is exported under. */
144
+ exportName: string;
145
+ /** The names it declares that React would otherwise respell (`class`,
146
+ * `style`, an attribute React renames, such as `tabindex`), which React
147
+ * takes as they are; any other attribute a story gives it is spelled as
148
+ * React takes it. Absent when it declares none. */
149
+ declaredAttributes?: string[];
150
+ /** The module it is imported from, per target the kit builds. */
151
+ modules: Partial<Record<PreviewScriptedTarget, string>>;
152
+ /** The type names its module exports, per target, which the module a
153
+ * consumer imports it from exports too: a story's source names a prop's
154
+ * type by one of them. Absent where it exports none. */
155
+ types?: Partial<Record<PreviewScriptedTarget, string[]>>;
156
+ }
157
+
158
+ /** A custom view a kit registers, as the catalog lists it. */
159
+ export interface PreviewCatalogView {
160
+ /** The selection id. */
161
+ id: string;
162
+ label: string;
163
+ /** The source whose entries list the view. */
164
+ source: string;
165
+ order: number;
166
+ /** The framework the view mounts with. */
167
+ framework: ViewFramework;
168
+ }
169
+
170
+ /** One entry of the navigation tree: a view, a component story, or a group of entries. */
171
+ export type PreviewNavigationNode =
172
+ | { kind: 'view'; id: string; label: string }
173
+ | { kind: 'component'; id: string; label: string; title: string }
174
+ | {
175
+ kind: 'group';
176
+ key: string;
177
+ label: string;
178
+ children: PreviewNavigationNode[];
179
+ };
180
+
181
+ /**
182
+ * Everything static about a kit's preview. A controller is created with one
183
+ * and never changes it; a shell renders its navigation and reads its
184
+ * components, views and option vocabulary.
185
+ */
186
+ export interface PreviewCatalog {
187
+ /** The kit's name. */
188
+ kitName: string;
189
+ /** The kit's package version, when known. */
190
+ kitVersion?: string;
191
+ /** What the kit builds, in order: its targets, then `html` when it
192
+ * builds html. A shell offers a pane and a toggle for these alone. */
193
+ targets: PreviewTarget[];
194
+ /** The sources, in order; the first is the kit's own. */
195
+ sources: PreviewSource[];
196
+ /** Every component, those with no story of their own and unlisted stories included, with its
197
+ * scenarios (the Empty one in place). */
198
+ components: PreviewComponent[];
199
+ /** The custom views, in order. */
200
+ views: PreviewCatalogView[];
201
+ /** The kit's own code block, when it names one: the framework it mounts
202
+ * with. A shell mounts it (`frames.mountCode`) in place of its own code
203
+ * view. */
204
+ codeBlock?: { framework: ViewFramework };
205
+ /** The navigation tree: the kit's own source hoisted, other sources grouped. */
206
+ navigation: PreviewNavigationNode[];
207
+ /** The plain HTML tags an editor may offer for a wrapper node. */
208
+ htmlTags: string[];
209
+ /** The color tokens a color control offers; `null` is the unset choice. */
210
+ colorTokens: (string | null)[];
211
+ /** The widths a shell offers to lay the story panes out at, besides
212
+ * fitting them to its own space (`state.paneWidth`), narrowest first:
213
+ * the kit's `paneWidths`, or `DEFAULT_PANE_WIDTHS`. */
214
+ paneWidths: PreviewPaneWidth[];
215
+ /** The entry the preview opens on when its address names none, as the
216
+ * kit names it (`landing`); absent, the first listed story. */
217
+ landing?: PreviewLanding;
218
+ /** The mode the kit builds its Svelte target in (`svelte.mode` in
219
+ * the kit configuration): what a story's Svelte source holds a
220
+ * bound prop's state in, `$state` in runes mode and a plain `let` in
221
+ * legacy mode. */
222
+ svelteMode: 'legacy' | 'runes';
223
+ /** How a consumer imports each of the kit's components, by component name,
224
+ * those with no story of their own included: what a story's source
225
+ * imports. A component missing here is imported by its own name from the
226
+ * first source's `moduleBase`. */
227
+ imports: Record<string, PreviewImport>;
228
+ /** How React spells the HTML attributes it renames (`tabindex` as
229
+ * `tabIndex`, `for` as `htmlFor`), by their HTML names: a story's React
230
+ * source spells the attributes it gives a component with them. */
231
+ reactAttributeNames: Record<string, string>;
232
+ }
233
+
234
+ /** The manifest fields `catalogFromManifest` reads. */
235
+ export type CatalogManifest = Pick<Manifest, 'components'> &
236
+ Partial<
237
+ Pick<
238
+ Manifest,
239
+ | 'kitName'
240
+ | 'kitVersion'
241
+ | 'targets'
242
+ | 'sources'
243
+ | 'colorOptions'
244
+ | 'labels'
245
+ | 'imports'
246
+ | 'codeBlock'
247
+ | 'paneWidths'
248
+ | 'landing'
249
+ | 'svelteMode'
250
+ | 'reactAttributeNames'
251
+ >
252
+ >;
253
+
254
+ /** The entry the preview opens on when its address names none: a custom
255
+ * view, or a component's story on one of its scenarios (by id) or, without
256
+ * one, on its baseline. */
257
+ export type PreviewLanding =
258
+ | { kind: 'view'; id: string }
259
+ | { kind: 'component'; id: string; scenario?: string };
260
+
261
+ /** The widths a shell offers to lay the story panes out at when the kit
262
+ * names none: a small screen's, a medium one's and a large one's. */
263
+ export const DEFAULT_PANE_WIDTHS: readonly PreviewPaneWidth[] = [
264
+ { label: 'small', width: 375 },
265
+ { label: 'medium', width: 768 },
266
+ { label: 'large', width: 1280 },
267
+ ];
268
+
269
+ const DEFAULT_SOURCES: PreviewSource[] = [
270
+ { id: 'core', label: 'Components', order: 0 },
271
+ ];
272
+
273
+ /**
274
+ * Build a catalog from the generated manifest and the kit's registered views.
275
+ *
276
+ * Components that accept slot content get the Empty scenario ahead of their
277
+ * own, labeled as the manifest says. Each component's `shortTitle` drops its
278
+ * source's label prefix. The navigation lists each source's views and its
279
+ * component tree interleaved by `order`; a source whose views set no
280
+ * component tree lists its components under `Components`, first among the
281
+ * entries of order 0. Components that are not standalone and unlisted stories stay in
282
+ * `components` but out of the navigation. The kit's targets are the manifest's,
283
+ * the three targets when it names none, with `html` only where it names
284
+ * it, and each component's targets are narrowed to the targets among them.
285
+ * The pane widths are the manifest's, narrowest first, or
286
+ * `DEFAULT_PANE_WIDTHS` when it names none, the Svelte mode the
287
+ * manifest's, `legacy` when it names none, and the landing entry the
288
+ * manifest's.
289
+ *
290
+ * @param manifest - The generated manifest.
291
+ * @param views - The kit's registered views (rendered views and component-tree markers).
292
+ * @returns The catalog.
293
+ */
294
+ export function catalogFromManifest(
295
+ manifest: CatalogManifest,
296
+ views: PreviewView[] = []
297
+ ): PreviewCatalog {
298
+ const sources = (
299
+ manifest.sources?.length ? manifest.sources : DEFAULT_SOURCES
300
+ )
301
+ .slice()
302
+ .sort((a, b) => a.order - b.order);
303
+ const rootSourceId = sources[0].id;
304
+ const targets = PREVIEW_TARGETS.filter(
305
+ (target) => manifest.targets?.includes(target) ?? target !== 'html'
306
+ );
307
+ const emptyScenario: PreviewScenario = {
308
+ id: EMPTY_SCENARIO_ID,
309
+ label: manifest.labels?.emptyScenario ?? 'Empty',
310
+ props: {},
311
+ empty: true,
312
+ };
313
+ const components: PreviewComponent[] = manifest.components.map(
314
+ (component) => {
315
+ const source = component.source ?? rootSourceId;
316
+ const hasContent =
317
+ component.slots.default || component.slots.named.length > 0;
318
+ return {
319
+ id: component.id,
320
+ name: component.name,
321
+ ...(component.description !== undefined
322
+ ? { description: component.description }
323
+ : {}),
324
+ title: component.title,
325
+ shortTitle: stripSourcePrefix(
326
+ component.title,
327
+ sources.find((candidate) => candidate.id === source)
328
+ ),
329
+ source,
330
+ standalone: component.standalone !== false,
331
+ unlisted: component.unlisted === true,
332
+ propTypes: component.propTypes,
333
+ events: component.events ?? [],
334
+ bindings: component.bindings ?? [],
335
+ defaultProps: component.defaultProps,
336
+ previewDefaults: component.previewDefaults ?? {},
337
+ scenarios: hasContent
338
+ ? [emptyScenario, ...component.scenarios]
339
+ : [...component.scenarios],
340
+ slots: component.slots,
341
+ ...(component.content ? { content: component.content } : {}),
342
+ ...(component.around ? { around: component.around } : {}),
343
+ ...(component.options
344
+ ? {
345
+ options: component.options as Record<string, OptionValue | false>,
346
+ }
347
+ : {}),
348
+ targets: (component.targets ?? PREVIEW_SCRIPTED_TARGETS).filter(
349
+ (target) => targets.includes(target)
350
+ ),
351
+ };
352
+ }
353
+ );
354
+ const catalogViews: PreviewCatalogView[] = views
355
+ .filter((view): view is RenderedView => !view.componentTree)
356
+ .map((view) => ({
357
+ id: view.id,
358
+ label: view.label,
359
+ source: view.source ?? rootSourceId,
360
+ order: view.order ?? 0,
361
+ framework: view.framework ?? 'dom',
362
+ }))
363
+ .sort((a, b) => a.order - b.order);
364
+ return {
365
+ kitName: manifest.kitName ?? 'kit',
366
+ ...(manifest.kitVersion ? { kitVersion: manifest.kitVersion } : {}),
367
+ targets,
368
+ sources,
369
+ components,
370
+ views: catalogViews,
371
+ ...(manifest.codeBlock ? { codeBlock: manifest.codeBlock } : {}),
372
+ navigation: buildNavigation(sources, components, views),
373
+ htmlTags: [...HTML_TAGS],
374
+ colorTokens: (manifest.colorOptions ?? []).map((token) => token ?? null),
375
+ paneWidths: (manifest.paneWidths ?? DEFAULT_PANE_WIDTHS)
376
+ .map((paneWidth) => ({ ...paneWidth }))
377
+ .sort((a, b) => a.width - b.width),
378
+ ...(manifest.landing
379
+ ? {
380
+ landing:
381
+ 'view' in manifest.landing
382
+ ? { kind: 'view' as const, id: manifest.landing.view }
383
+ : {
384
+ kind: 'component' as const,
385
+ id: manifest.landing.component,
386
+ ...(manifest.landing.scenario !== undefined
387
+ ? { scenario: manifest.landing.scenario }
388
+ : {}),
389
+ },
390
+ }
391
+ : {}),
392
+ imports: manifest.imports ?? {},
393
+ svelteMode: manifest.svelteMode ?? 'legacy',
394
+ reactAttributeNames: manifest.reactAttributeNames ?? {},
395
+ };
396
+ }
397
+
398
+ function stripSourcePrefix(
399
+ title: string,
400
+ source: PreviewSource | undefined
401
+ ): string {
402
+ const prefix = source ? `${source.label}/` : '';
403
+ return prefix && title.startsWith(prefix)
404
+ ? title.slice(prefix.length)
405
+ : title;
406
+ }
407
+
408
+ // --- navigation ---------------------------------------------------------------
409
+
410
+ interface TitleTreeNode {
411
+ label: string;
412
+ children: TitleTreeNode[];
413
+ component?: PreviewComponent;
414
+ }
415
+
416
+ /** A source's stories as a category tree: titles (minus the source's label)
417
+ * split on `/` into folders and leaves, folders first, each level alphabetical. */
418
+ function buildTitleTree(
419
+ components: PreviewComponent[],
420
+ rootLabel: string
421
+ ): TitleTreeNode {
422
+ const root: TitleTreeNode = { label: rootLabel, children: [] };
423
+ const prefix = `${rootLabel}/`;
424
+ for (const component of components) {
425
+ const rest = component.title.startsWith(prefix)
426
+ ? component.title.slice(prefix.length)
427
+ : component.title;
428
+ const segments = rest.split('/');
429
+ let node = root;
430
+ segments.forEach((segment, index) => {
431
+ let child = node.children.find(
432
+ (candidate) => candidate.label === segment
433
+ );
434
+ if (!child) {
435
+ child = { label: segment, children: [] };
436
+ node.children.push(child);
437
+ }
438
+ if (index === segments.length - 1) child.component = component;
439
+ node = child;
440
+ });
441
+ }
442
+ const sort = (treeNode: TitleTreeNode) => {
443
+ treeNode.children.sort((a, b) => {
444
+ const aFolder = a.children.length > 0;
445
+ const bFolder = b.children.length > 0;
446
+ if (aFolder !== bFolder) return aFolder ? -1 : 1;
447
+ return a.label.localeCompare(b.label);
448
+ });
449
+ treeNode.children.forEach(sort);
450
+ };
451
+ sort(root);
452
+ return root;
453
+ }
454
+
455
+ function titleTreeToNavigation(
456
+ node: TitleTreeNode,
457
+ parentKey: string
458
+ ): PreviewNavigationNode {
459
+ const key = `${parentKey}/${node.label}`;
460
+ if (node.children.length > 0) {
461
+ return {
462
+ kind: 'group',
463
+ key,
464
+ label: node.label,
465
+ children: node.children.map((child) => titleTreeToNavigation(child, key)),
466
+ };
467
+ }
468
+ const component = node.component!;
469
+ return {
470
+ kind: 'component',
471
+ id: component.id,
472
+ label: node.label,
473
+ title: component.title,
474
+ };
475
+ }
476
+
477
+ /** A source's component tree as one group, or `null` when it has no stories. */
478
+ function componentsEntry(
479
+ source: PreviewSource,
480
+ components: PreviewComponent[],
481
+ entryLabel: string
482
+ ): PreviewNavigationNode | null {
483
+ const stories = components.filter(
484
+ (component) =>
485
+ component.standalone &&
486
+ !component.unlisted &&
487
+ component.source === source.id
488
+ );
489
+ if (stories.length === 0) return null;
490
+ const root = buildTitleTree(stories, source.label);
491
+ const key = `components:${source.id}`;
492
+ return {
493
+ kind: 'group',
494
+ key,
495
+ label: entryLabel,
496
+ children: root.children.map((child) => titleTreeToNavigation(child, key)),
497
+ };
498
+ }
499
+
500
+ /** The label of a source's component tree where no view sets one. */
501
+ const COMPONENT_TREE_LABEL = 'Components';
502
+
503
+ /** A source's entries: its views and its component tree, by `order`. A source
504
+ * with stories whose views set no component tree gets one, labeled
505
+ * `Components`, first among the entries of its order. */
506
+ function sourceEntries(
507
+ source: PreviewSource,
508
+ rootSourceId: string,
509
+ components: PreviewComponent[],
510
+ views: PreviewView[]
511
+ ): PreviewNavigationNode[] {
512
+ const scoped = views.filter(
513
+ (view) => (view.source ?? rootSourceId) === source.id
514
+ );
515
+ const entries: { order: number; entry: PreviewNavigationNode | null }[] =
516
+ scoped.map((view) => ({
517
+ order: view.order ?? 0,
518
+ entry: view.componentTree
519
+ ? componentsEntry(source, components, view.label)
520
+ : ({
521
+ kind: 'view',
522
+ id: view.id,
523
+ label: view.label,
524
+ } satisfies PreviewNavigationNode),
525
+ }));
526
+ if (!scoped.some((view) => view.componentTree))
527
+ entries.unshift({
528
+ order: 0,
529
+ entry: componentsEntry(source, components, COMPONENT_TREE_LABEL),
530
+ });
531
+ // A stable sort: the default tree stays ahead of the views of its order.
532
+ return entries
533
+ .sort((a, b) => a.order - b.order)
534
+ .map(({ entry }) => entry)
535
+ .filter((entry): entry is PreviewNavigationNode => entry !== null);
536
+ }
537
+
538
+ function buildNavigation(
539
+ sources: PreviewSource[],
540
+ components: PreviewComponent[],
541
+ views: PreviewView[]
542
+ ): PreviewNavigationNode[] {
543
+ const rootSourceId = sources[0].id;
544
+ const nodes: PreviewNavigationNode[] = [];
545
+ for (const source of sources) {
546
+ const entries = sourceEntries(source, rootSourceId, components, views);
547
+ if (source.id === rootSourceId) nodes.push(...entries);
548
+ else if (entries.length > 0)
549
+ nodes.push({
550
+ kind: 'group',
551
+ key: `source:${source.id}`,
552
+ label: source.label,
553
+ children: entries,
554
+ });
555
+ }
556
+ return nodes;
557
+ }
558
+
559
+ // --- helpers over a catalog ---------------------------------------------------
560
+
561
+ /** One choice of a scenario picker. */
562
+ export interface ScenarioChoice {
563
+ /** The scenario id; `''` is the anonymous default. */
564
+ value: string;
565
+ label: string;
566
+ }
567
+
568
+ /**
569
+ * The scenario picker's choices for a component: its baseline first, labeled
570
+ * as the default, then the rest. The Empty scenario renders through a
571
+ * regeneration, so it is left out where the preview cannot regenerate, as in
572
+ * a static build. Empty when the component offers no choice beyond its
573
+ * default, so a shell can leave the picker out.
574
+ *
575
+ * @param component - The component.
576
+ * @param capabilities - What the preview can do: the controller's
577
+ * `capabilities`.
578
+ * @returns The choices, or an empty list.
579
+ * @example
580
+ * ```ts
581
+ * const choices = scenarioChoices(component, controller.capabilities);
582
+ * ```
583
+ */
584
+ export function scenarioChoices(
585
+ component: PreviewComponent | undefined,
586
+ capabilities: { regeneration: boolean }
587
+ ): ScenarioChoice[] {
588
+ if (!component) return [];
589
+ const baseline = component.scenarios.find((scenario) => scenario.default);
590
+ const others = component.scenarios.filter(
591
+ (scenario) =>
592
+ !scenario.default && (capabilities.regeneration || !scenario.empty)
593
+ );
594
+ if (others.length === 0) return [];
595
+ return [
596
+ baseline
597
+ ? { value: baseline.id, label: `${baseline.label} (default)` }
598
+ : { value: '', label: '(default)' },
599
+ ...others.map((scenario) => ({
600
+ value: scenario.id,
601
+ label: scenario.label,
602
+ })),
603
+ ];
604
+ }
605
+
606
+ /** A story to mount in a detached pane: a component, a scenario and props. */
607
+ export interface PreviewStoryReference {
608
+ /** The component's story id (`PreviewComponent.id`). */
609
+ component: string;
610
+ /** The scenario's id; `''` is the anonymous default. Omitted, the story's
611
+ * starting scenario, as selecting the story picks it. */
612
+ scenario?: string;
613
+ /** Props laid over the scenario's. */
614
+ props?: Record<string, unknown>;
615
+ }
616
+
617
+ /**
618
+ * The key a detached story pane's readiness is reported under in the
619
+ * state's `storyFrames`: the target, the component, then the scenario and
620
+ * the props as given. Two panes of the same story, scenario and props share
621
+ * a key, and it reads `ready` once either has loaded.
622
+ *
623
+ * @param target - The target the pane renders in.
624
+ * @param story - The story, as passed to `frames.mountStory`.
625
+ * @returns The key, such as `react:components-button:ghost`.
626
+ * @example
627
+ * ```ts
628
+ * const ready = state.storyFrames[storyFrameKey('vue', { component: id, scenario: 'ghost' })] === 'ready';
629
+ * ```
630
+ */
631
+ export function storyFrameKey(
632
+ target: PreviewScriptedTarget,
633
+ story: PreviewStoryReference
634
+ ): string {
635
+ return [
636
+ target,
637
+ story.component,
638
+ ...(story.scenario !== undefined ? [story.scenario] : []),
639
+ ...(story.props !== undefined ? [JSON.stringify(story.props)] : []),
640
+ ].join(':');
641
+ }
642
+
643
+ /**
644
+ * The scenario a component starts on: its default-flagged one, else `''`,
645
+ * the anonymous default.
646
+ *
647
+ * @param component - The component.
648
+ * @returns The scenario id.
649
+ */
650
+ export function defaultScenarioId(
651
+ component: PreviewComponent | undefined
652
+ ): string {
653
+ return component?.scenarios.find((scenario) => scenario.default)?.id ?? '';
654
+ }
655
+
656
+ /**
657
+ * The slots a node of the editor's tree holds content in: a component's
658
+ * default slot (when it has one) and then its named slots, as the catalog
659
+ * records them; an HTML tag's default slot; nothing for text or an opaque
660
+ * fill. The tree itself counts as its root component's node.
661
+ *
662
+ * @param catalog - The catalog the node's component is looked up in.
663
+ * @param node - A node of the tree, or the tree itself for its root.
664
+ * @returns The slot names, the default slot first.
665
+ * @example
666
+ * ```ts
667
+ * const tree = controller.getState().editor.tree!;
668
+ * for (const slot of nodeSlots(controller.catalog, tree)) render(slot, tree.slots[slot] ?? []);
669
+ * ```
670
+ */
671
+ export function nodeSlots(
672
+ catalog: Pick<PreviewCatalog, 'components'>,
673
+ node: EditorNode | EditorState
674
+ ): string[] {
675
+ let name: string;
676
+ if ('root' in node) name = node.root;
677
+ else if (!isContainer(node)) return [];
678
+ else if (node.kind === 'html') return [DEFAULT_SLOT];
679
+ else name = node.component;
680
+ const slots = catalog.components.find(
681
+ (component) => component.name === name
682
+ )?.slots;
683
+ if (!slots) return [];
684
+ return [...(slots.default ? [DEFAULT_SLOT] : []), ...slots.named];
685
+ }
686
+
687
+ /**
688
+ * The slots the editor can add plain content to in a node of its tree: the
689
+ * node's slots ({@link nodeSlots}) without those whose content must take
690
+ * their scope, which take only a scoped fill the editor cannot author. Such a
691
+ * slot still holds and shows the fill a story gives it.
692
+ *
693
+ * @param catalog - The catalog the node's component is looked up in.
694
+ * @param node - A node of the tree, or the tree itself for its root.
695
+ * @returns The slot names, the default slot first.
696
+ * @example
697
+ * ```ts
698
+ * const targets = nodeAddSlots(controller.catalog, controller.getState().editor.tree!);
699
+ * ```
700
+ */
701
+ export function nodeAddSlots(
702
+ catalog: Pick<PreviewCatalog, 'components'>,
703
+ node: EditorNode | EditorState
704
+ ): string[] {
705
+ const name =
706
+ 'root' in node
707
+ ? node.root
708
+ : isContainer(node) && node.kind !== 'html'
709
+ ? node.component
710
+ : undefined;
711
+ const scopeRequired =
712
+ name === undefined
713
+ ? []
714
+ : (catalog.components.find((component) => component.name === name)?.slots
715
+ .scopeRequired ?? []);
716
+ return nodeSlots(catalog, node).filter(
717
+ (slot) => !scopeRequired.includes(slot)
718
+ );
719
+ }