@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,530 @@
1
+ // The editor's node tree: its immutable path operations, seeding, and its round
2
+ // trip through the story file's slot fills, kept free of React so it can be tested
3
+ // beside the emitters. A story file's SlotPlan seeds the tree (fillToNodes); every
4
+ // structural edit goes through the path operations below and serializes back
5
+ // into the regeneration payload (toPayload).
6
+ import type {
7
+ AroundFill,
8
+ JsonValue,
9
+ SlotFill,
10
+ SlotPlan,
11
+ StoryAround,
12
+ } from '../lib/types.js';
13
+
14
+ export type { AroundFill, SlotFill, SlotPlan, StoryAround };
15
+
16
+ type Json = unknown;
17
+
18
+ /** A fill the editor cannot author: a `raw` body, a `scoped` slot or a
19
+ * `part` call, each per-target strings. It is held verbatim, so it can be
20
+ * moved or removed but not edited, and serializes back unchanged. */
21
+ export type OpaqueFill = Extract<SlotFill, { kind: 'raw' | 'scoped' | 'part' }>;
22
+
23
+ // The editor's authoring shape: a real component (props + content keyed by
24
+ // slot), a plain HTML tag (default slot only), a text leaf, or an opaque fill.
25
+ // `slots.default` is the default-slot children; other keys are named slots
26
+ // (elements only - HTML tags carry no named slots). `id` is the node's identity
27
+ // while the editor holds it (the sortable key that travels with it between
28
+ // slots); `toPayload` leaves it out, and `ensureIds` gives one to any node
29
+ // created without.
30
+ export type EditorNode =
31
+ | { kind: 'text'; value: string; id?: string }
32
+ | {
33
+ kind: 'element';
34
+ component: string;
35
+ props: Record<string, Json>;
36
+ slots: Record<string, EditorNode[]>;
37
+ id?: string;
38
+ }
39
+ | {
40
+ kind: 'html';
41
+ tag: string;
42
+ props: Record<string, Json>;
43
+ slots: Record<string, EditorNode[]>;
44
+ id?: string;
45
+ }
46
+ | { kind: 'opaque'; fill: OpaqueFill; id?: string };
47
+
48
+ /** A node that holds content of its own: an element or an HTML tag. */
49
+ export type ContainerNode = Extract<EditorNode, { kind: 'element' | 'html' }>;
50
+
51
+ export function isContainer(node: EditorNode): node is ContainerNode {
52
+ return node.kind === 'element' || node.kind === 'html';
53
+ }
54
+
55
+ export const DEFAULT_SLOT = 'default';
56
+
57
+ /** The plain wrapper tags an HTML node can be, picked from a list rather than
58
+ * typed: the layout and content elements a slot fill might need around its
59
+ * children. */
60
+ export const HTML_TAGS = [
61
+ 'div',
62
+ 'section',
63
+ 'article',
64
+ 'aside',
65
+ 'header',
66
+ 'footer',
67
+ 'main',
68
+ 'nav',
69
+ 'p',
70
+ 'span',
71
+ 'strong',
72
+ 'em',
73
+ 'small',
74
+ 'code',
75
+ 'pre',
76
+ 'blockquote',
77
+ 'ul',
78
+ 'ol',
79
+ 'li',
80
+ ];
81
+
82
+ let nodeIdCounter = 0;
83
+ /** Gives every node in the content an id, in place: idempotent, and the spreads
84
+ * every update makes carry a node's id along. */
85
+ export function ensureIds(content: Record<string, EditorNode[]>): void {
86
+ for (const list of Object.values(content)) {
87
+ for (const node of list) {
88
+ if (!node.id) node.id = `editor-node-${nodeIdCounter++}`;
89
+ if (isContainer(node)) ensureIds(node.slots);
90
+ }
91
+ }
92
+ }
93
+
94
+ /** The root's state: its component, its props and its content keyed by
95
+ * slot. The content the story renders beside the root is not the tree's: it
96
+ * is the story's (`PreviewComponent.around`), which no edit reaches. */
97
+ export interface EditorState {
98
+ root: string;
99
+ props: Record<string, Json>;
100
+ slots: Record<string, EditorNode[]>;
101
+ }
102
+
103
+ // --- paths and tree operations (immutable) ----------------------------------
104
+
105
+ /** One hop into a slot: which slot, and the node's index in it. */
106
+ export interface Step {
107
+ slot: string;
108
+ index: number;
109
+ }
110
+
111
+ /** A node's address: the hops from the root, one per slot it descends into.
112
+ * The empty path `[]` addresses the root itself. */
113
+ export type NodePath = Step[];
114
+
115
+ /** Where a move lands: into `slot` of the node at `parent`, before `index`
116
+ * (counted before the moved node is removed). */
117
+ export interface DropTarget {
118
+ parent: Step[];
119
+ slot: string;
120
+ index: number;
121
+ }
122
+
123
+ /** Apply `transform` to the node at `path` (path must be non-empty). */
124
+ export function updateAt(
125
+ content: Record<string, EditorNode[]>,
126
+ path: Step[],
127
+ transform: (node: EditorNode) => EditorNode
128
+ ): Record<string, EditorNode[]> {
129
+ const [step, ...rest] = path;
130
+ const list = content[step.slot] ?? [];
131
+ const nextList = list.map((node, index) => {
132
+ if (index !== step.index) return node;
133
+ if (rest.length === 0) return transform(node);
134
+ if (!isContainer(node)) return node;
135
+ return { ...node, slots: updateAt(node.slots, rest, transform) };
136
+ });
137
+ return { ...content, [step.slot]: nextList };
138
+ }
139
+
140
+ /** Insert `child` into `slot` of the node at `parentPath` (empty path = the
141
+ * root's slots) at `index`; an omitted index appends. */
142
+ export function insertAt(
143
+ content: Record<string, EditorNode[]>,
144
+ parentPath: Step[],
145
+ slot: string,
146
+ child: EditorNode,
147
+ index?: number
148
+ ): Record<string, EditorNode[]> {
149
+ if (parentPath.length === 0) {
150
+ const list = content[slot] ?? [];
151
+ const at = index ?? list.length;
152
+ return {
153
+ ...content,
154
+ [slot]: [...list.slice(0, at), child, ...list.slice(at)],
155
+ };
156
+ }
157
+ const [step, ...rest] = parentPath;
158
+ const list = content[step.slot] ?? [];
159
+ const nextList = list.map((node, position) =>
160
+ position === step.index && isContainer(node)
161
+ ? { ...node, slots: insertAt(node.slots, rest, slot, child, index) }
162
+ : node
163
+ );
164
+ return { ...content, [step.slot]: nextList };
165
+ }
166
+
167
+ /** Remove the node at `path` (path must be non-empty). */
168
+ export function removeAt(
169
+ content: Record<string, EditorNode[]>,
170
+ path: Step[]
171
+ ): Record<string, EditorNode[]> {
172
+ const [step, ...rest] = path;
173
+ const list = content[step.slot] ?? [];
174
+ if (rest.length === 0)
175
+ return {
176
+ ...content,
177
+ [step.slot]: list.filter((_, index) => index !== step.index),
178
+ };
179
+ const nextList = list.map((node, index) =>
180
+ index === step.index && isContainer(node)
181
+ ? { ...node, slots: removeAt(node.slots, rest) }
182
+ : node
183
+ );
184
+ return { ...content, [step.slot]: nextList };
185
+ }
186
+
187
+ /** The node at `path`, or null when nothing is there. The root is the tree
188
+ * itself rather than a node of its content, so the empty path gives null. */
189
+ export function nodeAt(
190
+ content: Record<string, EditorNode[]>,
191
+ path: Step[]
192
+ ): EditorNode | null {
193
+ let slots = content;
194
+ let node: EditorNode | null = null;
195
+ for (const step of path) {
196
+ node = (slots[step.slot] ?? [])[step.index] ?? null;
197
+ if (!node) return null;
198
+ slots = isContainer(node) ? node.slots : {};
199
+ }
200
+ return node;
201
+ }
202
+
203
+ /**
204
+ * The path of a child: the node at `index` in `slot` of the node at `parent`.
205
+ *
206
+ * @param parent - The parent's path; `[]` for the root.
207
+ * @param slot - The slot the child is in.
208
+ * @param index - The child's index in the slot.
209
+ * @returns The child's path.
210
+ */
211
+ export function childPath(
212
+ parent: NodePath,
213
+ slot: string,
214
+ index: number
215
+ ): NodePath {
216
+ return [...parent, { slot, index }];
217
+ }
218
+
219
+ /**
220
+ * A string that identifies a path, for a key or a lookup: `'root'` for the
221
+ * root, otherwise each step's slot and index.
222
+ *
223
+ * @param path - The path.
224
+ * @returns The key, such as `'default:0/header:1'`.
225
+ */
226
+ export function pathKey(path: NodePath): string {
227
+ return path.length === 0
228
+ ? 'root'
229
+ : path.map((step) => `${step.slot}:${step.index}`).join('/');
230
+ }
231
+
232
+ /** True if `candidate` addresses `path` itself or one of its ancestors
233
+ * (`candidate` is a prefix of `path`). Used to forbid dropping a node into its
234
+ * own subtree. */
235
+ function isAncestorOrSelf(candidate: Step[], path: Step[]): boolean {
236
+ if (candidate.length > path.length) return false;
237
+ return candidate.every(
238
+ (step, position) =>
239
+ step.slot === path[position].slot && step.index === path[position].index
240
+ );
241
+ }
242
+
243
+ /** Removing the node at `from` shifts the indices of its later siblings, so a
244
+ * target parent path that runs through one of them shifts down by one. */
245
+ function adjustForRemoval(target: Step[], from: Step[]): Step[] {
246
+ const fromParent = from.slice(0, -1);
247
+ const fromStep = from[from.length - 1];
248
+ if (target.length <= fromParent.length) return target;
249
+ for (let k = 0; k < fromParent.length; k++) {
250
+ if (
251
+ target[k].slot !== fromParent[k].slot ||
252
+ target[k].index !== fromParent[k].index
253
+ )
254
+ return target;
255
+ }
256
+ const divergence = target[fromParent.length];
257
+ if (divergence.slot === fromStep.slot && divergence.index > fromStep.index) {
258
+ const next = [...target];
259
+ next[fromParent.length] = { ...divergence, index: divergence.index - 1 };
260
+ return next;
261
+ }
262
+ return target;
263
+ }
264
+
265
+ /** Move the node at `from` to `target`: remove it, then insert it, adjusting the
266
+ * target for the index shift the removal causes in a shared sibling list. The
267
+ * root, a missing node, and a drop into the node's own subtree leave the
268
+ * content unchanged (the same object back). */
269
+ export function moveNode(
270
+ content: Record<string, EditorNode[]>,
271
+ from: Step[],
272
+ target: DropTarget
273
+ ): Record<string, EditorNode[]> {
274
+ if (from.length === 0) return content;
275
+ if (isAncestorOrSelf(from, target.parent)) return content;
276
+ const node = nodeAt(content, from);
277
+ if (!node) return content;
278
+ const fromParent = from.slice(0, -1);
279
+ const fromStep = from[from.length - 1];
280
+ const sameList =
281
+ pathKey(fromParent) === pathKey(target.parent) &&
282
+ fromStep.slot === target.slot;
283
+ const index =
284
+ sameList && fromStep.index < target.index ? target.index - 1 : target.index;
285
+ const removed = removeAt(content, from);
286
+ return insertAt(
287
+ removed,
288
+ adjustForRemoval(target.parent, from),
289
+ target.slot,
290
+ node,
291
+ index
292
+ );
293
+ }
294
+
295
+ /** A sortable list's address: the parent whose slot it lists, and the slot. */
296
+ export interface SortableListAddress {
297
+ parent: Step[];
298
+ slot: string;
299
+ }
300
+
301
+ /** Translate a sortable move (where the node ends up in its list) into the path
302
+ * it leaves and the drop target `moveNode` takes (the index it goes before,
303
+ * counted before the removal). */
304
+ export function resolveSortableMove(
305
+ source: SortableListAddress,
306
+ target: SortableListAddress,
307
+ sameList: boolean,
308
+ fromIndex: number,
309
+ toIndex: number
310
+ ): { from: Step[]; target: DropTarget } {
311
+ return {
312
+ from: childPath(source.parent, source.slot, fromIndex),
313
+ target: {
314
+ parent: target.parent,
315
+ slot: target.slot,
316
+ index: sameList && toIndex > fromIndex ? toIndex + 1 : toIndex,
317
+ },
318
+ };
319
+ }
320
+
321
+ // --- seeding ----------------------------------------------------------------
322
+
323
+ /** What the editor reads of a manifest component to seed a node from it. */
324
+ export interface SeedableComponent {
325
+ name: string;
326
+ defaultProps: Record<string, Json>;
327
+ content?: SlotPlan;
328
+ }
329
+
330
+ /** A component swapped in under an existing node: its default props, with a
331
+ * string `children` default as a default-slot text child (children is the
332
+ * default slot, not a prop). */
333
+ export function seedNode(component: SeedableComponent): EditorNode {
334
+ const props = { ...component.defaultProps };
335
+ const slots: Record<string, EditorNode[]> = {};
336
+ if (typeof props.children === 'string')
337
+ slots[DEFAULT_SLOT] = [{ kind: 'text', value: props.children }];
338
+ delete props.children;
339
+ return { kind: 'element', component: component.name, props, slots };
340
+ }
341
+
342
+ /** A component added through the add dialog. With `props` the node carries them
343
+ * plus the component's curated slot content (the default or a named scenario);
344
+ * without, it comes in empty - no props, no content. */
345
+ export function seedComponentNode(
346
+ component: SeedableComponent,
347
+ props?: Record<string, Json>
348
+ ): EditorNode {
349
+ if (!props)
350
+ return { kind: 'element', component: component.name, props: {}, slots: {} };
351
+ const nodeProps = { ...props };
352
+ const slots = seedSlots(component.content, nodeProps);
353
+ delete nodeProps.children;
354
+ return {
355
+ kind: 'element',
356
+ component: component.name,
357
+ props: nodeProps,
358
+ slots,
359
+ };
360
+ }
361
+
362
+ /** The root seeded from its story file content, so the tree starts from what the
363
+ * browse view renders, with `props` (a story's scenario-merged props) or the
364
+ * component's defaults. */
365
+ export function seedRoot(
366
+ component: SeedableComponent,
367
+ props?: Record<string, Json>
368
+ ): EditorState {
369
+ const rootProps = { ...(props ?? component.defaultProps) };
370
+ const slots = seedSlots(component.content, rootProps);
371
+ delete rootProps.children;
372
+ return { root: component.name, props: rootProps, slots };
373
+ }
374
+
375
+ /**
376
+ * A key for a tree's slot content, equal for two trees of the same content
377
+ * whatever their node ids or the order of their props' keys: what the
378
+ * regeneration payload would carry, serialized with sorted keys.
379
+ */
380
+ export function slotsKey(slots: Record<string, EditorNode[]>): string {
381
+ return stableStringify(
382
+ toPayload({ root: '', props: {}, slots }).slots as unknown
383
+ );
384
+ }
385
+
386
+ /** A value as JSON with every object's keys sorted, so two values equal by
387
+ * content serialize the same; `undefined` entries are left out. */
388
+ export function stableStringify(value: unknown): string {
389
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
390
+ if (typeof value === 'object' && value !== null) {
391
+ const entries = Object.entries(value)
392
+ .filter(([, entry]) => entry !== undefined)
393
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
394
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
395
+ }
396
+ return JSON.stringify(value) ?? 'null';
397
+ }
398
+
399
+ // --- serialization to the regeneration payload -------------------------------------
400
+
401
+ /**
402
+ * The props as JSON, each one left unset (`undefined`) left out. An empty
403
+ * string is a value like any other and stays, so a component given `''`
404
+ * keeps it on every update; a story unsets a prop with `null`.
405
+ *
406
+ * @param record - The props, from the controls or a pane's write-back.
407
+ * @returns The props that are set.
408
+ */
409
+ export function cleanRecord(
410
+ record: Record<string, Json>
411
+ ): Record<string, JsonValue> {
412
+ const out: Record<string, JsonValue> = {};
413
+ for (const [key, value] of Object.entries(record))
414
+ if (value !== undefined) out[key] = value as JsonValue;
415
+ return out;
416
+ }
417
+
418
+ function listToFill(nodes: EditorNode[]): SlotFill | undefined {
419
+ const fills = nodes.map(nodeToFill);
420
+ if (fills.length === 0) return undefined;
421
+ if (fills.length === 1) return fills[0];
422
+ return { kind: 'fragment', children: fills };
423
+ }
424
+
425
+ export function namedFills(
426
+ slots: Record<string, EditorNode[]>
427
+ ): Record<string, SlotFill> {
428
+ const out: Record<string, SlotFill> = {};
429
+ for (const [name, nodes] of Object.entries(slots)) {
430
+ if (name === DEFAULT_SLOT) continue;
431
+ const fill = listToFill(nodes);
432
+ if (fill) out[name] = fill;
433
+ }
434
+ return out;
435
+ }
436
+
437
+ function nodeToFill(node: EditorNode): SlotFill {
438
+ if (node.kind === 'text') return { kind: 'text', value: node.value };
439
+ if (node.kind === 'opaque') return node.fill;
440
+ const children = listToFill(node.slots[DEFAULT_SLOT] ?? []);
441
+ if (node.kind === 'html') {
442
+ return {
443
+ kind: 'html',
444
+ tag: node.tag,
445
+ props: cleanRecord(node.props),
446
+ ...(children ? { children } : {}),
447
+ };
448
+ }
449
+ const named = namedFills(node.slots);
450
+ return {
451
+ kind: 'element',
452
+ component: node.component,
453
+ props: cleanRecord(node.props),
454
+ ...(children ? { children } : {}),
455
+ ...(Object.keys(named).length ? { slots: named } : {}),
456
+ };
457
+ }
458
+
459
+ // --- seeding from story file content (SlotFill → EditorNode) --------------------
460
+
461
+ // Inverse of nodeToFill: turn a story file's SlotFill into the editor's node list so
462
+ // a per-story tree starts from the curated content the browse view renders. A
463
+ // `fragment` flattens into its siblings; a `raw` or `scoped` fill or a call to
464
+ // a part becomes one opaque node that keeps the fill as authored.
465
+ export function fillToNodes(fill: SlotFill | undefined): EditorNode[] {
466
+ if (!fill) return [];
467
+ if (fill.kind === 'text') return [{ kind: 'text', value: fill.value }];
468
+ if (fill.kind === 'fragment') return fill.children.flatMap(fillToNodes);
469
+ if (fill.kind === 'raw' || fill.kind === 'scoped' || fill.kind === 'part')
470
+ return [{ kind: 'opaque', fill }];
471
+ const slots: Record<string, EditorNode[]> = {};
472
+ const def = fillToNodes(fill.children);
473
+ if (def.length) slots[DEFAULT_SLOT] = def;
474
+ if (fill.kind === 'html') {
475
+ return [
476
+ { kind: 'html', tag: fill.tag, props: { ...(fill.props ?? {}) }, slots },
477
+ ];
478
+ }
479
+ for (const [name, named] of Object.entries(fill.slots ?? {})) {
480
+ const nodes = fillToNodes(named);
481
+ if (nodes.length) slots[name] = nodes;
482
+ }
483
+ return [
484
+ {
485
+ kind: 'element',
486
+ component: fill.component,
487
+ props: { ...(fill.props ?? {}) },
488
+ slots,
489
+ },
490
+ ];
491
+ }
492
+
493
+ /** Seed a root component's slot content from its story file SlotPlan, falling back to
494
+ * a default-slot text child when a string `children` prop is the only content. */
495
+ export function seedSlots(
496
+ content: SlotPlan | undefined,
497
+ props: Record<string, Json>
498
+ ): Record<string, EditorNode[]> {
499
+ if (content) {
500
+ const slots: Record<string, EditorNode[]> = {};
501
+ const def = fillToNodes(content.default);
502
+ if (def.length) slots[DEFAULT_SLOT] = def;
503
+ for (const [name, fill] of Object.entries(content.named ?? {})) {
504
+ const nodes = fillToNodes(fill);
505
+ if (nodes.length) slots[name] = nodes;
506
+ }
507
+ return slots;
508
+ }
509
+ if (typeof props.children === 'string')
510
+ return { [DEFAULT_SLOT]: [{ kind: 'text', value: props.children }] };
511
+ return {};
512
+ }
513
+
514
+ /** The regeneration payload for a root component, its props and its slot
515
+ * content. */
516
+ export function toPayload(state: EditorState) {
517
+ const def = listToFill(state.slots[DEFAULT_SLOT] ?? []);
518
+ const named = namedFills(state.slots);
519
+ const hasContent = def || Object.keys(named).length > 0;
520
+ return {
521
+ rootComponent: state.root,
522
+ props: cleanRecord(state.props),
523
+ slots: hasContent
524
+ ? {
525
+ ...(def ? { default: def } : {}),
526
+ ...(Object.keys(named).length ? { named } : {}),
527
+ }
528
+ : undefined,
529
+ };
530
+ }
@@ -0,0 +1,114 @@
1
+ // Where the controller gets a story's html source: the markup the story
2
+ // renders to, which needs the kit's templates and so cannot be printed in the
3
+ // browser. The development server prints it for any story on request; the
4
+ // static build prints it once per scenario and serves one file per
5
+ // component. A controller with neither reports html as unavailable.
6
+ // Browser-safe.
7
+
8
+ import type { JsonValue, SlotPlan, StoryAround } from '../lib/types.js';
9
+
10
+ /** A story to print as html: the root component, its props and its slot content. */
11
+ export interface HtmlSourceRequest {
12
+ /** The root component's name. */
13
+ rootComponent: string;
14
+ /** The props to render it with. */
15
+ props: Record<string, JsonValue>;
16
+ /** Its slot content: the default slot and the named slots. */
17
+ slots?: SlotPlan;
18
+ /** The content beside the root, as its siblings: `before` and `after` it. */
19
+ around?: StoryAround;
20
+ }
21
+
22
+ /**
23
+ * A story's html: its markup, with the styles the kit's build wrote for the
24
+ * components it renders when there are any and the warnings for what the
25
+ * markup leaves out when there are any, or what keeps it from having markup.
26
+ */
27
+ export type HtmlSourceResponse =
28
+ | { markup: string; stylesheet?: string; warnings?: string[] }
29
+ | { missing: string[] };
30
+
31
+ /**
32
+ * Prints a story's html. It resolves with the markup or what the story holds
33
+ * that has no markup, and rejects with the reason it could not print; the
34
+ * controller aborts `signal` when the story changes before it answers.
35
+ */
36
+ export type HtmlSourceTransport = (
37
+ request: HtmlSourceRequest,
38
+ signal: AbortSignal
39
+ ) => Promise<HtmlSourceResponse>;
40
+
41
+ /**
42
+ * Reads the html printed ahead of time for a component's scenarios, by
43
+ * scenario id (`''` for the anonymous default). It rejects when there is
44
+ * none to read.
45
+ */
46
+ export type ScenarioHtmlSourceLoader = (
47
+ componentId: string
48
+ ) => Promise<Record<string, HtmlSourceResponse>>;
49
+
50
+ /** The path the preview's development server answers html source requests on. */
51
+ export const HTML_SOURCE_ENDPOINT = '/__preview_html_source';
52
+
53
+ /** The directory, under the URL the preview is served from, that the static
54
+ * build keeps each component's scenario html in, one `<component id>.json`
55
+ * each. */
56
+ export const HTML_SOURCE_DIRECTORY = 'html-source';
57
+
58
+ /**
59
+ * A transport that POSTs the story as JSON to the development server's html
60
+ * source endpoint and reads the JSON it answers. A response that is not ok
61
+ * rejects with the response's text, or its status when the text is empty.
62
+ *
63
+ * @param endpoint - The endpoint's path; the development server's by default.
64
+ * @param fetchImplementation - The `fetch` to use; the global one by default.
65
+ * @returns The transport.
66
+ */
67
+ export function createFetchHtmlSourceTransport(
68
+ endpoint: string = HTML_SOURCE_ENDPOINT,
69
+ fetchImplementation: typeof fetch = (...parameters) => fetch(...parameters)
70
+ ): HtmlSourceTransport {
71
+ return async (request, signal) => {
72
+ const response = await fetchImplementation(endpoint, {
73
+ method: 'POST',
74
+ body: JSON.stringify(request),
75
+ signal,
76
+ });
77
+ if (!response.ok) {
78
+ const text = await response.text().catch(() => '');
79
+ throw new Error(text || `HTTP ${response.status}`);
80
+ }
81
+ return (await response.json()) as HtmlSourceResponse;
82
+ };
83
+ }
84
+
85
+ /**
86
+ * A loader that fetches a component's scenario html from the files the
87
+ * static build serves, `<baseUrl>html-source/<component id>.json`, once per
88
+ * component.
89
+ *
90
+ * @param baseUrl - The URL the preview is served under, ending in `/`.
91
+ * @param fetchImplementation - The `fetch` to use; the global one by default.
92
+ * @returns The loader.
93
+ */
94
+ export function createStaticHtmlSourceLoader(
95
+ baseUrl: string,
96
+ fetchImplementation: typeof fetch = (...parameters) => fetch(...parameters)
97
+ ): ScenarioHtmlSourceLoader {
98
+ const loaded = new Map<string, Promise<Record<string, HtmlSourceResponse>>>();
99
+ return (componentId) => {
100
+ let pending = loaded.get(componentId);
101
+ if (!pending) {
102
+ pending = fetchImplementation(
103
+ `${baseUrl}${HTML_SOURCE_DIRECTORY}/${encodeURIComponent(componentId)}.json`
104
+ ).then(async (response) => {
105
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
106
+ return (await response.json()) as Record<string, HtmlSourceResponse>;
107
+ });
108
+ // A failed read is tried again the next time.
109
+ pending.catch(() => loaded.delete(componentId));
110
+ loaded.set(componentId, pending);
111
+ }
112
+ return pending;
113
+ };
114
+ }