@scaffold-ui-kit/preview 0.0.0-stage → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1868 -2
  3. package/application/host/bootstrap.ts +43 -0
  4. package/application/host/main.ts +31 -0
  5. package/application/host/shell-host.ts +47 -0
  6. package/application/index.html +12 -0
  7. package/application/lib/base-url.ts +15 -0
  8. package/application/lib/no-code-block.ts +7 -0
  9. package/application/lib/no-setup.ts +3 -0
  10. package/application/lib/no-views.ts +6 -0
  11. package/application/preview/code/code.html +12 -0
  12. package/application/preview/code/main.ts +89 -0
  13. package/application/preview/globals.ts +254 -0
  14. package/application/preview/html/main.ts +93 -0
  15. package/application/preview/html/preview.html +12 -0
  16. package/application/preview/react/main.tsx +146 -0
  17. package/application/preview/react/preview.html +12 -0
  18. package/application/preview/svelte/Preview.svelte +104 -0
  19. package/application/preview/svelte/main.ts +13 -0
  20. package/application/preview/svelte/preview.html +12 -0
  21. package/application/preview/svelte/story-props.ts +172 -0
  22. package/application/preview/view/main.ts +124 -0
  23. package/application/preview/view/view.html +12 -0
  24. package/application/preview/vue/main.ts +145 -0
  25. package/application/preview/vue/preview.html +12 -0
  26. package/application/shells/charcell/components/Application/index.css +37 -0
  27. package/application/shells/charcell/components/Application/index.tsx +65 -0
  28. package/application/shells/charcell/components/Bar/index.css +68 -0
  29. package/application/shells/charcell/components/Bar/index.tsx +60 -0
  30. package/application/shells/charcell/components/CodePane/index.css +60 -0
  31. package/application/shells/charcell/components/CodePane/index.tsx +113 -0
  32. package/application/shells/charcell/components/ComponentStage/index.css +26 -0
  33. package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
  34. package/application/shells/charcell/components/Control/index.css +20 -0
  35. package/application/shells/charcell/components/Control/index.tsx +118 -0
  36. package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
  37. package/application/shells/charcell/components/Description/index.css +6 -0
  38. package/application/shells/charcell/components/Description/index.tsx +24 -0
  39. package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
  40. package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
  41. package/application/shells/charcell/components/EditorPanel/index.css +48 -0
  42. package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
  43. package/application/shells/charcell/components/EventsRow/index.css +42 -0
  44. package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
  45. package/application/shells/charcell/components/HeadingRow/index.css +11 -0
  46. package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
  47. package/application/shells/charcell/components/Hint/index.css +16 -0
  48. package/application/shells/charcell/components/Hint/index.tsx +81 -0
  49. package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
  50. package/application/shells/charcell/components/KeyHints/index.css +27 -0
  51. package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
  52. package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
  53. package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
  54. package/application/shells/charcell/components/Markdown/index.tsx +16 -0
  55. package/application/shells/charcell/components/Navigation/index.css +13 -0
  56. package/application/shells/charcell/components/Navigation/index.tsx +98 -0
  57. package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
  58. package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
  59. package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
  60. package/application/shells/charcell/components/PaneStatus/index.css +7 -0
  61. package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
  62. package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
  63. package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
  64. package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
  65. package/application/shells/charcell/components/PreviewPane/index.css +127 -0
  66. package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
  67. package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
  68. package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
  69. package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
  70. package/application/shells/charcell/components/Select/index.tsx +48 -0
  71. package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
  72. package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
  73. package/application/shells/charcell/components/StageBar/index.css +7 -0
  74. package/application/shells/charcell/components/StageBar/index.tsx +109 -0
  75. package/application/shells/charcell/components/StageSplit/index.css +11 -0
  76. package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
  77. package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
  78. package/application/shells/charcell/components/ViewControls/index.css +15 -0
  79. package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
  80. package/application/shells/charcell/components/ViewHeading/index.css +13 -0
  81. package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
  82. package/application/shells/charcell/components/ViewStage/index.css +9 -0
  83. package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
  84. package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
  85. package/application/shells/charcell/configuration.ts +45 -0
  86. package/application/shells/charcell/controller-context.ts +27 -0
  87. package/application/shells/charcell/fit-labels.ts +42 -0
  88. package/application/shells/charcell/html-warnings.ts +18 -0
  89. package/application/shells/charcell/index.tsx +82 -0
  90. package/application/shells/charcell/page-loading.html +57 -0
  91. package/application/shells/charcell/placement.ts +24 -0
  92. package/application/shells/charcell/shell-options.ts +23 -0
  93. package/application/shells/charcell/shell.css +37 -0
  94. package/application/shells/charcell/shell.tsx +25 -0
  95. package/application/shells/charcell/split-reset.ts +47 -0
  96. package/application/shells/charcell/stacked-rows.ts +117 -0
  97. package/application/shells/charcell/stage-toggles.ts +93 -0
  98. package/application/types/manifest.d.ts +6 -0
  99. package/application/types/preview-configuration.d.ts +10 -0
  100. package/application/types/preview-shell.d.ts +7 -0
  101. package/application/types/react-registry.d.ts +15 -0
  102. package/application/types/vue-registry.d.ts +6 -0
  103. package/dist/build.d.ts +24 -0
  104. package/dist/build.js +34 -0
  105. package/dist/check.d.ts +48 -0
  106. package/dist/check.js +781 -0
  107. package/dist/cli.d.ts +2 -0
  108. package/dist/cli.js +88 -0
  109. package/dist/context.d.ts +179 -0
  110. package/dist/context.js +783 -0
  111. package/dist/controller/catalog.d.ts +339 -0
  112. package/dist/controller/catalog.js +367 -0
  113. package/dist/controller/create-preview-controller.d.ts +532 -0
  114. package/dist/controller/create-preview-controller.js +1707 -0
  115. package/dist/controller/editor-tree.d.ts +161 -0
  116. package/dist/controller/editor-tree.js +373 -0
  117. package/dist/controller/html-source.d.ts +62 -0
  118. package/dist/controller/html-source.js +61 -0
  119. package/dist/controller/index.d.ts +9 -0
  120. package/dist/controller/index.js +12 -0
  121. package/dist/controller/persistence.d.ts +191 -0
  122. package/dist/controller/persistence.js +315 -0
  123. package/dist/controller/regeneration.d.ts +30 -0
  124. package/dist/controller/regeneration.js +27 -0
  125. package/dist/controller/source.d.ts +80 -0
  126. package/dist/controller/source.js +236 -0
  127. package/dist/controller/store.d.ts +27 -0
  128. package/dist/controller/store.js +40 -0
  129. package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
  130. package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
  131. package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
  132. package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
  133. package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
  134. package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
  135. package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
  136. package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
  137. package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
  138. package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
  139. package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
  140. package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
  141. package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
  142. package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
  143. package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
  144. package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
  145. package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
  146. package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
  147. package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
  148. package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
  149. package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
  150. package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
  151. package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
  152. package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
  153. package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
  154. package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
  155. package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
  156. package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
  157. package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
  158. package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
  159. package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
  160. package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
  161. package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
  162. package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
  163. package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
  164. package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
  165. package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
  166. package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
  167. package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
  168. package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
  169. package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
  170. package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
  171. package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
  172. package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
  173. package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
  174. package/dist/declarations/src/controller/catalog.d.ts +339 -0
  175. package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
  176. package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
  177. package/dist/declarations/src/controller/html-source.d.ts +62 -0
  178. package/dist/declarations/src/controller/index.d.ts +9 -0
  179. package/dist/declarations/src/controller/persistence.d.ts +191 -0
  180. package/dist/declarations/src/controller/regeneration.d.ts +30 -0
  181. package/dist/declarations/src/controller/source.d.ts +80 -0
  182. package/dist/declarations/src/controller/store.d.ts +27 -0
  183. package/dist/declarations/src/emit/common.d.ts +103 -0
  184. package/dist/declarations/src/emit/manifest.d.ts +138 -0
  185. package/dist/declarations/src/emit/source.d.ts +87 -0
  186. package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
  187. package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
  188. package/dist/declarations/src/lib/naming.d.ts +29 -0
  189. package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
  190. package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
  191. package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
  192. package/dist/declarations/src/lib/types.d.ts +741 -0
  193. package/dist/declarations/src/options.d.ts +224 -0
  194. package/dist/declarations/src/protocol.d.ts +541 -0
  195. package/dist/declarations/src/react-view.d.ts +22 -0
  196. package/dist/declarations/src/react.d.ts +17 -0
  197. package/dist/declarations/src/shell.d.ts +99 -0
  198. package/dist/declarations/src/views.d.ts +152 -0
  199. package/dist/development-server.d.ts +121 -0
  200. package/dist/development-server.js +331 -0
  201. package/dist/emit/common.d.ts +103 -0
  202. package/dist/emit/common.js +170 -0
  203. package/dist/emit/html-source.d.ts +46 -0
  204. package/dist/emit/html-source.js +322 -0
  205. package/dist/emit/manifest.d.ts +138 -0
  206. package/dist/emit/manifest.js +74 -0
  207. package/dist/emit/react.d.ts +16 -0
  208. package/dist/emit/react.js +210 -0
  209. package/dist/emit/scratch.d.ts +40 -0
  210. package/dist/emit/scratch.js +116 -0
  211. package/dist/emit/source.d.ts +87 -0
  212. package/dist/emit/source.js +789 -0
  213. package/dist/emit/svelte.d.ts +9 -0
  214. package/dist/emit/svelte.js +177 -0
  215. package/dist/emit/vue.d.ts +2 -0
  216. package/dist/emit/vue.js +152 -0
  217. package/dist/enumerate.d.ts +2 -0
  218. package/dist/enumerate.js +162 -0
  219. package/dist/generate.d.ts +43 -0
  220. package/dist/generate.js +302 -0
  221. package/dist/html-stylesheet.d.ts +43 -0
  222. package/dist/html-stylesheet.js +145 -0
  223. package/dist/index.d.ts +18 -0
  224. package/dist/index.js +21 -0
  225. package/dist/landing.d.ts +16 -0
  226. package/dist/landing.js +45 -0
  227. package/dist/lib/exported-types.d.ts +42 -0
  228. package/dist/lib/exported-types.js +86 -0
  229. package/dist/lib/html-attributes.d.ts +2 -0
  230. package/dist/lib/html-attributes.js +111 -0
  231. package/dist/lib/html-warnings.d.ts +19 -0
  232. package/dist/lib/html-warnings.js +79 -0
  233. package/dist/lib/jsx-roots.d.ts +22 -0
  234. package/dist/lib/jsx-roots.js +243 -0
  235. package/dist/lib/module-of-source.d.ts +18 -0
  236. package/dist/lib/module-of-source.js +24 -0
  237. package/dist/lib/naming.d.ts +29 -0
  238. package/dist/lib/naming.js +56 -0
  239. package/dist/lib/parse-type.d.ts +32 -0
  240. package/dist/lib/parse-type.js +103 -0
  241. package/dist/lib/react-spelling.d.ts +68 -0
  242. package/dist/lib/react-spelling.js +197 -0
  243. package/dist/lib/scenario-story.d.ts +41 -0
  244. package/dist/lib/scenario-story.js +84 -0
  245. package/dist/lib/slot-fill.d.ts +15 -0
  246. package/dist/lib/slot-fill.js +92 -0
  247. package/dist/lib/template-files.d.ts +14 -0
  248. package/dist/lib/template-files.js +25 -0
  249. package/dist/lib/types.d.ts +741 -0
  250. package/dist/lib/types.js +13 -0
  251. package/dist/lib/void-elements.d.ts +7 -0
  252. package/dist/lib/void-elements.js +13 -0
  253. package/dist/load.d.ts +37 -0
  254. package/dist/load.js +82 -0
  255. package/dist/model.d.ts +44 -0
  256. package/dist/model.js +464 -0
  257. package/dist/options.d.ts +224 -0
  258. package/dist/options.js +223 -0
  259. package/dist/prop-types.d.ts +26 -0
  260. package/dist/prop-types.js +102 -0
  261. package/dist/props-from.d.ts +84 -0
  262. package/dist/props-from.js +510 -0
  263. package/dist/protocol.d.ts +541 -0
  264. package/dist/protocol.js +819 -0
  265. package/dist/react-view.d.ts +22 -0
  266. package/dist/react-view.js +36 -0
  267. package/dist/react.d.ts +17 -0
  268. package/dist/react.js +23 -0
  269. package/dist/sass-importers.d.ts +26 -0
  270. package/dist/sass-importers.js +32 -0
  271. package/dist/shell-acceptance.d.ts +21 -0
  272. package/dist/shell-acceptance.js +37 -0
  273. package/dist/shell-page.d.ts +36 -0
  274. package/dist/shell-page.js +90 -0
  275. package/dist/shell-peers.d.ts +38 -0
  276. package/dist/shell-peers.js +61 -0
  277. package/dist/shell.d.ts +99 -0
  278. package/dist/shell.js +52 -0
  279. package/dist/shells/charcell/page.d.ts +8 -0
  280. package/dist/shells/charcell/page.js +69 -0
  281. package/dist/story-source.d.ts +113 -0
  282. package/dist/story-source.js +271 -0
  283. package/dist/story.d.ts +5 -0
  284. package/dist/story.js +28 -0
  285. package/dist/testing.d.ts +218 -0
  286. package/dist/testing.js +284 -0
  287. package/dist/views.d.ts +152 -0
  288. package/dist/views.js +7 -0
  289. package/dist/vite-configuration.d.ts +114 -0
  290. package/dist/vite-configuration.js +578 -0
  291. package/package.json +123 -3
  292. package/src/build.ts +51 -0
  293. package/src/check.ts +1062 -0
  294. package/src/cli.ts +123 -0
  295. package/src/context.ts +1168 -0
  296. package/src/controller/catalog.ts +719 -0
  297. package/src/controller/create-preview-controller.ts +2683 -0
  298. package/src/controller/editor-tree.ts +530 -0
  299. package/src/controller/html-source.ts +114 -0
  300. package/src/controller/index.ts +105 -0
  301. package/src/controller/persistence.ts +403 -0
  302. package/src/controller/regeneration.ts +55 -0
  303. package/src/controller/source.ts +333 -0
  304. package/src/controller/store.ts +55 -0
  305. package/src/development-server.ts +442 -0
  306. package/src/emit/common.ts +201 -0
  307. package/src/emit/html-source.ts +402 -0
  308. package/src/emit/manifest.ts +241 -0
  309. package/src/emit/react.ts +263 -0
  310. package/src/emit/scratch.ts +175 -0
  311. package/src/emit/source.ts +1094 -0
  312. package/src/emit/svelte.ts +230 -0
  313. package/src/emit/vue.ts +194 -0
  314. package/src/enumerate.ts +220 -0
  315. package/src/generate.ts +443 -0
  316. package/src/html-stylesheet.ts +173 -0
  317. package/src/index.ts +60 -0
  318. package/src/landing.ts +58 -0
  319. package/src/lib/exported-types.ts +114 -0
  320. package/src/lib/html-attributes.ts +113 -0
  321. package/src/lib/html-warnings.ts +81 -0
  322. package/src/lib/jsx-roots.ts +238 -0
  323. package/src/lib/module-of-source.ts +26 -0
  324. package/src/lib/naming.ts +65 -0
  325. package/src/lib/parse-type.ts +143 -0
  326. package/src/lib/react-spelling.ts +235 -0
  327. package/src/lib/scenario-story.ts +97 -0
  328. package/src/lib/slot-fill.ts +102 -0
  329. package/src/lib/template-files.ts +31 -0
  330. package/src/lib/types.ts +778 -0
  331. package/src/lib/void-elements.ts +15 -0
  332. package/src/load.ts +144 -0
  333. package/src/model.ts +568 -0
  334. package/src/options.ts +435 -0
  335. package/src/prop-types.ts +133 -0
  336. package/src/props-from.ts +678 -0
  337. package/src/protocol.ts +1135 -0
  338. package/src/react-view.ts +45 -0
  339. package/src/react.ts +34 -0
  340. package/src/sass-importers.ts +45 -0
  341. package/src/shell-acceptance.ts +45 -0
  342. package/src/shell-page.ts +133 -0
  343. package/src/shell-peers.ts +70 -0
  344. package/src/shell.ts +194 -0
  345. package/src/shells/charcell/page.ts +82 -0
  346. package/src/story-source.ts +391 -0
  347. package/src/story.ts +42 -0
  348. package/src/testing.ts +497 -0
  349. package/src/views.ts +183 -0
  350. package/src/vite-configuration.ts +675 -0
package/src/landing.ts ADDED
@@ -0,0 +1,58 @@
1
+ // The entry a kit names for the preview to open on (`preview.landing`),
2
+ // checked against the kit's stories: the generator and the check both refuse
3
+ // a story or scenario the kit has none of. A view's id is the views module's,
4
+ // which only the browser loads, so the preview's controller checks that one.
5
+
6
+ import { sanitize } from './lib/naming.js';
7
+ import type {
8
+ PreviewLandingConfiguration,
9
+ ResolvedStory,
10
+ } from './lib/types.js';
11
+
12
+ /** The id of the synthetic scenario that renders a story with no content. */
13
+ const EMPTY_SCENARIO_ID = 'empty';
14
+
15
+ /**
16
+ * What is wrong with the landing entry a kit names, against its stories:
17
+ * a component with no story, or a scenario the story does not have. A
18
+ * view's id is not checked here. Empty when the entry is sound or unset.
19
+ *
20
+ * @param landing - The kit's `preview.landing`.
21
+ * @param stories - The kit's browseable stories: omitted components and those
22
+ * that are not standalone left out.
23
+ * @returns One message per problem.
24
+ * @example
25
+ * ```ts
26
+ * landingProblems({ component: 'components-button', scenario: 'primary' }, stories);
27
+ * ```
28
+ */
29
+ export function landingProblems(
30
+ landing: PreviewLandingConfiguration | undefined,
31
+ stories: readonly ResolvedStory[]
32
+ ): string[] {
33
+ if (!landing || !('component' in landing)) return [];
34
+ const story = stories.find(
35
+ (candidate) => sanitize(candidate.title) === landing.component
36
+ );
37
+ if (!story)
38
+ return [
39
+ `preview.landing names component '${landing.component}', which has no story; a story's id is the one the preview's address carries as component=`,
40
+ ];
41
+ const scenario = landing.scenario;
42
+ if (scenario === undefined) return [];
43
+ const hasContent =
44
+ story.contentSlots.default || story.contentSlots.named.length > 0;
45
+ const scenarios = [
46
+ ...(hasContent ? [EMPTY_SCENARIO_ID] : []),
47
+ ...story.scenarios.map((candidate) => candidate.id),
48
+ ];
49
+ return scenarios.includes(scenario)
50
+ ? []
51
+ : [
52
+ `preview.landing names scenario '${scenario}' of '${landing.component}', which has ${
53
+ scenarios.length > 0
54
+ ? `the scenarios ${scenarios.join(', ')}`
55
+ : 'no scenarios'
56
+ }`,
57
+ ];
58
+ }
@@ -0,0 +1,114 @@
1
+ // The type names a component's generated module exports, which the kit's
2
+ // package entry re-exports with everything else the module exports: the
3
+ // exported declarations among a template's module-scope lines, as each
4
+ // target places them. Browser-safe.
5
+
6
+ import type { Target } from './types.js';
7
+
8
+ const IDENTIFIER = '[A-Za-z_$][\\w$]*';
9
+
10
+ /** A declaration that names a type: `export interface X`, `export type X =`,
11
+ * `export enum X`, `export class X` (each with `declare` or `abstract`). */
12
+ const TYPE_DECLARATION = new RegExp(
13
+ `^\\s*export\\s+(?:declare\\s+)?(?:(?:abstract\\s+)?class|interface|type|(?:const\\s+)?enum)\\s+(${IDENTIFIER})`
14
+ );
15
+
16
+ /** An export list: `export { A, B as C }` or `export type { A } from '...'`. */
17
+ const EXPORT_LIST = /^\s*export\s+(type\s+)?\{([^}]*)\}/;
18
+
19
+ /**
20
+ * The type names one module-scope line exports. A value-only export list
21
+ * (`export { a }`) names no type unless a member is marked `type`, as a value
22
+ * cannot be told from a type there.
23
+ *
24
+ * @param line - A verbatim module-scope line.
25
+ * @returns The type names it exports, in order.
26
+ */
27
+ export function exportedTypeNamesOf(line: string): string[] {
28
+ const declaration = TYPE_DECLARATION.exec(line);
29
+ if (declaration) return [declaration[1]];
30
+ const list = EXPORT_LIST.exec(line);
31
+ if (!list) return [];
32
+ const typeOnly = list[1] !== undefined;
33
+ return list[2]
34
+ .split(',')
35
+ .map((member) => member.trim())
36
+ .filter((member) => member !== '' && (typeOnly || /^type\s/.test(member)))
37
+ .map((member) =>
38
+ member
39
+ .replace(/^type\s+/, '')
40
+ .split(/\s+as\s+/)
41
+ .pop()!
42
+ .trim()
43
+ )
44
+ .filter((name) => new RegExp(`^${IDENTIFIER}$`).test(name));
45
+ }
46
+
47
+ /** The parts of a template the exported types are read from. */
48
+ export interface ExportingTemplate {
49
+ imports?: string[];
50
+ extensions?: Record<string, unknown>;
51
+ }
52
+
53
+ /**
54
+ * The type names a template's generated module exports in one scripted
55
+ * target: those its module-scope lines declare, the target's own `imports`
56
+ * merged in (or in their place, with `importsStrategy: 'replace'`).
57
+ *
58
+ * @param template - The component's template.
59
+ * @param target - The scripted target.
60
+ * @returns The exported type names, each once.
61
+ * @example
62
+ * ```ts
63
+ * exportedTypeNames({ imports: ['export interface DateValue { day: string }'] }, 'react');
64
+ * // ['DateValue']
65
+ * ```
66
+ */
67
+ export function exportedTypeNames(
68
+ template: ExportingTemplate,
69
+ target: Target
70
+ ): string[] {
71
+ const override = template.extensions?.[target] as
72
+ | { imports?: unknown; importsStrategy?: unknown }
73
+ | undefined;
74
+ const own = Array.isArray(override?.imports)
75
+ ? override.imports.filter(
76
+ (line): line is string => typeof line === 'string'
77
+ )
78
+ : undefined;
79
+ const lines =
80
+ own === undefined
81
+ ? (template.imports ?? [])
82
+ : override?.importsStrategy === 'replace'
83
+ ? own
84
+ : [...(template.imports ?? []), ...own];
85
+ return [
86
+ ...new Set(
87
+ lines.flatMap((entry) => entry.split('\n').flatMap(exportedTypeNamesOf))
88
+ ),
89
+ ];
90
+ }
91
+
92
+ /**
93
+ * The type names a component's generated module exports, per scripted
94
+ * target: none for a component written by hand, whose module the preview
95
+ * does not read.
96
+ *
97
+ * @param component - The component, with the template it was read from.
98
+ * @param targets - The scripted targets the kit builds.
99
+ * @returns The names per target that exports any, or `undefined` when none does.
100
+ */
101
+ export function exportedTypesOf(
102
+ component: { template?: unknown },
103
+ targets: readonly Target[]
104
+ ): Partial<Record<Target, string[]>> | undefined {
105
+ const template = component.template;
106
+ if (!template || typeof template !== 'object' || Array.isArray(template))
107
+ return undefined;
108
+ const types: Partial<Record<Target, string[]>> = {};
109
+ for (const target of targets) {
110
+ const names = exportedTypeNames(template as ExportingTemplate, target);
111
+ if (names.length > 0) types[target] = names;
112
+ }
113
+ return Object.keys(types).length > 0 ? types : undefined;
114
+ }
@@ -0,0 +1,113 @@
1
+ // Native attribute names a passthrough component forwards to its root element,
2
+ // so a story file may set them beside the declared props. The global attributes
3
+ // plus those of the elements a passthrough root usually is (form controls,
4
+ // links, media, tables); `aria-*` and `data-*` are matched by prefix. Spelled
5
+ // as in HTML, the form a story file's value reaches every target in.
6
+
7
+ const HTML_ATTRIBUTES = new Set([
8
+ // global
9
+ 'accesskey',
10
+ 'autocapitalize',
11
+ 'autofocus',
12
+ 'class',
13
+ 'contenteditable',
14
+ 'dir',
15
+ 'draggable',
16
+ 'enterkeyhint',
17
+ 'hidden',
18
+ 'id',
19
+ 'inert',
20
+ 'inputmode',
21
+ 'is',
22
+ 'lang',
23
+ 'nonce',
24
+ 'part',
25
+ 'popover',
26
+ 'role',
27
+ 'slot',
28
+ 'spellcheck',
29
+ 'style',
30
+ 'tabindex',
31
+ 'title',
32
+ 'translate',
33
+ // forms
34
+ 'accept',
35
+ 'action',
36
+ 'autocomplete',
37
+ 'checked',
38
+ 'cols',
39
+ 'dirname',
40
+ 'disabled',
41
+ 'enctype',
42
+ 'for',
43
+ 'form',
44
+ 'formaction',
45
+ 'formenctype',
46
+ 'formmethod',
47
+ 'formnovalidate',
48
+ 'formtarget',
49
+ 'list',
50
+ 'max',
51
+ 'maxlength',
52
+ 'method',
53
+ 'min',
54
+ 'minlength',
55
+ 'multiple',
56
+ 'name',
57
+ 'novalidate',
58
+ 'pattern',
59
+ 'placeholder',
60
+ 'readonly',
61
+ 'required',
62
+ 'rows',
63
+ 'selected',
64
+ 'size',
65
+ 'step',
66
+ 'type',
67
+ 'value',
68
+ 'wrap',
69
+ 'popovertarget',
70
+ 'popovertargetaction',
71
+ 'label',
72
+ 'open',
73
+ // links and media
74
+ 'alt',
75
+ 'download',
76
+ 'height',
77
+ 'href',
78
+ 'hreflang',
79
+ 'loading',
80
+ 'media',
81
+ 'ping',
82
+ 'referrerpolicy',
83
+ 'rel',
84
+ 'sizes',
85
+ 'src',
86
+ 'srcset',
87
+ 'target',
88
+ 'width',
89
+ 'decoding',
90
+ 'crossorigin',
91
+ 'poster',
92
+ 'controls',
93
+ 'autoplay',
94
+ 'loop',
95
+ 'muted',
96
+ 'playsinline',
97
+ 'preload',
98
+ // tables and lists
99
+ 'colspan',
100
+ 'rowspan',
101
+ 'headers',
102
+ 'scope',
103
+ 'span',
104
+ 'reversed',
105
+ 'start',
106
+ 'datetime',
107
+ 'cite',
108
+ ]);
109
+
110
+ /** Whether `name` is a native HTML attribute a passthrough root forwards. */
111
+ export function isHtmlAttribute(name: string): boolean {
112
+ return HTML_ATTRIBUTES.has(name) || /^(aria|data)-[a-z0-9-]+$/.test(name);
113
+ }
@@ -0,0 +1,81 @@
1
+ // What a story's html could not evaluate at rest, summarized for reading: a
2
+ // name that only the component's script defines, such as a per-instance id
3
+ // or its state, fails every expression that reads it, so a component that
4
+ // reads it in several places would otherwise repeat one warning per
5
+ // expression and bury the warnings that differ.
6
+
7
+ /** A warning as the render words it: `Name: could not evaluate expression (reason)`. */
8
+ const UNEVALUATED = /^(?:(.+?): )?could not evaluate ([\s\S]+)$/;
9
+
10
+ /**
11
+ * Summarizes repeated html warnings: the warnings one component gives for
12
+ * the same reason, such as `baseId is not defined`, become one line counting
13
+ * the expressions, at the place of the first; a warning whose reason no other
14
+ * warning of its component shares stays as it is, as does any warning that
15
+ * is not an expression that could not be evaluated.
16
+ *
17
+ * @param warnings - The warnings, as the render gives them.
18
+ * @returns The warnings, each kind of repeated warning once.
19
+ * @example
20
+ * ```ts
21
+ * summarizeHtmlWarnings([
22
+ * "Tabs: could not evaluate baseId + '-list' (baseId is not defined)",
23
+ * "Tabs: could not evaluate baseId + '-panel' (baseId is not defined)",
24
+ * ]);
25
+ * // ['Tabs: could not evaluate 2 expressions (baseId is not defined)']
26
+ * ```
27
+ */
28
+ export function summarizeHtmlWarnings(warnings: readonly string[]): string[] {
29
+ const groups = new Map<
30
+ string,
31
+ { component?: string; reason: string; warnings: string[] }
32
+ >();
33
+ const entries: Array<string | { key: string }> = [];
34
+ for (const warning of warnings) {
35
+ const parsed = parseWarning(warning);
36
+ if (parsed === undefined) {
37
+ entries.push(warning);
38
+ continue;
39
+ }
40
+ const key = `${parsed.component ?? ''}\u0000${parsed.reason}`;
41
+ const group = groups.get(key);
42
+ if (group === undefined) {
43
+ groups.set(key, { ...parsed, warnings: [warning] });
44
+ entries.push({ key });
45
+ } else if (!group.warnings.includes(warning)) {
46
+ group.warnings.push(warning);
47
+ }
48
+ }
49
+ return entries.map((entry) => {
50
+ if (typeof entry === 'string') return entry;
51
+ const group = groups.get(entry.key)!;
52
+ if (group.warnings.length === 1) return group.warnings[0];
53
+ const prefix = group.component === undefined ? '' : `${group.component}: `;
54
+ return `${prefix}could not evaluate ${group.warnings.length} expressions (${group.reason})`;
55
+ });
56
+ }
57
+
58
+ /** A warning's component and the reason in its closing parentheses, which
59
+ * may hold parentheses of their own. */
60
+ function parseWarning(
61
+ warning: string
62
+ ): { component?: string; reason: string } | undefined {
63
+ const match = UNEVALUATED.exec(warning);
64
+ if (match === null || !warning.endsWith(')')) return undefined;
65
+ let depth = 0;
66
+ for (let index = warning.length - 1; index >= 0; index--) {
67
+ const character = warning[index];
68
+ if (character === ')') depth++;
69
+ else if (character === '(') {
70
+ depth--;
71
+ if (depth === 0) {
72
+ if (warning[index - 1] !== ' ') return undefined;
73
+ const reason = warning.slice(index + 1, -1);
74
+ return match[1] === undefined
75
+ ? { reason }
76
+ : { component: match[1], reason };
77
+ }
78
+ }
79
+ }
80
+ return undefined;
81
+ }
@@ -0,0 +1,238 @@
1
+ // A React body (a scoped fill's or a part's) is written where JSX takes one
2
+ // expression: an arrow's body, a `return (...)`. Content of two or more root
3
+ // nodes - two sibling elements, an element and text - is no expression there,
4
+ // and neither is text alone (`{name}` after `=>` reads as a block), so each
5
+ // goes in a fragment. Vue and Svelte bodies are markup, which takes them as
6
+ // they are. A plain scanner rather than a parser: the printed source also runs
7
+ // in the browser.
8
+
9
+ /** Words that stand before an operand, so a word after them is no text. */
10
+ const PREFIX_KEYWORDS = new Set(['typeof', 'void', 'new', 'await', 'delete']);
11
+
12
+ /** Words that stand between operands, so the words beside them are no text. */
13
+ const INFIX_KEYWORDS = new Set(['in', 'instanceof', 'as', 'satisfies']);
14
+
15
+ /**
16
+ * A React body as one JSX expression: JSX content that is not one already in
17
+ * a fragment, anything else as given.
18
+ *
19
+ * Content opening with a tag goes in a fragment when it has two or more root
20
+ * nodes. Content opening with anything an expression cannot open with - a
21
+ * `{name}` placeholder, punctuation - is text, and goes in one too. Content
22
+ * opening as an expression can (a word, a number, a bracket, a quote) is an
23
+ * expression (`row.name`, `open && <Badge />`) unless it reads as text: two
24
+ * words side by side (`4 messages`), or a word followed by a placeholder or a
25
+ * tag (`hello {name}`, `remove <Button />`). A body the scanner cannot read
26
+ * through is left as given.
27
+ *
28
+ * @param body - The React body, as the fill gives it.
29
+ * @returns The body, in `<>...</>` when it is not one JSX expression.
30
+ * @example
31
+ * singleJsxRoot('<Button label="keep" /><Button label="empty" />');
32
+ * // '<><Button label="keep" /><Button label="empty" /></>'
33
+ * singleJsxRoot('{name}');
34
+ * // '<>{name}</>'
35
+ */
36
+ export function singleJsxRoot(body: string): string {
37
+ const trimmed = body.trim();
38
+ if (trimmed === '') return body;
39
+ if (trimmed.startsWith('<')) {
40
+ const roots = countRoots(trimmed);
41
+ return roots !== undefined && roots > 1 ? `<>${trimmed}</>` : body;
42
+ }
43
+ const opensExpression = /^[A-Za-z0-9_$([!+\-~.'"`]/.test(trimmed);
44
+ return !opensExpression || readsAsText(trimmed) ? `<>${trimmed}</>` : body;
45
+ }
46
+
47
+ /** Whether content that opens as an expression can is text instead: two
48
+ * words side by side, or a word before a placeholder or a tag, outside
49
+ * brackets. */
50
+ function readsAsText(content: string): boolean {
51
+ let depth = 0;
52
+ // The word last read at the top level, while only whitespace follows it.
53
+ let previousWord: string | undefined;
54
+ let index = 0;
55
+ while (index < content.length) {
56
+ const character = content[index];
57
+ if (character === '"' || character === "'" || character === '`') {
58
+ const end = stringEnd(content, index);
59
+ // A quote that never closes is an apostrophe in text.
60
+ if (end === undefined) return true;
61
+ index = end;
62
+ previousWord = undefined;
63
+ continue;
64
+ }
65
+ if (depth === 0) {
66
+ const word = /^[A-Za-z0-9_$]+/.exec(content.slice(index))?.[0];
67
+ if (word) {
68
+ const spaced = index > 0 && /\s/.test(content[index - 1]);
69
+ if (
70
+ previousWord !== undefined &&
71
+ spaced &&
72
+ !PREFIX_KEYWORDS.has(previousWord) &&
73
+ !INFIX_KEYWORDS.has(previousWord) &&
74
+ !INFIX_KEYWORDS.has(word)
75
+ )
76
+ return true;
77
+ previousWord = word;
78
+ index += word.length;
79
+ continue;
80
+ }
81
+ if (/\s/.test(character)) {
82
+ index++;
83
+ continue;
84
+ }
85
+ if (previousWord !== undefined && character === '{') return true;
86
+ if (character === '<' && /[A-Za-z>]/.test(content[index + 1] ?? '')) {
87
+ const elementClose = elementEnd(content, index);
88
+ if (elementClose !== undefined) {
89
+ // A tag after a word is text beside an element; after an operator
90
+ // it is an operand, read past whole.
91
+ if (previousWord !== undefined) return true;
92
+ index = elementClose;
93
+ continue;
94
+ }
95
+ }
96
+ // A word joined to what follows by punctuation (`row.name`) is one
97
+ // expression, not text.
98
+ previousWord = undefined;
99
+ }
100
+ if (character === '(' || character === '[' || character === '{') depth++;
101
+ else if (character === ')' || character === ']' || character === '}')
102
+ depth--;
103
+ index++;
104
+ }
105
+ return false;
106
+ }
107
+
108
+ /** The root nodes of JSX content, or `undefined` when it does not read through. */
109
+ function countRoots(content: string): number | undefined {
110
+ let roots = 0;
111
+ let index = 0;
112
+ while (index < content.length) {
113
+ const character = content[index];
114
+ if (/\s/.test(character)) {
115
+ index++;
116
+ continue;
117
+ }
118
+ const end =
119
+ character === '<'
120
+ ? elementEnd(content, index)
121
+ : character === '{'
122
+ ? braceEnd(content, index)
123
+ : textEnd(content, index);
124
+ if (end === undefined) return undefined;
125
+ roots++;
126
+ index = end;
127
+ }
128
+ return roots;
129
+ }
130
+
131
+ /** The index past a run of text, at the next tag or expression. */
132
+ function textEnd(content: string, start: number): number {
133
+ let index = start;
134
+ while (
135
+ index < content.length &&
136
+ content[index] !== '<' &&
137
+ content[index] !== '{'
138
+ )
139
+ index++;
140
+ return index;
141
+ }
142
+
143
+ /** The index past the element (or fragment) that opens at `start`. */
144
+ function elementEnd(content: string, start: number): number | undefined {
145
+ let depth = 0;
146
+ let index = start;
147
+ while (index < content.length) {
148
+ const character = content[index];
149
+ if (character === '{') {
150
+ const end = braceEnd(content, index);
151
+ if (end === undefined) return undefined;
152
+ index = end;
153
+ continue;
154
+ }
155
+ if (character !== '<') {
156
+ index++;
157
+ continue;
158
+ }
159
+ const closing = content[index + 1] === '/';
160
+ const end = tagEnd(content, index);
161
+ if (end === undefined) return undefined;
162
+ const selfClosing = !closing && content[end - 2] === '/';
163
+ if (closing) depth--;
164
+ else if (!selfClosing) depth++;
165
+ index = end;
166
+ if (depth === 0) return index;
167
+ if (depth < 0) return undefined;
168
+ }
169
+ return undefined;
170
+ }
171
+
172
+ /** The index past a tag's `>`, its attribute strings and expressions skipped. */
173
+ function tagEnd(content: string, start: number): number | undefined {
174
+ let index = start + 1;
175
+ while (index < content.length) {
176
+ const character = content[index];
177
+ if (character === '>') return index + 1;
178
+ if (character === '"' || character === "'") {
179
+ const end = content.indexOf(character, index + 1);
180
+ if (end === -1) return undefined;
181
+ index = end + 1;
182
+ continue;
183
+ }
184
+ if (character === '{') {
185
+ const end = braceEnd(content, index);
186
+ if (end === undefined) return undefined;
187
+ index = end;
188
+ continue;
189
+ }
190
+ index++;
191
+ }
192
+ return undefined;
193
+ }
194
+
195
+ /** The index past the `}` that balances the `{` at `start`, strings skipped. */
196
+ function braceEnd(content: string, start: number): number | undefined {
197
+ let depth = 0;
198
+ let index = start;
199
+ while (index < content.length) {
200
+ const character = content[index];
201
+ if (character === '"' || character === "'" || character === '`') {
202
+ const end = stringEnd(content, index);
203
+ if (end === undefined) return undefined;
204
+ index = end;
205
+ continue;
206
+ }
207
+ if (character === '{') depth++;
208
+ else if (character === '}') {
209
+ depth--;
210
+ if (depth === 0) return index + 1;
211
+ }
212
+ index++;
213
+ }
214
+ return undefined;
215
+ }
216
+
217
+ /** The index past a string literal opening at `start`, escapes and a template's
218
+ * `${...}` skipped. */
219
+ function stringEnd(content: string, start: number): number | undefined {
220
+ const quote = content[start];
221
+ let index = start + 1;
222
+ while (index < content.length) {
223
+ const character = content[index];
224
+ if (character === '\\') {
225
+ index += 2;
226
+ continue;
227
+ }
228
+ if (character === quote) return index + 1;
229
+ if (quote === '`' && character === '$' && content[index + 1] === '{') {
230
+ const end = braceEnd(content, index + 1);
231
+ if (end === undefined) return undefined;
232
+ index = end;
233
+ continue;
234
+ }
235
+ index++;
236
+ }
237
+ return undefined;
238
+ }
@@ -0,0 +1,26 @@
1
+ // The module a source's components are imported from for one target, shared by
2
+ // the generator (Node) and the controller's source printer (browser).
3
+
4
+ /** The token a source's `moduleBase` names its target with. */
5
+ export const TARGET_TOKEN = '{target}';
6
+
7
+ /**
8
+ * The module a source's components are imported from for one target: the
9
+ * source's `moduleBase` with `{target}` replaced by the target, or, when it
10
+ * has no `{target}`, the target as a subpath of it.
11
+ *
12
+ * @param moduleBase - The source's `moduleBase`.
13
+ * @param target - The target, such as `'react'`.
14
+ * @returns The module specifier.
15
+ * @example
16
+ * ```ts
17
+ * moduleOfSource('my-ui-kit', 'react'); // 'my-ui-kit/react'
18
+ * moduleOfSource('@acme/{target}', 'react'); // '@acme/react'
19
+ * moduleOfSource('acme-{target}', 'vue'); // 'acme-vue'
20
+ * ```
21
+ */
22
+ export function moduleOfSource(moduleBase: string, target: string): string {
23
+ return moduleBase.includes(TARGET_TOKEN)
24
+ ? moduleBase.split(TARGET_TOKEN).join(target)
25
+ : `${moduleBase}/${target}`;
26
+ }
@@ -0,0 +1,65 @@
1
+ // Id / title helpers. The id algorithm (lowercase, non-alphanumerics → '-',
2
+ // collapse, trim) keeps component ids deterministic and identical across the
3
+ // per-target registries and the host manifest - which is what the
4
+ // nav ↔ render lookup and the side-by-side iframe panes depend on. Pinned by
5
+ // naming.test.ts.
6
+
7
+ export function sanitize(value: string): string {
8
+ return value
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, '-')
11
+ .replace(/^-+|-+$/g, '')
12
+ .replace(/-+/g, '-');
13
+ }
14
+
15
+ /** The id of the preview's own Empty scenario, which renders a component
16
+ * with its slots empty: no scenario of a story's takes it. */
17
+ export const EMPTY_SCENARIO_ID = 'empty';
18
+
19
+ /**
20
+ * The id a pane mounts the story of one scenario by, when the scenario has
21
+ * slot content of its own: the component's id and the scenario's, with two
22
+ * hyphens between, which no component id holds.
23
+ *
24
+ * @param componentId - The component's story id.
25
+ * @param scenarioId - The scenario's id.
26
+ * @returns The scenario story's id.
27
+ * @example
28
+ * ```ts
29
+ * scenarioStoryId('components-notice', 'warning'); // 'components-notice--warning'
30
+ * ```
31
+ */
32
+ export function scenarioStoryId(
33
+ componentId: string,
34
+ scenarioId: string
35
+ ): string {
36
+ return `${componentId}--${scenarioId}`;
37
+ }
38
+
39
+ export function storyId(title: string, exportName: string): string {
40
+ return `${sanitize(title)}--${sanitize(exportName)}`;
41
+ }
42
+
43
+ /**
44
+ * The name a kit's build gives a template without a component node, from its
45
+ * file: the stem's alphanumeric runs in PascalCase, `_` before a leading
46
+ * digit, `Component` when nothing is left (`theme-toggle.ts` → `ThemeToggle`,
47
+ * `2up.grid.ts` → `_2upGrid`). A copy of `componentNameFromFilePath` in
48
+ * scaffold-ui-kit's `template-sources.ts`, the source of truth, which this
49
+ * package cannot import; the two must agree, since the name namespaces the
50
+ * `data-jte-node` markers the kit's stylesheet selects.
51
+ */
52
+ export function componentNameFromFile(file: string): string {
53
+ const base = file.slice(
54
+ Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')) + 1
55
+ );
56
+ const dot = base.lastIndexOf('.');
57
+ const stem = dot > 0 ? base.slice(0, dot) : base;
58
+ const name = stem
59
+ .split(/[^a-zA-Z0-9]+/)
60
+ .filter((segment) => segment.length > 0)
61
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
62
+ .join('');
63
+ if (name === '') return 'Component';
64
+ return /^[0-9]/.test(name) ? `_${name}` : name;
65
+ }