@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/model.ts ADDED
@@ -0,0 +1,568 @@
1
+ // Build the framework-agnostic ResolvedStory for one component: merge prop-derived
2
+ // propTypes with the story file (controls, prop defaults, slots, scenarios, omit).
3
+
4
+ import {
5
+ isEventType,
6
+ relayedBindingsOf,
7
+ twoWayBindingsOf,
8
+ } from '@js-template-engine/core';
9
+
10
+ import { propTypeForProp, defaultPropForProp } from './prop-types.js';
11
+ import { isStructurallyOmitted } from './story.js';
12
+ import { collectSlotComponents } from './lib/slot-fill.js';
13
+ import { EMPTY_SCENARIO_ID, sanitize, scenarioStoryId } from './lib/naming.js';
14
+ import { isVoidElement } from './lib/void-elements.js';
15
+ import { ALL_TARGETS } from './lib/types.js';
16
+ import type {
17
+ PropTypeSpecification,
18
+ EnumeratedComponent,
19
+ JsonValue,
20
+ Scenario,
21
+ SlotFill,
22
+ SlotPlan,
23
+ Story,
24
+ PreviewBinding,
25
+ ResolvedStory,
26
+ PreviewPropType,
27
+ PreviewControlHints,
28
+ PreviewLabels,
29
+ Target,
30
+ } from './lib/types.js';
31
+
32
+ /** The category a component without a story file `category` sits in. */
33
+ export const DEFAULT_CATEGORY = 'Components';
34
+
35
+ /**
36
+ * The two-way props a component's template declares, in prop order: each
37
+ * `bindable` object's prop, copy form or relayed, the props it names, and
38
+ * whether a Vue consumer binds it with a bare `v-model`. A component written
39
+ * by hand declares none.
40
+ *
41
+ * @param component - The enumerated component.
42
+ * @returns The bindings.
43
+ */
44
+ export function bindingsOf(component: EnumeratedComponent): PreviewBinding[] {
45
+ const template = component.template;
46
+ if (!template || Array.isArray(template) || !template.props) return [];
47
+ const vue: unknown = template.extensions?.['vue'];
48
+ const vueModel =
49
+ typeof vue === 'object' && vue !== null
50
+ ? (vue as { model?: unknown }).model
51
+ : undefined;
52
+ const order = Object.keys(template.props);
53
+ return [
54
+ ...twoWayBindingsOf(template.props).map((binding) => ({
55
+ prop: binding.prop,
56
+ ...(binding.defaultProp !== undefined
57
+ ? { defaultProp: binding.defaultProp }
58
+ : {}),
59
+ changeProp: binding.changeProp,
60
+ })),
61
+ ...relayedBindingsOf(template.props).map((binding) => ({
62
+ prop: binding.prop,
63
+ changeProp: binding.changeProp,
64
+ })),
65
+ ]
66
+ .sort(
67
+ (first, second) => order.indexOf(first.prop) - order.indexOf(second.prop)
68
+ )
69
+ .map(({ changeProp, ...binding }) => ({
70
+ ...binding,
71
+ ...(changeProp !== undefined ? { changeProp } : {}),
72
+ ...(vueModel === binding.prop ? { vueModel: true } : {}),
73
+ ...(template.typeParameters?.length ? { generic: true } : {}),
74
+ }));
75
+ }
76
+
77
+ /**
78
+ * The values a prop's scenarios should cover: each select option and each
79
+ * boolean value other than the prop's default (a select's `false` too only
80
+ * when the prop has one), for every included select and
81
+ * boolean control. Shared by scenario generation and the check's coverage.
82
+ */
83
+ export function scenarioValues(
84
+ propTypes: readonly PropTypeSpecification[],
85
+ defaultProps: Record<string, JsonValue>
86
+ ): Array<{ prop: string; value: string | boolean }> {
87
+ const values: Array<{ prop: string; value: string | boolean }> = [];
88
+ for (const propType of propTypes) {
89
+ if (!propType.include) continue;
90
+ const current = defaultProps[propType.name];
91
+ if (propType.control === 'boolean') {
92
+ values.push({ prop: propType.name, value: current !== true });
93
+ } else if (propType.control === 'select') {
94
+ for (const option of propType.options ?? []) {
95
+ // As for a toggle, `false` is no departure from a prop left unset.
96
+ if (
97
+ option !== undefined &&
98
+ option !== current &&
99
+ !(option === false && current === undefined)
100
+ )
101
+ values.push({ prop: propType.name, value: option });
102
+ }
103
+ }
104
+ }
105
+ return values;
106
+ }
107
+
108
+ /** Whether a story file's `generate` lets this prop generate scenarios. */
109
+ export function generatesProp(
110
+ generate: Story['generate'],
111
+ prop: string
112
+ ): boolean {
113
+ if (generate === undefined) return true;
114
+ if (generate === false) return false;
115
+ if (Array.isArray(generate)) return generate.includes(prop);
116
+ return !generate.exclude.includes(prop);
117
+ }
118
+
119
+ /** The keys a story file sets. */
120
+ const STORY_KEYS = [
121
+ 'omit',
122
+ 'standalone',
123
+ 'unlisted',
124
+ 'category',
125
+ 'label',
126
+ 'defaultProps',
127
+ 'controls',
128
+ 'fixed',
129
+ 'generate',
130
+ 'slots',
131
+ 'around',
132
+ 'scenarios',
133
+ 'options',
134
+ ] as const satisfies readonly (keyof Story)[];
135
+
136
+ /** What to write for a key a story file does not take, where one thing is meant. */
137
+ const STORY_KEY_REPLACEMENTS: ReadonlyMap<string, string> = new Map([
138
+ ['partial', 'standalone: false'],
139
+ ['description', 'description on the template'],
140
+ ]);
141
+
142
+ /** The keys a curated scenario may set. */
143
+ const CURATED_SCENARIO_KEYS: ReadonlySet<string> = new Set([
144
+ 'id',
145
+ 'label',
146
+ 'description',
147
+ 'props',
148
+ 'slots',
149
+ 'options',
150
+ 'default',
151
+ ]);
152
+
153
+ /**
154
+ * The component of the first element fill, at any depth of a fill tree,
155
+ * that fills its default slot through `slots.default` rather than
156
+ * `children`, or `undefined` when none does.
157
+ */
158
+ function elementFillSettingDefaultSlot(
159
+ fill: SlotFill | undefined
160
+ ): string | undefined {
161
+ if (!fill || typeof fill !== 'object' || Array.isArray(fill))
162
+ return undefined;
163
+ if (fill.kind === 'element') {
164
+ if (fill.slots && 'default' in fill.slots) return fill.component;
165
+ for (const nested of [...Object.values(fill.slots ?? {}), fill.children]) {
166
+ const found = elementFillSettingDefaultSlot(nested);
167
+ if (found !== undefined) return found;
168
+ }
169
+ } else if (fill.kind === 'html') {
170
+ return elementFillSettingDefaultSlot(fill.children);
171
+ } else if (fill.kind === 'fragment') {
172
+ for (const child of fill.children ?? []) {
173
+ const found = elementFillSettingDefaultSlot(child);
174
+ if (found !== undefined) return found;
175
+ }
176
+ }
177
+ return undefined;
178
+ }
179
+
180
+ /**
181
+ * The tag of the first html fill, at any depth of a fill tree, that gives a
182
+ * void element (img, input, br) children, or `undefined` when none does.
183
+ */
184
+ function voidElementWithChildren(
185
+ fill: SlotFill | undefined
186
+ ): string | undefined {
187
+ if (!fill || typeof fill !== 'object' || Array.isArray(fill))
188
+ return undefined;
189
+ if (fill.kind === 'html') {
190
+ if (fill.children !== undefined && isVoidElement(fill.tag)) return fill.tag;
191
+ return voidElementWithChildren(fill.children);
192
+ }
193
+ if (fill.kind === 'element') {
194
+ for (const nested of [...Object.values(fill.slots ?? {}), fill.children]) {
195
+ const found = voidElementWithChildren(nested);
196
+ if (found !== undefined) return found;
197
+ }
198
+ } else if (fill.kind === 'fragment') {
199
+ for (const child of fill.children ?? []) {
200
+ const found = voidElementWithChildren(child);
201
+ if (found !== undefined) return found;
202
+ }
203
+ }
204
+ return undefined;
205
+ }
206
+
207
+ /** The id a generated scenario takes: `<prop>-<value>`, or the prop for a
208
+ * boolean turned on, unless that is the preview's own Empty scenario's id,
209
+ * which a boolean named `empty` takes as `empty-true`. A curated scenario
210
+ * with this id replaces it. */
211
+ export function generatedScenarioId(
212
+ prop: string,
213
+ value: string | boolean
214
+ ): string {
215
+ if (value === true) return prop === EMPTY_SCENARIO_ID ? `${prop}-true` : prop;
216
+ return `${prop}-${String(value)}`;
217
+ }
218
+
219
+ /** The default label of a generated scenario: `prop: value`, or the bare prop
220
+ * for a boolean turned on. */
221
+ function defaultGeneratedLabel(prop: string, value: string | boolean): string {
222
+ return value === true ? prop : `${prop}: ${String(value)}`;
223
+ }
224
+
225
+ /** Whether a scenario sets exactly one prop, to this value. */
226
+ function setsOnly(scenario: Scenario, prop: string, value: JsonValue): boolean {
227
+ const entries = Object.entries(scenario.props ?? {});
228
+ return (
229
+ entries.length === 1 && entries[0][0] === prop && entries[0][1] === value
230
+ );
231
+ }
232
+
233
+ /**
234
+ * One scenario per value `scenarioValues` lists, narrowed by the story file's
235
+ * `generate`, skipping a value a curated scenario already sets alone, and any
236
+ * id a curated scenario already uses: that scenario replaces the generated one.
237
+ * Labeled by the kit's `generatedScenario`, or `prop: value` and the bare prop
238
+ * for a boolean turned on.
239
+ */
240
+ function generateScenarios(
241
+ propTypes: readonly PropTypeSpecification[],
242
+ defaultProps: Record<string, JsonValue>,
243
+ story: Story | undefined,
244
+ labels: PreviewLabels
245
+ ): Scenario[] {
246
+ const generate = story?.generate;
247
+ if (generate === false) return [];
248
+ const curated = story?.scenarios ?? [];
249
+ const takenIds = new Set(curated.map((scenario) => scenario.id));
250
+ const label = labels.generatedScenario ?? defaultGeneratedLabel;
251
+ const scenarios: Scenario[] = [];
252
+ for (const { prop, value } of scenarioValues(propTypes, defaultProps)) {
253
+ if (!generatesProp(generate, prop)) continue;
254
+ if (curated.some((scenario) => setsOnly(scenario, prop, value))) continue;
255
+ const id = generatedScenarioId(prop, value);
256
+ if (takenIds.has(id)) continue;
257
+ takenIds.add(id);
258
+ scenarios.push({
259
+ id,
260
+ label: label(prop, value),
261
+ props: { [prop]: value },
262
+ generated: true,
263
+ });
264
+ }
265
+ return scenarios;
266
+ }
267
+
268
+ /**
269
+ * Resolve the story of one component: its controls, default props,
270
+ * scenarios, slot content and the targets it renders in.
271
+ *
272
+ * @param component - The enumerated component.
273
+ * @param story - The component's story file, when it has one.
274
+ * @param hints - The kit's control hints.
275
+ * @param labels - The kit's preview labels.
276
+ * @param omitPattern - Component names matching it get no story.
277
+ * @param kitTargets - The scripted targets the kit builds; the component's own
278
+ * targets are narrowed to them.
279
+ * @returns The resolved story.
280
+ */
281
+ export function resolveStory(
282
+ component: EnumeratedComponent,
283
+ story: Story | undefined,
284
+ hints: PreviewControlHints = {},
285
+ labels: PreviewLabels = {},
286
+ omitPattern?: RegExp,
287
+ kitTargets: readonly Target[] = ALL_TARGETS
288
+ ): ResolvedStory {
289
+ const omit =
290
+ (story?.omit ?? false) ||
291
+ isStructurallyOmitted(component.name, omitPattern);
292
+ // A component that is not standalone (a compound's part, a shared building
293
+ // block) stays in the manifest for the editor but gets no story - set on the
294
+ // story file (templates) or the manual spec (hand-written components).
295
+ const standalone =
296
+ (story?.standalone ?? true) && (component.standalone ?? true);
297
+ // An unlisted story is generated as any other, only left out of what a
298
+ // reader browses; a component with no story of its own has nothing to hide.
299
+ const unlisted =
300
+ ((story?.unlisted ?? false) || (component.unlisted ?? false)) &&
301
+ !omit &&
302
+ standalone;
303
+
304
+ // propTypes from props, then story file control overrides (false disables).
305
+ const propTypes: PreviewPropType[] = [];
306
+ for (const [propName, def] of Object.entries(component.props)) {
307
+ let propType = propTypeForProp(component.name, propName, def, hints);
308
+ if (def.description)
309
+ propType = { ...propType, description: def.description };
310
+ const override = story?.controls?.[propName];
311
+ if (override === false) propType = { ...propType, include: false };
312
+ else if (override) propType = { ...propType, ...override };
313
+ // A fixed prop is always passed and never shown.
314
+ if (story?.fixed && propName in story.fixed)
315
+ propType = { ...propType, include: false };
316
+ propTypes.push({
317
+ ...propType,
318
+ declaredType: def.type,
319
+ required: def.required === true,
320
+ ...(def.default !== undefined ? { declaredDefault: def.default } : {}),
321
+ ...(def.documentedDefault !== undefined
322
+ ? { documentedDefault: def.documentedDefault }
323
+ : {}),
324
+ });
325
+ }
326
+
327
+ // default props: template defaults + size config, then story file defaultProps.
328
+ const defaultProps: Record<string, JsonValue> = {};
329
+ // The values the preview starts a prop from that a consumer never writes:
330
+ // a hint's, for a prop the component gives no default and the story file
331
+ // leaves alone.
332
+ const previewDefaults: Record<string, JsonValue> = {};
333
+ for (const [propName, def] of Object.entries(component.props)) {
334
+ const value = defaultPropForProp(component.name, propName, def, hints);
335
+ if (value !== undefined) defaultProps[propName] = value;
336
+ if (
337
+ value !== undefined &&
338
+ def.default === undefined &&
339
+ !(propName in (story?.defaultProps ?? {})) &&
340
+ !(propName in (story?.fixed ?? {}))
341
+ )
342
+ previewDefaults[propName] = value;
343
+ }
344
+ // A component with a default slot gets visible placeholder text - but only
345
+ // when the story file declares no slot plan at all. A story file that curates any
346
+ // slot (a named fill, an explicit default) owns the slot story: injecting
347
+ // children beside a named fill fabricates a mount no consumer writes.
348
+ // `slots: {}` forces a genuinely slotless mount. A default slot that
349
+ // exposes values gets none either: its content is a function of those
350
+ // values (React calls `children(scope)`), so text there is a mount no
351
+ // consumer writes, and the slot's fallback renders instead.
352
+ if (
353
+ component.hasDefaultSlot &&
354
+ !component.defaultSlotScoped &&
355
+ !story?.slots &&
356
+ defaultProps.children === undefined
357
+ ) {
358
+ defaultProps.children = component.name;
359
+ // The preview's own, which a consumer's source leaves out, unless the
360
+ // story file gives the slot text of its own.
361
+ if (
362
+ !('children' in (story?.defaultProps ?? {})) &&
363
+ !('children' in (story?.fixed ?? {}))
364
+ )
365
+ previewDefaults.children = component.name;
366
+ }
367
+ Object.assign(defaultProps, story?.defaultProps ?? {}, story?.fixed ?? {});
368
+
369
+ // A named slot spelled flat (`slots: { header: ... }` instead of
370
+ // `slots: { named: { header: ... } }`) would silently never render - the
371
+ // emitters read only `default` and `named`. Fail loudly instead. A
372
+ // scenario's own slots are a plan of the same shape.
373
+ const slotPlans: Array<[string, SlotPlan | undefined]> = [
374
+ ['', story?.slots],
375
+ ...(story?.scenarios ?? [])
376
+ .filter((scenario) => scenario.generated !== true)
377
+ .map((scenario): [string, SlotPlan | undefined] => [
378
+ `scenario '${scenario.id}': `,
379
+ scenario.slots,
380
+ ]),
381
+ ];
382
+ for (const [where, plan] of slotPlans) {
383
+ if (plan === undefined) continue;
384
+ if (typeof plan !== 'object' || plan === null || Array.isArray(plan)) {
385
+ throw new Error(
386
+ `${component.name}: ${where}slots must be a slot plan, { default, named }`
387
+ );
388
+ }
389
+ const unknownSlotKeys = Object.keys(plan).filter(
390
+ (key) => key !== 'default' && key !== 'named'
391
+ );
392
+ if (unknownSlotKeys.length > 0) {
393
+ throw new Error(
394
+ `${component.name}: ${where}unknown slot-plan key(s) ${unknownSlotKeys.join(', ')} - ` +
395
+ `named slots go under "named": { slots: { named: { ... } } }.`
396
+ );
397
+ }
398
+ }
399
+ const planFills = slotPlans.flatMap(([, plan]) => [
400
+ plan?.default,
401
+ ...Object.values(plan?.named ?? {}),
402
+ ]);
403
+
404
+ // `around` holds the content before and after the component, and a scoped
405
+ // fill has no slot there to give it values.
406
+ const around: unknown = story?.around;
407
+ if (around !== undefined) {
408
+ if (typeof around !== 'object' || around === null || Array.isArray(around))
409
+ throw new Error(
410
+ `${component.name}: around must be { before, after }, each a fill`
411
+ );
412
+ const unknownAroundKeys = Object.keys(around).filter(
413
+ (key) => key !== 'before' && key !== 'after'
414
+ );
415
+ if (unknownAroundKeys.length > 0)
416
+ throw new Error(
417
+ `${component.name}: unknown around key(s) ${unknownAroundKeys.join(', ')} - around takes before and after`
418
+ );
419
+ for (const [where, fill] of Object.entries(around) as Array<
420
+ [string, SlotFill | undefined]
421
+ >) {
422
+ if (fill?.kind === 'scoped')
423
+ throw new Error(
424
+ `${component.name}: around.${where} is a scoped fill, which takes values from a slot; beside the component there is none`
425
+ );
426
+ }
427
+ }
428
+ const aroundFills = [story?.around?.before, story?.around?.after];
429
+
430
+ // The composed component's default slot takes an element fill's
431
+ // `children`; `slots` names its named slots only. A `default` there would
432
+ // otherwise reach the emitters as a slot named `default`, which Svelte
433
+ // refuses as a reserved word.
434
+ for (const fill of [...planFills, ...aroundFills]) {
435
+ const filled = elementFillSettingDefaultSlot(fill);
436
+ if (filled !== undefined) {
437
+ throw new Error(
438
+ `${component.name}: the element fill of '${filled}' sets slots.default - its default slot takes children`
439
+ );
440
+ }
441
+ }
442
+
443
+ // A void element renders self-closed, so children on one would be dropped
444
+ // in one framework and refused by another's compiler.
445
+ for (const fill of [...planFills, ...aroundFills]) {
446
+ const tag = voidElementWithChildren(fill);
447
+ if (tag !== undefined) {
448
+ throw new Error(
449
+ `${component.name}: the html fill <${tag}> has children - ${tag} is a void element and takes none`
450
+ );
451
+ }
452
+ }
453
+
454
+ // A story file sets only what a story holds; a key the preview does not
455
+ // read would otherwise change nothing without a word.
456
+ const unknownStoryKey = Object.keys(story ?? {}).find(
457
+ (key) => !(STORY_KEYS as readonly string[]).includes(key)
458
+ );
459
+ if (unknownStoryKey !== undefined) {
460
+ const replacement = STORY_KEY_REPLACEMENTS.get(unknownStoryKey);
461
+ throw new Error(
462
+ `${component.name}: story sets unknown key '${unknownStoryKey}' - ${
463
+ replacement === undefined
464
+ ? `a story sets ${STORY_KEYS.join(', ')}`
465
+ : `use ${replacement}`
466
+ }`
467
+ );
468
+ }
469
+
470
+ // A curated scenario sets only what a scenario holds. The scenarios the
471
+ // preview generates itself reach the model beside them, marked
472
+ // `generated`, and are not the story file's to check.
473
+ for (const scenario of story?.scenarios ?? []) {
474
+ if (scenario.generated === true) continue;
475
+ if (scenario.id === EMPTY_SCENARIO_ID)
476
+ throw new Error(
477
+ `${component.name}: scenario id '${EMPTY_SCENARIO_ID}' is the preview's own Empty scenario's; give the scenario another id`
478
+ );
479
+ const unknownKey = Object.keys(scenario).find(
480
+ (key) => !CURATED_SCENARIO_KEYS.has(key)
481
+ );
482
+ if (unknownKey !== undefined) {
483
+ throw new Error(
484
+ `${component.name}: scenario '${scenario.id}' sets unknown key '${unknownKey}' - a scenario sets ${[...CURATED_SCENARIO_KEYS].join(', ')}`
485
+ );
486
+ }
487
+ }
488
+
489
+ // A scenario's description is text for a shell to show, and its options
490
+ // are option values by id; anything else is a story file mistake that would
491
+ // otherwise reach the catalog unnoticed. Whether each names one of the
492
+ // kit's options is the check's to say, which reads the kit's options.
493
+ for (const scenario of story?.scenarios ?? []) {
494
+ if (
495
+ scenario.description !== undefined &&
496
+ typeof scenario.description !== 'string'
497
+ ) {
498
+ throw new Error(
499
+ `${component.name}: scenario '${scenario.id}' has a description that is not a string.`
500
+ );
501
+ }
502
+ const options: unknown = scenario.options;
503
+ if (
504
+ options !== undefined &&
505
+ (typeof options !== 'object' ||
506
+ options === null ||
507
+ Array.isArray(options) ||
508
+ Object.values(options).some(
509
+ (value) => typeof value !== 'string' && typeof value !== 'boolean'
510
+ ))
511
+ ) {
512
+ throw new Error(
513
+ `${component.name}: scenario '${scenario.id}' has options that are not option values by id, each a string or a boolean.`
514
+ );
515
+ }
516
+ }
517
+
518
+ // slot components to import
519
+ const slotComponentSet = new Set<string>();
520
+ try {
521
+ // A scenario's own content is imported with the story's: one module
522
+ // per target holds them all.
523
+ for (const fill of planFills) collectSlotComponents(fill, slotComponentSet);
524
+ for (const fill of aroundFills)
525
+ collectSlotComponents(fill, slotComponentSet);
526
+ } catch (error) {
527
+ // Name the component so a bad story file is findable without a bisect.
528
+ throw new Error(`${component.name}: ${(error as Error).message}`, {
529
+ cause: error,
530
+ });
531
+ }
532
+
533
+ return {
534
+ name: component.name,
535
+ ...(component.description !== undefined
536
+ ? { description: component.description }
537
+ : {}),
538
+ title: `${story?.category ?? labels.category ?? DEFAULT_CATEGORY}/${story?.label ?? component.name}`,
539
+ omit,
540
+ standalone,
541
+ unlisted,
542
+ propTypes,
543
+ events: propTypes
544
+ .filter((propType) => isEventType(propType.declaredType))
545
+ .map((propType) => propType.name),
546
+ bindings: bindingsOf(component),
547
+ defaultProps,
548
+ ...(Object.keys(previewDefaults).length > 0 ? { previewDefaults } : {}),
549
+ slots: story?.slots,
550
+ ...(story?.around ? { around: story.around } : {}),
551
+ scenarios: [
552
+ ...(story?.scenarios ?? []),
553
+ ...generateScenarios(propTypes, defaultProps, story, labels),
554
+ ],
555
+ slotComponents: [...slotComponentSet],
556
+ contentSlots: {
557
+ default: component.hasDefaultSlot,
558
+ named: component.slotNames,
559
+ ...(component.scopeRequiredSlots?.length
560
+ ? { scopeRequired: component.scopeRequiredSlots }
561
+ : {}),
562
+ },
563
+ options: story?.options,
564
+ targets: (component.targets ?? ALL_TARGETS).filter((target) =>
565
+ kitTargets.includes(target)
566
+ ),
567
+ };
568
+ }