@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,819 @@
1
+ // The messages the preview host and its iframes exchange over postMessage, the
2
+ // flag an iframe sets once it can receive them, and the URLs the iframes load.
3
+ // The host, the five iframe entries and the testing helper all speak through
4
+ // this module, so the vocabulary lives in one place. Browser-safe - no node
5
+ // imports; type-only imports keep it free of runtime dependencies.
6
+ /**
7
+ * What a story pane renders in: a scripted target, which a component preview
8
+ * iframe runs the story's script in, or `html`, the markup the story renders
9
+ * to with no JavaScript of its own.
10
+ */
11
+ export const PREVIEW_TARGETS = ['react', 'vue', 'svelte', 'html'];
12
+ /** The scripted targets, in the preview's order: {@link PREVIEW_TARGETS}
13
+ * without `html`. */
14
+ export const PREVIEW_SCRIPTED_TARGETS = [
15
+ 'react',
16
+ 'vue',
17
+ 'svelte',
18
+ ];
19
+ /** The `type` of each message, keyed by a short name. */
20
+ export const PREVIEW_MESSAGE = {
21
+ /** Iframe → host: the iframe listens for messages now (sent on each load). */
22
+ ready: 'preview:ready',
23
+ /** Host → iframe: mount a story by id with its root props. */
24
+ setStory: 'preview:set-story',
25
+ /** Host → iframe: replace the mounted story's props in place. */
26
+ props: 'preview:props',
27
+ /** Host → iframe: the merged option values to apply to the document. */
28
+ globals: 'preview:globals',
29
+ /** Host → view iframe: the catalog a custom view builds its context from. */
30
+ catalog: 'preview:catalog',
31
+ /** View iframe → host: open a component story, as a navigation click does. */
32
+ select: 'preview:select',
33
+ /** Iframe → host: the size of what the iframe shows, sent once it renders
34
+ * and again on each change. */
35
+ contentSize: 'preview:content-size',
36
+ /** Host → html iframe: the markup to show, or why there is none. */
37
+ markup: 'preview:markup',
38
+ /** Iframe → host: the story threw as it rendered, or, with no message,
39
+ * the story it mounts since renders. */
40
+ renderError: 'preview:render-error',
41
+ /** Host → code iframe: the source the kit's code block shows. */
42
+ code: 'preview:code',
43
+ /** Iframe → host: the story called one of its events. */
44
+ event: 'preview:event',
45
+ /** Iframe → host: its first content has mounted and painted, so the host
46
+ * can show the iframe. Sent once per document. */
47
+ shown: 'preview:shown',
48
+ };
49
+ const MESSAGE_TYPES = new Set(Object.values(PREVIEW_MESSAGE));
50
+ /**
51
+ * Read posted data as a preview message: the data itself when it is an object
52
+ * whose `type` is one of the preview's, otherwise `undefined`. Only the `type`
53
+ * is checked; a receiver validates the payload it relies on.
54
+ *
55
+ * @param data - A `MessageEvent`'s `data`.
56
+ * @returns The data as a {@link PreviewMessage}, or `undefined`.
57
+ */
58
+ export function readPreviewMessage(data) {
59
+ if (!data || typeof data !== 'object')
60
+ return undefined;
61
+ const type = data.type;
62
+ return typeof type === 'string' && MESSAGE_TYPES.has(type)
63
+ ? data
64
+ : undefined;
65
+ }
66
+ /**
67
+ * Whether posted data is a preview message of one type.
68
+ *
69
+ * @param data - A `MessageEvent`'s `data`.
70
+ * @param type - The message type to match.
71
+ * @returns `true` when `data` is an object whose `type` is `type`.
72
+ */
73
+ export function isPreviewMessage(data, type) {
74
+ return readPreviewMessage(data)?.type === type;
75
+ }
76
+ /** @returns A {@link PreviewReadyMessage}. */
77
+ export function readyMessage() {
78
+ return { type: PREVIEW_MESSAGE.ready };
79
+ }
80
+ /**
81
+ * @param id - The story's id.
82
+ * @param props - Its root props.
83
+ * @param events - The function props a pane records the calls of.
84
+ * @param bindings - The two-way props and the events they report through.
85
+ * @param mount - Which of the host's story mounts this is.
86
+ * @returns A {@link PreviewSetStoryMessage}.
87
+ */
88
+ export function setStoryMessage(id, props, events, bindings, mount) {
89
+ return {
90
+ type: PREVIEW_MESSAGE.setStory,
91
+ id,
92
+ props,
93
+ ...(mount !== undefined ? { mount } : {}),
94
+ ...(events && events.length > 0 ? { events } : {}),
95
+ ...(bindings && bindings.length > 0
96
+ ? {
97
+ bindings: bindings.map(({ prop, changeProp }) => ({
98
+ prop,
99
+ changeProp,
100
+ })),
101
+ }
102
+ : {}),
103
+ };
104
+ }
105
+ /**
106
+ * The story's props with each two-way prop's change event writing the value
107
+ * it reports back as that prop at once, through `write`, while the story
108
+ * gives the prop: what the host does with the value after it hears of the
109
+ * call, done inside the pane, so a framework that restores a controlled
110
+ * input to its prop's value as the event ends (React) finds the new value
111
+ * there and keeps the caret where it was. The host's own write-back follows
112
+ * with the same value. Only plain data is written, as the host takes only
113
+ * plain data; a prop the story leaves out stays the component's own.
114
+ *
115
+ * @param props - The story's root props, its events' recorders in place.
116
+ * @param bindings - The two-way props and the events they report through.
117
+ * @param write - Takes a two-way prop's new value.
118
+ * @returns The props, each given two-way prop's change event writing back.
119
+ * @example
120
+ * ```ts
121
+ * withWriteBacks(props, [{ prop: 'value', changeProp: 'onValueChange' }], (prop, value) =>
122
+ * setProps((current) => ({ ...current, [prop]: value }))
123
+ * );
124
+ * ```
125
+ */
126
+ export function withWriteBacks(props, bindings = [], write) {
127
+ // The given two-way props by the change prop they report through: one
128
+ // change prop may report several, each written back.
129
+ const byChangeProp = new Map();
130
+ for (const { prop, changeProp } of bindings) {
131
+ if (props[prop] === undefined || changeProp === prop)
132
+ continue;
133
+ byChangeProp.set(changeProp, [
134
+ ...(byChangeProp.get(changeProp) ?? []),
135
+ prop,
136
+ ]);
137
+ }
138
+ if (byChangeProp.size === 0)
139
+ return props;
140
+ const next = { ...props };
141
+ for (const [changeProp, written] of byChangeProp) {
142
+ const own = props[changeProp];
143
+ next[changeProp] = (...parameters) => {
144
+ const copy = plainValue(parameters[0]);
145
+ if ('value' in copy)
146
+ for (const prop of written)
147
+ write(prop, copy.value);
148
+ return typeof own === 'function' ? own(...parameters) : undefined;
149
+ };
150
+ }
151
+ return next;
152
+ }
153
+ /**
154
+ * The values a pane has written back itself (see {@link withWriteBacks}) and
155
+ * the host has not yet answered, per two-way prop, oldest first. Each write
156
+ * gets a number, counting up for the life of the pane, which the next event
157
+ * the pane reports carries (`write`); each props message from the host
158
+ * names the newest write it had taken when it sent them (`answers`). Props
159
+ * the host sent before it took a write hold an older value for that prop,
160
+ * whether they answer the write's own echo or a change of another control,
161
+ * so `reconcile` keeps the pane's newest value over them.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * const pending = new PendingWriteBacks();
166
+ * pending.wrote('value', 'ab'); // 1
167
+ * pending.wrote('value', 'abc'); // 2
168
+ * pending.reconcile({ value: 'ab' }, 1); // { value: 'abc' }, still waiting for 2
169
+ * pending.reconcile({ value: 'abc' }, 2); // { value: 'abc' }, nothing waiting
170
+ * ```
171
+ */
172
+ export class PendingWriteBacks {
173
+ pending = new Map();
174
+ count = 0;
175
+ unreported;
176
+ /**
177
+ * Note a value the pane has written back for a prop.
178
+ *
179
+ * @param prop - The two-way prop.
180
+ * @param value - The value, plain data.
181
+ * @returns The write's number.
182
+ */
183
+ wrote(prop, value) {
184
+ this.count += 1;
185
+ const values = this.pending.get(prop) ?? [];
186
+ values.push({ write: this.count, value });
187
+ this.pending.set(prop, values);
188
+ this.unreported = this.count;
189
+ return this.count;
190
+ }
191
+ /**
192
+ * The number of the newest write since the last call, for the event the
193
+ * pane reports next to carry; `undefined` when there has been none.
194
+ *
195
+ * @returns The write's number, or `undefined`.
196
+ */
197
+ takeUnreported() {
198
+ const write = this.unreported;
199
+ this.unreported = undefined;
200
+ return write;
201
+ }
202
+ /**
203
+ * The host's props as the pane takes them. The writes up to `answers` are
204
+ * done with: the host had them when it sent the props. A two-way prop the
205
+ * host gives with a later write still waiting keeps the newest value the
206
+ * pane wrote. Without `answers` the props are taken as they are, and
207
+ * every write is done with.
208
+ *
209
+ * @param props - The props the host sends.
210
+ * @param answers - The newest of the pane's writes the host had taken.
211
+ * @returns The props to take.
212
+ */
213
+ reconcile(props, answers) {
214
+ if (answers === undefined) {
215
+ this.pending.clear();
216
+ return props;
217
+ }
218
+ let taken = props;
219
+ for (const [prop, values] of this.pending) {
220
+ const waiting = values.filter((written) => written.write > answers);
221
+ if (waiting.length === 0) {
222
+ this.pending.delete(prop);
223
+ continue;
224
+ }
225
+ this.pending.set(prop, waiting);
226
+ // A prop the host leaves out is no longer given: the pane writes it
227
+ // back no more, and the component keeps its own value.
228
+ if (props[prop] === undefined)
229
+ continue;
230
+ taken = { ...taken, [prop]: waiting[waiting.length - 1].value };
231
+ }
232
+ return taken;
233
+ }
234
+ /** Forget every write, as a new story starts. The count goes on. */
235
+ clear() {
236
+ this.pending.clear();
237
+ this.unreported = undefined;
238
+ }
239
+ }
240
+ /**
241
+ * The story's props with a recorder for each of its events: a function that
242
+ * reports its call to the host as a `preview:event`, the event's name and a
243
+ * readable summary of what it was called with. A function the props hold
244
+ * already for an event is called too.
245
+ *
246
+ * @param props - The story's root props.
247
+ * @param events - The function props to record the calls of.
248
+ * @param target - The pane's window.
249
+ * @param mount - The `mount` of the set-story message the pane shows, which
250
+ * each report carries.
251
+ * @param writes - The pane's own write-backs: a report carries the number of
252
+ * the newest write since the last one (`write`), for the host to answer.
253
+ * @returns The props, an event's recorder in its place.
254
+ */
255
+ export function withEventRecorders(props, events = [], target = window, mount, writes) {
256
+ if (events.length === 0)
257
+ return props;
258
+ const recorded = { ...props };
259
+ for (const name of events) {
260
+ const own = props[name];
261
+ recorded[name] = (...parameters) => {
262
+ const write = writes?.takeUnreported();
263
+ const report = {
264
+ type: PREVIEW_MESSAGE.event,
265
+ name,
266
+ payload: summarizeArguments(parameters),
267
+ ...plainValue(parameters[0]),
268
+ ...(mount !== undefined ? { mount } : {}),
269
+ ...(write !== undefined ? { write } : {}),
270
+ };
271
+ target.parent?.postMessage(report, '*');
272
+ return typeof own === 'function' ? own(...parameters) : undefined;
273
+ };
274
+ }
275
+ return recorded;
276
+ }
277
+ /** An event's first argument as `{ value }`, a copy, when it is plain data:
278
+ * `null`, a boolean, a finite number, a string, or an array or plain object
279
+ * of these. Anything else, such as a browser event or an element, gives
280
+ * nothing. */
281
+ function plainValue(value) {
282
+ return isPlainData(value, 0)
283
+ ? { value: JSON.parse(JSON.stringify(value)) }
284
+ : {};
285
+ }
286
+ /**
287
+ * Whether a value is plain data, which survives `postMessage` and JSON as it
288
+ * is: `null`, a boolean, a finite number, a string, or an array or plain
289
+ * object of these.
290
+ *
291
+ * @param value - The value.
292
+ * @param depth - How deep the value sits in the one being read.
293
+ * @returns `true` when the value is plain data.
294
+ */
295
+ export function isPlainData(value, depth = 0) {
296
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
297
+ return true;
298
+ if (typeof value === 'number')
299
+ return Number.isFinite(value);
300
+ if (typeof value !== 'object' || depth > 32)
301
+ return false;
302
+ if (Array.isArray(value))
303
+ return value.every((entry) => isPlainData(entry, depth + 1));
304
+ const prototype = Object.getPrototypeOf(value);
305
+ if (prototype !== Object.prototype && prototype !== null)
306
+ return false;
307
+ return Object.values(value).every((entry) => isPlainData(entry, depth + 1));
308
+ }
309
+ /** A readable, one-line summary of what an event was called with. */
310
+ export function summarizeArguments(parameters) {
311
+ return parameters.map(summarizeValue).join(', ');
312
+ }
313
+ function summarizeValue(value) {
314
+ if (value === undefined)
315
+ return 'undefined';
316
+ if (typeof value === 'function')
317
+ return 'function';
318
+ if (typeof Event !== 'undefined' && value instanceof Event)
319
+ return `${value.constructor.name} ${value.type}`;
320
+ // React hands a handler its own wrapper of the browser's event, which
321
+ // holds the event itself: summarized alike, it reads as Vue's and
322
+ // Svelte's do.
323
+ if (typeof Event !== 'undefined' &&
324
+ typeof value === 'object' &&
325
+ value !== null &&
326
+ value.nativeEvent instanceof Event)
327
+ return summarizeValue(value.nativeEvent);
328
+ if (typeof Element !== 'undefined' && value instanceof Element)
329
+ return `<${value.tagName.toLowerCase()}>`;
330
+ try {
331
+ const text = JSON.stringify(value);
332
+ return text.length > 200 ? `${text.slice(0, 197)}...` : text;
333
+ }
334
+ catch {
335
+ // An object JSON cannot hold, such as one that refers to itself, by
336
+ // the name of what made it.
337
+ return typeof value === 'object' && value !== null
338
+ ? (value.constructor?.name ?? 'Object')
339
+ : String(value);
340
+ }
341
+ }
342
+ /**
343
+ * @param props - The mounted story's new root props.
344
+ * @param answers - The newest of the receiving pane's write-backs the host
345
+ * has taken.
346
+ * @returns A {@link PreviewPropsMessage}.
347
+ */
348
+ export function propsMessage(props, answers) {
349
+ return {
350
+ type: PREVIEW_MESSAGE.props,
351
+ props,
352
+ ...(answers !== undefined ? { answers } : {}),
353
+ };
354
+ }
355
+ /**
356
+ * @param options - The option values, keyed by option id.
357
+ * @returns A {@link PreviewGlobalsMessage}.
358
+ */
359
+ export function globalsMessage(options) {
360
+ return { type: PREVIEW_MESSAGE.globals, options };
361
+ }
362
+ /**
363
+ * @param catalog - The kit's name, sources and browseable stories.
364
+ * @returns A {@link PreviewCatalogMessage}.
365
+ */
366
+ export function catalogMessage(catalog) {
367
+ return { type: PREVIEW_MESSAGE.catalog, ...catalog };
368
+ }
369
+ /**
370
+ * @param id - The component story to open.
371
+ * @param props - Root props to open it with, over the component's defaults.
372
+ * @returns A {@link PreviewSelectMessage}.
373
+ */
374
+ export function selectMessage(id, props) {
375
+ return { type: PREVIEW_MESSAGE.select, id, props };
376
+ }
377
+ /**
378
+ * @param content - The target's source, its language, or why it has none.
379
+ * @returns A {@link PreviewCodeMessage}.
380
+ */
381
+ export function codeMessage(content) {
382
+ return { type: PREVIEW_MESSAGE.code, ...content };
383
+ }
384
+ /**
385
+ * Tell the host what the iframe's story threw as it rendered, or, with no
386
+ * message, that the story mounted since renders.
387
+ *
388
+ * @param message - The error's message; omitted when a new story mounts.
389
+ * @param target - The iframe's window.
390
+ */
391
+ export function reportRenderError(message, target = window) {
392
+ const report = {
393
+ type: PREVIEW_MESSAGE.renderError,
394
+ };
395
+ if (message !== undefined)
396
+ report.message = message;
397
+ target[PREVIEW_RENDER_ERROR_FLAG] =
398
+ message ?? null;
399
+ target.parent?.postMessage(report, '*');
400
+ }
401
+ /**
402
+ * @param size - The content's width and height in CSS pixels.
403
+ * @returns A {@link PreviewContentSizeMessage}.
404
+ */
405
+ export function contentSizeMessage(size) {
406
+ return {
407
+ type: PREVIEW_MESSAGE.contentSize,
408
+ width: size.width,
409
+ height: size.height,
410
+ };
411
+ }
412
+ /**
413
+ * @param story - The markup to show, the reason there is none, or neither
414
+ * while it is being printed.
415
+ * @returns A {@link PreviewMarkupMessage}.
416
+ */
417
+ export function markupMessage(story) {
418
+ return { type: PREVIEW_MESSAGE.markup, ...story };
419
+ }
420
+ /**
421
+ * Read posted data as a content size: its width and height when both are
422
+ * finite numbers of zero or more, otherwise `undefined`.
423
+ *
424
+ * @param message - A {@link PreviewContentSizeMessage} as it was received.
425
+ * @returns The size, or `undefined` for a malformed message.
426
+ */
427
+ export function readContentSize(message) {
428
+ const { width, height } = message;
429
+ const valid = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0;
430
+ return valid(width) && valid(height) ? { width, height } : undefined;
431
+ }
432
+ /** The window property an iframe sets to `true` once it can receive messages,
433
+ * for a test that loads the iframe's page on its own (where the ready message
434
+ * has no parent to reach). */
435
+ export const PREVIEW_READY_FLAG = '__previewReady';
436
+ /** The window property a story iframe sets to the ids of the stories it can
437
+ * mount, for a test helper to reject an id that would mount nothing. */
438
+ export const PREVIEW_STORIES_FLAG = '__previewStories';
439
+ /** The window property a preview iframe sets to the kit's options, each with
440
+ * the values a choice option offers, for a test helper to reject an id or a
441
+ * value the iframe would ignore. */
442
+ export const PREVIEW_OPTIONS_FLAG = '__previewOptions';
443
+ /** The window property a story iframe sets to the message of what its story
444
+ * threw as it rendered, or to `null` while the story mounted since renders,
445
+ * for a test that loads the iframe's page on its own (where the report has no
446
+ * parent to reach). The message is a string, and may be empty. */
447
+ export const PREVIEW_RENDER_ERROR_FLAG = '__previewRenderError';
448
+ /** The window property a story iframe sets to the id of the story it last
449
+ * finished mounting, or to the empty string once a story has unmounted with
450
+ * none set after it. Set after the story's first render and its effects, so
451
+ * a test that loads the iframe's page on its own can tell a story that
452
+ * rendered nothing from one still mounting. */
453
+ export const PREVIEW_MOUNTED_STORY_FLAG = '__previewMountedStory';
454
+ /** The id of the element each preview iframe mounts its story or view in. */
455
+ export const PREVIEW_MOUNT_ELEMENT_ID = 'app';
456
+ /**
457
+ * Announce that an iframe shows its first content: post a
458
+ * {@link PreviewShownMessage} to the parent window once the content mounted
459
+ * now has painted, two animation frames on. Call it after the iframe mounts
460
+ * the first content the host sends; a later call in the same document sends
461
+ * nothing.
462
+ *
463
+ * @param target - The iframe's window.
464
+ */
465
+ export function announcePreviewShown(target = window) {
466
+ const flags = target;
467
+ if (flags[PREVIEW_SHOWN_FLAG])
468
+ return;
469
+ flags[PREVIEW_SHOWN_FLAG] = true;
470
+ target.requestAnimationFrame(() => target.requestAnimationFrame(() => {
471
+ if (target.parent !== target)
472
+ target.parent.postMessage({ type: PREVIEW_MESSAGE.shown }, '*');
473
+ }));
474
+ }
475
+ /**
476
+ * Record under {@link PREVIEW_MOUNTED_STORY_FLAG} that the iframe has finished
477
+ * mounting a story. Call it once the framework has committed the mount,
478
+ * whatever the story rendered.
479
+ *
480
+ * @param id - The story's id, or the empty string after an unmount.
481
+ * @param target - The iframe's window.
482
+ */
483
+ export function announceStoryMounted(id, target = window) {
484
+ target[PREVIEW_MOUNTED_STORY_FLAG] =
485
+ id;
486
+ }
487
+ /** Set on an iframe's window once it has announced its first content. */
488
+ const PREVIEW_SHOWN_FLAG = '__previewShown';
489
+ /**
490
+ * List the stories an iframe can mount under {@link PREVIEW_STORIES_FLAG}.
491
+ * Call it before {@link announcePreviewReady}, so a test that waits for the
492
+ * iframe finds the list.
493
+ *
494
+ * @param ids - The ids of the stories.
495
+ * @param target - The iframe's window.
496
+ */
497
+ export function announcePreviewStories(ids, target = window) {
498
+ target[PREVIEW_STORIES_FLAG] = [
499
+ ...ids,
500
+ ];
501
+ }
502
+ /**
503
+ * List the kit's options under {@link PREVIEW_OPTIONS_FLAG}.
504
+ *
505
+ * @param options - The options.
506
+ * @param target - The iframe's window.
507
+ */
508
+ export function announcePreviewOptions(options, target = window) {
509
+ target[PREVIEW_OPTIONS_FLAG] =
510
+ options.map(({ id, values }) => values ? { id, values: values.map((choice) => choice.value) } : { id });
511
+ }
512
+ /**
513
+ * Announce that an iframe listens for messages: set {@link PREVIEW_READY_FLAG}
514
+ * and post a {@link PreviewReadyMessage} to the parent window. Call it right
515
+ * after attaching the message listener.
516
+ *
517
+ * @param target - The iframe's window.
518
+ */
519
+ export function announcePreviewReady(target = window) {
520
+ target[PREVIEW_READY_FLAG] = true;
521
+ // A pane opened on its own, with no host, gives itself the story its
522
+ // address names, as a host would.
523
+ const standalone = target.parent === target ? standaloneMessages(target.location.search) : [];
524
+ if (standalone.length > 0) {
525
+ for (const message of standalone)
526
+ target.postMessage(message, '*');
527
+ return;
528
+ }
529
+ target.parent?.postMessage(readyMessage(), '*');
530
+ }
531
+ /** The pixel value of a computed length such as `12px`; `0` for anything else. */
532
+ function pixels(value) {
533
+ const number = parseFloat(value);
534
+ return Number.isFinite(number) ? number : 0;
535
+ }
536
+ /** Whether the mount's height may follow the layout around it rather than its
537
+ * content: it holds a minimum height of its own, or it is a flex or grid
538
+ * item, which the container may stretch. */
539
+ function heightFollowsLayout(mount, view) {
540
+ if (pixels(view.getComputedStyle(mount).minHeight) > 0)
541
+ return true;
542
+ const parent = mount.parentElement;
543
+ if (!parent)
544
+ return false;
545
+ const display = view.getComputedStyle(parent).display;
546
+ return /flex|grid/.test(display);
547
+ }
548
+ /** The height the mount's children span, each with its vertical margins:
549
+ * fixed-position children overlay the page and are left out. */
550
+ function childrenHeight(mount, view) {
551
+ let top = Infinity;
552
+ let bottom = -Infinity;
553
+ const range = mount.ownerDocument.createRange();
554
+ for (const node of mount.childNodes) {
555
+ if (node.nodeType === 1) {
556
+ const element = node;
557
+ const style = view.getComputedStyle(element);
558
+ if (style.display === 'none' || style.position === 'fixed')
559
+ continue;
560
+ const rectangle = element.getBoundingClientRect();
561
+ top = Math.min(top, rectangle.top - pixels(style.marginTop));
562
+ bottom = Math.max(bottom, rectangle.bottom + pixels(style.marginBottom));
563
+ }
564
+ else if (node.nodeType === 3 && node.textContent?.trim()) {
565
+ range.selectNodeContents(node);
566
+ const rectangle = range.getBoundingClientRect();
567
+ top = Math.min(top, rectangle.top);
568
+ bottom = Math.max(bottom, rectangle.bottom);
569
+ }
570
+ }
571
+ return bottom > top ? bottom - top : 0;
572
+ }
573
+ /**
574
+ * Measure what an iframe shows: the mount element's own box, with the
575
+ * padding, borders and margins of the mount and every element around it up
576
+ * to the body, rounded up to whole CSS pixels. Nothing in it depends on the
577
+ * height of the iframe, so a host that sizes the iframe to it does not make
578
+ * it grow: a wrapper's `min-height: 100vh` adds nothing but its padding.
579
+ * Where the mount's own height may follow the layout around it (a minimum
580
+ * height of its own, or a flex or grid item), its children's extent stands
581
+ * in for it.
582
+ *
583
+ * @param mount - The element the story or view is mounted in.
584
+ * @returns The content's width and height.
585
+ */
586
+ export function measureContentSize(mount) {
587
+ const view = mount.ownerDocument.defaultView;
588
+ if (!view)
589
+ return { width: 0, height: 0 };
590
+ const box = mount.getBoundingClientRect();
591
+ const mountStyle = view.getComputedStyle(mount);
592
+ let width = box.width;
593
+ let height = heightFollowsLayout(mount, view)
594
+ ? childrenHeight(mount, view) +
595
+ pixels(mountStyle.paddingTop) +
596
+ pixels(mountStyle.paddingBottom) +
597
+ pixels(mountStyle.borderTopWidth) +
598
+ pixels(mountStyle.borderBottomWidth)
599
+ : box.height;
600
+ const root = mount.ownerDocument.documentElement;
601
+ for (let element = mount; element && element !== root; element = element.parentElement) {
602
+ const style = view.getComputedStyle(element);
603
+ height += pixels(style.marginTop) + pixels(style.marginBottom);
604
+ width += pixels(style.marginLeft) + pixels(style.marginRight);
605
+ if (element === mount)
606
+ continue;
607
+ height +=
608
+ pixels(style.paddingTop) +
609
+ pixels(style.paddingBottom) +
610
+ pixels(style.borderTopWidth) +
611
+ pixels(style.borderBottomWidth);
612
+ width +=
613
+ pixels(style.paddingLeft) +
614
+ pixels(style.paddingRight) +
615
+ pixels(style.borderLeftWidth) +
616
+ pixels(style.borderRightWidth);
617
+ }
618
+ return { width: Math.ceil(width), height: Math.ceil(height) };
619
+ }
620
+ const reporting = new WeakMap();
621
+ /**
622
+ * Report the size of what an iframe shows to the parent window as a
623
+ * {@link PreviewContentSizeMessage}: once now, and again whenever it
624
+ * changes, at most once per animation frame and only when the size differs
625
+ * from the last one reported. It watches the mount, its children and the
626
+ * elements around it for a change of size, the mount's content for a change
627
+ * of nodes or text, the elements around it for a change of class or style,
628
+ * and the window for a resize. Call it once the message listener is
629
+ * attached; a second call for the same mount returns the first one's stop.
630
+ *
631
+ * @param mount - The element the story or view is mounted in.
632
+ * @param target - The iframe's window.
633
+ * @returns A function that stops the reports.
634
+ */
635
+ export function reportContentSize(mount, target = window) {
636
+ const running = reporting.get(mount);
637
+ if (running)
638
+ return running;
639
+ const root = mount.ownerDocument.documentElement;
640
+ let pending;
641
+ let last = '';
642
+ function report() {
643
+ pending = undefined;
644
+ const size = measureContentSize(mount);
645
+ const key = `${size.width}x${size.height}`;
646
+ if (key === last)
647
+ return;
648
+ last = key;
649
+ target.parent?.postMessage(contentSizeMessage(size), '*');
650
+ }
651
+ function schedule() {
652
+ if (pending === undefined)
653
+ pending = target.requestAnimationFrame(report);
654
+ }
655
+ const observed = new Set();
656
+ const resizeObserver = typeof ResizeObserver === 'function'
657
+ ? new ResizeObserver(schedule)
658
+ : undefined;
659
+ const surroundings = new MutationObserver(schedule);
660
+ function observeElements() {
661
+ const elements = [...mount.children];
662
+ for (let element = mount; element && element !== root; element = element.parentElement) {
663
+ elements.push(element);
664
+ }
665
+ for (const element of elements) {
666
+ if (observed.has(element))
667
+ continue;
668
+ observed.add(element);
669
+ resizeObserver?.observe(element, { box: 'border-box' });
670
+ }
671
+ for (const element of [...observed]) {
672
+ if (elements.includes(element))
673
+ continue;
674
+ observed.delete(element);
675
+ resizeObserver?.unobserve(element);
676
+ }
677
+ }
678
+ const content = new MutationObserver(() => {
679
+ observeElements();
680
+ schedule();
681
+ });
682
+ content.observe(mount, {
683
+ childList: true,
684
+ subtree: true,
685
+ characterData: true,
686
+ });
687
+ for (let element = mount; element; element = element.parentElement) {
688
+ surroundings.observe(element, {
689
+ attributes: true,
690
+ attributeFilter: ['class', 'style'],
691
+ });
692
+ }
693
+ observeElements();
694
+ target.addEventListener('resize', schedule);
695
+ schedule();
696
+ const stop = () => {
697
+ if (pending !== undefined)
698
+ target.cancelAnimationFrame(pending);
699
+ pending = undefined;
700
+ resizeObserver?.disconnect();
701
+ content.disconnect();
702
+ surroundings.disconnect();
703
+ target.removeEventListener('resize', schedule);
704
+ reporting.delete(mount);
705
+ };
706
+ reporting.set(mount, stop);
707
+ return stop;
708
+ }
709
+ /** The query parameter that names the view the view iframe renders. */
710
+ export const VIEW_PARAMETER = 'view';
711
+ /** The query parameter that names the framework of the view the view iframe
712
+ * renders, so the iframe loads that framework's setup module before the view. */
713
+ export const VIEW_FRAMEWORK_PARAMETER = 'framework';
714
+ /** The query parameter that names the target whose source a code iframe
715
+ * shows. */
716
+ export const CODE_TARGET_PARAMETER = 'target';
717
+ /** The query parameter a component iframe's URL carries so that changing it
718
+ * reloads the iframe. */
719
+ export const RELOAD_PARAMETER = 'r';
720
+ /**
721
+ * The URL of a story pane's iframe: a target's, or the html one's.
722
+ *
723
+ * @param target - What the iframe renders in.
724
+ * @param reload - A counter whose change reloads the iframe; omitted, the URL
725
+ * carries no reload parameter.
726
+ * @param baseUrl - The URL the preview is served under, ending in `/`; the
727
+ * root of the origin, `/`, by default.
728
+ * @returns The page's URL, such as `/preview/vue/preview.html?r=0`.
729
+ */
730
+ export function componentFrameUrl(target, reload, baseUrl = '/') {
731
+ const path = `${baseUrl}preview/${target}/preview.html`;
732
+ return reload === undefined ? path : `${path}?${RELOAD_PARAMETER}=${reload}`;
733
+ }
734
+ /** The query parameters a target's pane, opened on its own, reads its
735
+ * story from: the story's id, its root props and the option values, the
736
+ * latter two as JSON. */
737
+ export const STANDALONE_PARAMETERS = {
738
+ story: 'story',
739
+ props: 'props',
740
+ options: 'options',
741
+ };
742
+ /**
743
+ * The URL of a target's pane opened on its own, outside the preview: it
744
+ * shows `story` with its props under its option values, as the preview's pane
745
+ * does, with no host to follow.
746
+ *
747
+ * @param target - The target to render the story in.
748
+ * @param story - The story's id, root props and option values.
749
+ * @param baseUrl - The URL the preview is served under, ending in `/`.
750
+ * @returns The pane's URL.
751
+ */
752
+ export function standaloneFrameUrl(target, story, baseUrl = '/') {
753
+ const query = new URLSearchParams({
754
+ [STANDALONE_PARAMETERS.story]: story.id,
755
+ [STANDALONE_PARAMETERS.props]: JSON.stringify(story.props),
756
+ [STANDALONE_PARAMETERS.options]: JSON.stringify(story.options),
757
+ });
758
+ return `${componentFrameUrl(target, undefined, baseUrl)}?${query}`;
759
+ }
760
+ /**
761
+ * The messages a pane opened on its own gives itself, from its address: the
762
+ * story to mount and the option values. None when the address names no story.
763
+ *
764
+ * @param search - The pane's `location.search`.
765
+ * @returns A set-story and a globals message, or none.
766
+ */
767
+ export function standaloneMessages(search) {
768
+ const query = new URLSearchParams(search);
769
+ const id = query.get(STANDALONE_PARAMETERS.story);
770
+ if (!id)
771
+ return [];
772
+ const parsed = (name) => {
773
+ try {
774
+ const value = JSON.parse(query.get(name) ?? '{}');
775
+ return value && typeof value === 'object' && !Array.isArray(value)
776
+ ? value
777
+ : {};
778
+ }
779
+ catch {
780
+ return {};
781
+ }
782
+ };
783
+ return [
784
+ setStoryMessage(id, parsed(STANDALONE_PARAMETERS.props)),
785
+ globalsMessage(parsed(STANDALONE_PARAMETERS.options)),
786
+ ];
787
+ }
788
+ /**
789
+ * The URL of a code iframe: the kit's code block, showing one target's
790
+ * source.
791
+ *
792
+ * @param target - The target whose source the block shows.
793
+ * @param framework - The block's framework; a framework other than `'dom'`
794
+ * is named in the URL, so the iframe loads its setup module first.
795
+ * @param baseUrl - The URL the preview is served under, ending in `/`.
796
+ * @returns The page's URL, such as `/preview/code/code.html?target=vue`.
797
+ */
798
+ export function codeFrameUrl(target, framework = 'dom', baseUrl = '/') {
799
+ const path = `${baseUrl}preview/code/code.html?${CODE_TARGET_PARAMETER}=${target}`;
800
+ return framework === 'dom'
801
+ ? path
802
+ : `${path}&${VIEW_FRAMEWORK_PARAMETER}=${framework}`;
803
+ }
804
+ /**
805
+ * The URL of the view iframe rendering one custom view.
806
+ *
807
+ * @param viewId - The view's id.
808
+ * @param framework - The view's framework; a framework other than `'dom'` is
809
+ * named in the URL, so the iframe loads its setup module ahead of the views.
810
+ * @param baseUrl - The URL the preview is served under, ending in `/`; the
811
+ * root of the origin, `/`, by default.
812
+ * @returns The page's URL, such as `/preview/view/view.html?view=tokens`.
813
+ */
814
+ export function viewFrameUrl(viewId, framework = 'dom', baseUrl = '/') {
815
+ const path = `${baseUrl}preview/view/view.html?${VIEW_PARAMETER}=${viewId}`;
816
+ return framework === 'dom'
817
+ ? path
818
+ : `${path}&${VIEW_FRAMEWORK_PARAMETER}=${framework}`;
819
+ }