meno-core 1.0.53 → 1.1.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 (415) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +66 -261
  3. package/build-astro.ts +150 -128
  4. package/dist/bin/cli.js +42 -185
  5. package/dist/bin/cli.js.map +2 -2
  6. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  7. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  8. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
  9. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  10. package/dist/chunks/chunk-4ZRU52J2.js +169 -0
  11. package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
  12. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
  13. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  14. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
  15. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  16. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
  17. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  18. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
  19. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  20. package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
  21. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  22. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
  23. package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
  24. package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
  25. package/dist/lib/client/index.js +1003 -475
  26. package/dist/lib/client/index.js.map +3 -3
  27. package/dist/lib/server/index.js +14393 -6371
  28. package/dist/lib/server/index.js.map +4 -4
  29. package/dist/lib/shared/index.js +342 -101
  30. package/dist/lib/shared/index.js.map +4 -4
  31. package/dist/lib/shared/richtext/index.js +1 -1
  32. package/dist/lib/test-utils/index.js +64 -80
  33. package/dist/lib/test-utils/index.js.map +2 -2
  34. package/entries/client-router.tsx +10 -8
  35. package/lib/client/ClientInitializer.ts +8 -8
  36. package/lib/client/ErrorBoundary.test.tsx +246 -186
  37. package/lib/client/ErrorBoundary.tsx +183 -132
  38. package/lib/client/componentRegistry.test.ts +96 -108
  39. package/lib/client/componentRegistry.ts +1 -3
  40. package/lib/client/contexts/ThemeContext.tsx +3 -2
  41. package/lib/client/core/ComponentBuilder.test.ts +663 -560
  42. package/lib/client/core/ComponentBuilder.ts +427 -155
  43. package/lib/client/core/ComponentRenderer.test.tsx +2 -4
  44. package/lib/client/core/ComponentRenderer.tsx +46 -33
  45. package/lib/client/core/builders/embedBuilder.ts +242 -54
  46. package/lib/client/core/builders/linkBuilder.ts +72 -45
  47. package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
  48. package/lib/client/core/builders/listBuilder.ts +155 -89
  49. package/lib/client/core/builders/localeListBuilder.ts +97 -62
  50. package/lib/client/core/builders/types.ts +5 -5
  51. package/lib/client/core/cmsTemplateProcessor.ts +8 -9
  52. package/lib/client/elementRegistry.ts +3 -3
  53. package/lib/client/fontFamiliesService.test.ts +2 -10
  54. package/lib/client/fontFamiliesService.ts +3 -3
  55. package/lib/client/hmr/HMRManager.tsx +47 -30
  56. package/lib/client/hmrCssReload.ts +49 -14
  57. package/lib/client/hmrWebSocket.ts +14 -19
  58. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  59. package/lib/client/hooks/useColorVariables.ts +23 -18
  60. package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
  61. package/lib/client/hooks/useVariables.ts +6 -4
  62. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  63. package/lib/client/hydration/HydrationUtils.ts +3 -4
  64. package/lib/client/i18nConfigService.test.ts +2 -7
  65. package/lib/client/i18nConfigService.ts +2 -2
  66. package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
  67. package/lib/client/meno-filter/MenoFilter.ts +14 -15
  68. package/lib/client/meno-filter/bindings.ts +18 -43
  69. package/lib/client/meno-filter/init.ts +4 -4
  70. package/lib/client/meno-filter/renderer.ts +30 -32
  71. package/lib/client/meno-filter/script.generated.ts +1 -3
  72. package/lib/client/meno-filter/ui.ts +4 -6
  73. package/lib/client/meno-filter/updates.ts +18 -23
  74. package/lib/client/meno-filter/utils.ts +1 -1
  75. package/lib/client/navigation.test.ts +174 -178
  76. package/lib/client/navigation.ts +0 -1
  77. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  78. package/lib/client/responsiveStyleResolver.test.ts +234 -232
  79. package/lib/client/responsiveStyleResolver.ts +74 -20
  80. package/lib/client/routing/RouteLoader.test.ts +24 -25
  81. package/lib/client/routing/RouteLoader.ts +27 -35
  82. package/lib/client/routing/Router.tsx +210 -153
  83. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  84. package/lib/client/scripts/ScriptExecutor.ts +18 -36
  85. package/lib/client/services/PrefetchService.test.ts +3 -3
  86. package/lib/client/services/PrefetchService.ts +23 -29
  87. package/lib/client/styleProcessor.test.ts +9 -9
  88. package/lib/client/styleProcessor.ts +24 -15
  89. package/lib/client/styles/StyleInjector.test.ts +122 -115
  90. package/lib/client/styles/StyleInjector.ts +10 -7
  91. package/lib/client/styles/UtilityClassCollector.ts +62 -28
  92. package/lib/client/styles/cspNonce.test.ts +2 -5
  93. package/lib/client/templateEngine.test.ts +554 -488
  94. package/lib/client/templateEngine.ts +203 -73
  95. package/lib/client/theme.test.ts +1 -1
  96. package/lib/client/theme.ts +0 -1
  97. package/lib/client/utils/toast.ts +0 -1
  98. package/lib/server/__integration__/api-routes.test.ts +8 -4
  99. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  100. package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
  101. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  102. package/lib/server/__integration__/static-assets.test.ts +3 -3
  103. package/lib/server/__integration__/test-helpers.ts +87 -74
  104. package/lib/server/ab/generateFunctions.ts +12 -10
  105. package/lib/server/astro/cmsPageEmitter.ts +26 -42
  106. package/lib/server/astro/componentEmitter.ts +35 -43
  107. package/lib/server/astro/cssCollector.ts +10 -26
  108. package/lib/server/astro/nodeToAstro.test.ts +1754 -34
  109. package/lib/server/astro/nodeToAstro.ts +232 -216
  110. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
  111. package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
  112. package/lib/server/astro/pageEmitter.ts +9 -13
  113. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  114. package/lib/server/astro/tailwindMapper.ts +33 -40
  115. package/lib/server/astro/templateTransformer.ts +17 -20
  116. package/lib/server/createServer.ts +7 -19
  117. package/lib/server/cssGenerator.test.ts +35 -44
  118. package/lib/server/cssGenerator.ts +8 -19
  119. package/lib/server/fileWatcher.test.ts +164 -13
  120. package/lib/server/fileWatcher.ts +209 -124
  121. package/lib/server/index.ts +14 -17
  122. package/lib/server/jsonLoader.test.ts +39 -2
  123. package/lib/server/jsonLoader.ts +39 -34
  124. package/lib/server/middleware/cors.test.ts +21 -21
  125. package/lib/server/middleware/cors.ts +28 -4
  126. package/lib/server/middleware/errorHandler.test.ts +7 -5
  127. package/lib/server/middleware/errorHandler.ts +3 -8
  128. package/lib/server/middleware/index.ts +0 -1
  129. package/lib/server/middleware/logger.test.ts +10 -8
  130. package/lib/server/middleware/logger.ts +17 -29
  131. package/lib/server/migrateTemplates.ts +3 -3
  132. package/lib/server/pageCache.test.ts +76 -77
  133. package/lib/server/pageCache.ts +0 -1
  134. package/lib/server/projectContext.ts +5 -4
  135. package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
  136. package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
  137. package/lib/server/routes/api/cms.test.ts +26 -14
  138. package/lib/server/routes/api/cms.ts +9 -14
  139. package/lib/server/routes/api/colors.test.ts +2 -2
  140. package/lib/server/routes/api/components.ts +31 -35
  141. package/lib/server/routes/api/config.ts +3 -4
  142. package/lib/server/routes/api/core-routes.ts +49 -76
  143. package/lib/server/routes/api/enums.test.ts +2 -2
  144. package/lib/server/routes/api/functions.ts +9 -17
  145. package/lib/server/routes/api/index.ts +3 -6
  146. package/lib/server/routes/api/pages.ts +8 -11
  147. package/lib/server/routes/api/shared.test.ts +1 -1
  148. package/lib/server/routes/api/shared.ts +12 -9
  149. package/lib/server/routes/api/variables.test.ts +3 -5
  150. package/lib/server/routes/api/variables.ts +1 -1
  151. package/lib/server/routes/index.ts +25 -28
  152. package/lib/server/routes/pages.ts +35 -31
  153. package/lib/server/routes/static.ts +17 -6
  154. package/lib/server/runtime/bundler.ts +98 -61
  155. package/lib/server/runtime/fs.ts +16 -26
  156. package/lib/server/runtime/httpServer.ts +8 -12
  157. package/lib/server/services/ColorService.test.ts +3 -3
  158. package/lib/server/services/ColorService.ts +37 -29
  159. package/lib/server/services/EnumService.test.ts +4 -8
  160. package/lib/server/services/EnumService.ts +11 -3
  161. package/lib/server/services/VariableService.test.ts +8 -12
  162. package/lib/server/services/VariableService.ts +11 -3
  163. package/lib/server/services/cmsService.test.ts +158 -120
  164. package/lib/server/services/cmsService.ts +36 -146
  165. package/lib/server/services/componentService.test.ts +125 -45
  166. package/lib/server/services/componentService.ts +70 -46
  167. package/lib/server/services/configService.test.ts +22 -45
  168. package/lib/server/services/configService.ts +50 -27
  169. package/lib/server/services/fileWatcherService.ts +42 -8
  170. package/lib/server/services/index.ts +0 -1
  171. package/lib/server/services/pageService.test.ts +8 -10
  172. package/lib/server/services/pageService.ts +32 -18
  173. package/lib/server/ssr/attributeBuilder.ts +18 -8
  174. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  175. package/lib/server/ssr/clientDataInjector.ts +7 -21
  176. package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
  177. package/lib/server/ssr/cssCollector.ts +1 -1
  178. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  179. package/lib/server/ssr/errorOverlay.ts +3 -9
  180. package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
  181. package/lib/server/ssr/htmlGenerator.test.ts +120 -44
  182. package/lib/server/ssr/htmlGenerator.ts +126 -91
  183. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  184. package/lib/server/ssr/imageMetadata.ts +27 -21
  185. package/lib/server/ssr/jsCollector.test.ts +8 -18
  186. package/lib/server/ssr/jsCollector.ts +3 -8
  187. package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
  188. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  189. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  190. package/lib/server/ssr/ssrRenderer.test.ts +316 -256
  191. package/lib/server/ssr/ssrRenderer.ts +795 -243
  192. package/lib/server/ssrRenderer.test.ts +1048 -954
  193. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  194. package/lib/server/utils/jsonLineMapper.ts +7 -7
  195. package/lib/server/validateStyleCoverage.ts +18 -21
  196. package/lib/server/websocketManager.ts +28 -19
  197. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  198. package/lib/shared/attributeNodeUtils.ts +5 -12
  199. package/lib/shared/breakpoints.test.ts +5 -5
  200. package/lib/shared/breakpoints.ts +4 -11
  201. package/lib/shared/cmsQuery.test.ts +81 -0
  202. package/lib/shared/cmsQuery.ts +92 -0
  203. package/lib/shared/cmsQueryParser.test.ts +50 -42
  204. package/lib/shared/cmsQueryParser.ts +60 -38
  205. package/lib/shared/colorConversions.ts +9 -9
  206. package/lib/shared/colorVariableUtils.test.ts +27 -5
  207. package/lib/shared/colorVariableUtils.ts +21 -12
  208. package/lib/shared/componentRefs.ts +1 -5
  209. package/lib/shared/constants.test.ts +3 -3
  210. package/lib/shared/constants.ts +8 -8
  211. package/lib/shared/cssGeneration.test.ts +285 -144
  212. package/lib/shared/cssGeneration.ts +217 -531
  213. package/lib/shared/cssNamedColors.ts +152 -30
  214. package/lib/shared/cssProperties.test.ts +69 -70
  215. package/lib/shared/cssProperties.ts +298 -114
  216. package/lib/shared/elementClassName.test.ts +109 -109
  217. package/lib/shared/elementClassName.ts +3 -3
  218. package/lib/shared/elementUtils.ts +12 -16
  219. package/lib/shared/errorLogger.test.ts +12 -12
  220. package/lib/shared/errorLogger.ts +2 -10
  221. package/lib/shared/errors.test.ts +2 -13
  222. package/lib/shared/errors.ts +2 -8
  223. package/lib/shared/expressionEvaluator.test.ts +145 -0
  224. package/lib/shared/expressionEvaluator.ts +52 -24
  225. package/lib/shared/fontLoader.test.ts +32 -18
  226. package/lib/shared/fontLoader.ts +2 -2
  227. package/lib/shared/friendlyError.ts +2 -3
  228. package/lib/shared/gradientUtils.test.ts +3 -7
  229. package/lib/shared/gradientUtils.ts +17 -14
  230. package/lib/shared/hrefRefs.test.ts +2 -2
  231. package/lib/shared/hrefRefs.ts +2 -10
  232. package/lib/shared/i18n.test.ts +4 -4
  233. package/lib/shared/i18n.ts +18 -38
  234. package/lib/shared/index.ts +27 -0
  235. package/lib/shared/inlineSvgStyleRules.ts +14 -13
  236. package/lib/shared/interactiveStyleMappings.test.ts +32 -54
  237. package/lib/shared/interactiveStyleMappings.ts +11 -24
  238. package/lib/shared/interactiveStyles.test.ts +165 -188
  239. package/lib/shared/interfaces/contentProvider.ts +1 -1
  240. package/lib/shared/itemTemplateUtils.test.ts +11 -17
  241. package/lib/shared/itemTemplateUtils.ts +27 -39
  242. package/lib/shared/jsonRepair.ts +16 -10
  243. package/lib/shared/libraryLoader.test.ts +21 -55
  244. package/lib/shared/libraryLoader.ts +7 -22
  245. package/lib/shared/linkUtils.ts +1 -1
  246. package/lib/shared/logger.test.ts +66 -0
  247. package/lib/shared/logger.ts +94 -0
  248. package/lib/shared/markdown.ts +25 -0
  249. package/lib/shared/netlifyLocale404.test.ts +179 -0
  250. package/lib/shared/netlifyLocale404.ts +110 -0
  251. package/lib/shared/nodeUtils.test.ts +28 -16
  252. package/lib/shared/nodeUtils.ts +76 -23
  253. package/lib/shared/pathArrayUtils.test.ts +1 -2
  254. package/lib/shared/pathArrayUtils.ts +1 -1
  255. package/lib/shared/pathSecurity.ts +2 -2
  256. package/lib/shared/pathUtils.test.ts +4 -6
  257. package/lib/shared/pathUtils.ts +42 -49
  258. package/lib/shared/paths/Path.test.ts +2 -2
  259. package/lib/shared/paths/Path.ts +0 -1
  260. package/lib/shared/paths/PathConverter.test.ts +1 -1
  261. package/lib/shared/paths/PathConverter.ts +17 -20
  262. package/lib/shared/paths/PathUtils.ts +13 -13
  263. package/lib/shared/paths/PathValidator.test.ts +2 -15
  264. package/lib/shared/paths/PathValidator.ts +12 -10
  265. package/lib/shared/paths/index.ts +1 -2
  266. package/lib/shared/permissions.test.ts +168 -0
  267. package/lib/shared/permissions.ts +162 -0
  268. package/lib/shared/propResolver.test.ts +240 -244
  269. package/lib/shared/propResolver.ts +15 -26
  270. package/lib/shared/pxToRem.test.ts +7 -6
  271. package/lib/shared/pxToRem.ts +2 -5
  272. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
  273. package/lib/shared/registry/ClientRegistry.ts +0 -2
  274. package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
  275. package/lib/shared/registry/ComponentRegistry.ts +12 -9
  276. package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
  277. package/lib/shared/registry/RegistryManager.ts +1 -2
  278. package/lib/shared/registry/SSRRegistry.ts +0 -2
  279. package/lib/shared/registry/createNodeType.ts +7 -9
  280. package/lib/shared/registry/defineNodeType.ts +2 -6
  281. package/lib/shared/registry/fieldPresets.ts +3 -1
  282. package/lib/shared/registry/index.ts +0 -1
  283. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  284. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  285. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  286. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
  287. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  288. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  289. package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
  290. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  291. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  292. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
  293. package/lib/shared/registry/nodeTypes/index.ts +26 -3
  294. package/lib/shared/responsiveScaling.test.ts +16 -34
  295. package/lib/shared/responsiveScaling.ts +61 -42
  296. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  297. package/lib/shared/responsiveStyleUtils.ts +11 -13
  298. package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
  299. package/lib/shared/richtext/htmlToTiptap.ts +2 -4
  300. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  301. package/lib/shared/richtext/types.ts +1 -8
  302. package/lib/shared/slugTranslator.test.ts +13 -13
  303. package/lib/shared/slugTranslator.ts +12 -16
  304. package/lib/shared/slugify.ts +9 -15
  305. package/lib/shared/styleNodeUtils.test.ts +8 -8
  306. package/lib/shared/styleNodeUtils.ts +18 -20
  307. package/lib/shared/styleUtils.test.ts +87 -61
  308. package/lib/shared/styleUtils.ts +5 -6
  309. package/lib/shared/themeDefaults.test.ts +11 -14
  310. package/lib/shared/themeDefaults.ts +3 -4
  311. package/lib/shared/tree/PathBuilder.test.ts +63 -145
  312. package/lib/shared/tree/PathBuilder.ts +59 -47
  313. package/lib/shared/treePathUtils.test.ts +51 -12
  314. package/lib/shared/treePathUtils.ts +91 -60
  315. package/lib/shared/types/api.ts +105 -8
  316. package/lib/shared/types/cms.test.ts +34 -0
  317. package/lib/shared/types/cms.ts +90 -34
  318. package/lib/shared/types/comment.ts +49 -16
  319. package/lib/shared/types/components.ts +51 -25
  320. package/lib/shared/types/errors.test.ts +1 -6
  321. package/lib/shared/types/errors.ts +3 -7
  322. package/lib/shared/types/experiments.ts +28 -28
  323. package/lib/shared/types/index.ts +25 -2
  324. package/lib/shared/types/permissions.ts +139 -0
  325. package/lib/shared/types/styles.ts +0 -1
  326. package/lib/shared/types/variables.test.ts +4 -13
  327. package/lib/shared/types/variables.ts +51 -30
  328. package/lib/shared/types.ts +1 -2
  329. package/lib/shared/utilityClassConfig.ts +751 -321
  330. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  331. package/lib/shared/utilityClassMapper.test.ts +433 -95
  332. package/lib/shared/utilityClassMapper.ts +322 -249
  333. package/lib/shared/utilityClassNames.test.ts +38 -0
  334. package/lib/shared/utilityClassNames.ts +458 -0
  335. package/lib/shared/utils.test.ts +2 -10
  336. package/lib/shared/utils.ts +19 -10
  337. package/lib/shared/validation/cmsValidators.ts +2 -1
  338. package/lib/shared/validation/commentValidators.test.ts +53 -0
  339. package/lib/shared/validation/commentValidators.ts +12 -1
  340. package/lib/shared/validation/index.ts +1 -0
  341. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  342. package/lib/shared/validation/permissionsValidators.ts +49 -0
  343. package/lib/shared/validation/propValidator.test.ts +19 -21
  344. package/lib/shared/validation/propValidator.ts +13 -18
  345. package/lib/shared/validation/schemas.test.ts +64 -32
  346. package/lib/shared/validation/schemas.ts +616 -345
  347. package/lib/shared/validation/validators.test.ts +3 -8
  348. package/lib/shared/validation/validators.ts +89 -68
  349. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  350. package/lib/shared/viewportUnits.test.ts +24 -23
  351. package/lib/shared/viewportUnits.ts +12 -5
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
  354. package/lib/test-utils/factories/DomMockFactory.ts +17 -23
  355. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  356. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  357. package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
  358. package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
  359. package/lib/test-utils/fixtures.ts +45 -45
  360. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  361. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  362. package/lib/test-utils/helpers.ts +1 -5
  363. package/lib/test-utils/index.ts +0 -4
  364. package/lib/test-utils/mockFactories.ts +13 -19
  365. package/lib/test-utils/mocks.ts +6 -4
  366. package/package.json +3 -5
  367. package/scripts/build-for-publish.mjs +2 -4
  368. package/scripts/build-meno-filter.ts +4 -7
  369. package/tsconfig.json +1 -0
  370. package/vite.config.ts +4 -4
  371. package/build-next.ts +0 -1361
  372. package/build-static.test.ts +0 -424
  373. package/build-static.ts +0 -1074
  374. package/dist/build-static.js +0 -36
  375. package/dist/chunks/chunk-2MHDV5BF.js +0 -251
  376. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  377. package/dist/chunks/chunk-AZQYF6KE.js +0 -559
  378. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  379. package/dist/chunks/chunk-GZHGVVW3.js +0 -322
  380. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  381. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  382. package/dist/chunks/chunk-I7YIGZXT.js +0 -467
  383. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  384. package/dist/chunks/chunk-IGYR22T6.js +0 -6192
  385. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  386. package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
  387. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  388. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  389. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  390. package/dist/chunks/chunk-QB2LNO4W.js +0 -77
  391. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  392. package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
  393. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  394. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  395. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  396. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  397. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  398. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  399. package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
  400. package/dist/chunks/constants-STK2YBIW.js +0 -46
  401. package/dist/chunks/constants-STK2YBIW.js.map +0 -7
  402. package/dist/chunks/fs-JGINUXGL.js.map +0 -7
  403. package/dist/entries/server-router.js +0 -67
  404. package/dist/entries/server-router.js.map +0 -7
  405. package/entries/server-router.tsx +0 -81
  406. package/lib/server/providers/fileSystemPageProvider.ts +0 -160
  407. package/lib/server/webflow/buildWebflow.ts +0 -635
  408. package/lib/server/webflow/index.ts +0 -23
  409. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
  410. package/lib/server/webflow/nodeToWebflow.ts +0 -2435
  411. package/lib/server/webflow/styleMapper.test.ts +0 -389
  412. package/lib/server/webflow/styleMapper.ts +0 -695
  413. package/lib/server/webflow/templateWrapper.ts +0 -49
  414. package/lib/server/webflow/types.ts +0 -396
  415. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -1,256 +1,382 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { generateRuleForClass, generateUtilityCSS, generateSingleClassCSS, extractUtilityClassesFromHTML, generateInteractiveCSS, applyContainerPattern } from './cssGeneration';
2
+ import {
3
+ generateRuleForClass,
4
+ generateUtilityCSS,
5
+ generateSingleClassCSS,
6
+ extractUtilityClassesFromHTML,
7
+ generateInteractiveCSS,
8
+ applyContainerPattern,
9
+ styleObjectToCSS,
10
+ } from './cssGeneration';
3
11
  import { DEFAULT_BREAKPOINTS } from './breakpoints';
4
12
  import type { ResponsiveScales } from './responsiveScaling';
5
13
  import type { InteractiveStyles } from './types/styles';
6
14
  import { registerStyleValue, clearRegistry } from './styleValueRegistry';
7
15
 
8
16
  describe('extractUtilityClassesFromHTML', () => {
9
- test('extracts ins- classes', () => {
10
- const html = '<div class="ins-0 ins-auto ins-auto-0-0"></div>';
17
+ test('extracts inset classes', () => {
18
+ const html = '<div class="inset-[0] inset-auto inset-[auto_0_0]"></div>';
11
19
  const classes = extractUtilityClassesFromHTML(html);
12
- expect(classes.has('ins-0')).toBe(true);
13
- expect(classes.has('ins-auto')).toBe(true);
14
- expect(classes.has('ins-auto-0-0')).toBe(true);
20
+ expect(classes.has('inset-[0]')).toBe(true);
21
+ expect(classes.has('inset-auto')).toBe(true);
22
+ expect(classes.has('inset-[auto_0_0]')).toBe(true);
15
23
  });
16
24
 
17
- test('extracts bgi- classes', () => {
18
- const html = '<div class="bgi-url(/image.jpg) bgi-linear-gradient(#000,-#fff)"></div>';
25
+ test('extracts background-image classes', () => {
26
+ const html = '<div class="bg-[url(/image.jpg)] bg-[linear-gradient(#000,_#fff)]"></div>';
19
27
  const classes = extractUtilityClassesFromHTML(html);
20
- expect(classes.has('bgi-url(/image.jpg)')).toBe(true);
21
- expect(classes.has('bgi-linear-gradient(#000,-#fff)')).toBe(true);
28
+ expect(classes.has('bg-[url(/image.jpg)]')).toBe(true);
29
+ expect(classes.has('bg-[linear-gradient(#000,_#fff)]')).toBe(true);
22
30
  });
23
31
 
24
- test('extracts ins- and bgi- alongside other classes', () => {
25
- const html = '<div class="w-100p h-30p bgi-linear-gradient(#0000,-#0f1442-94p) pos-absolute bo-0 ins-auto-0-0 z-2"></div>';
32
+ test('extracts a mix of utility class forms alongside each other', () => {
33
+ const html =
34
+ '<div class="w-full h-[30%] bg-[linear-gradient(#0000,_#0f1442_94%)] absolute bottom-[0] inset-[auto_0_0] z-2"></div>';
26
35
  const classes = extractUtilityClassesFromHTML(html);
27
- expect(classes.has('w-100p')).toBe(true);
28
- expect(classes.has('h-30p')).toBe(true);
29
- expect(classes.has('bgi-linear-gradient(#0000,-#0f1442-94p)')).toBe(true);
30
- expect(classes.has('pos-absolute')).toBe(true);
31
- expect(classes.has('bo-0')).toBe(true);
32
- expect(classes.has('ins-auto-0-0')).toBe(true);
36
+ expect(classes.has('w-full')).toBe(true);
37
+ expect(classes.has('h-[30%]')).toBe(true);
38
+ expect(classes.has('bg-[linear-gradient(#0000,_#0f1442_94%)]')).toBe(true);
39
+ expect(classes.has('absolute')).toBe(true);
40
+ expect(classes.has('bottom-[0]')).toBe(true);
41
+ expect(classes.has('inset-[auto_0_0]')).toBe(true);
33
42
  expect(classes.has('z-2')).toBe(true);
34
43
  });
35
44
 
36
- test('extracts ins- classes with % character', () => {
37
- const html = '<div class="ins-auto-0%-0%"></div>';
45
+ test('extracts responsive variant classes', () => {
46
+ const html = '<div class="p-[40px] tablet:p-[24px] mobile:hidden"></div>';
38
47
  const classes = extractUtilityClassesFromHTML(html);
39
- expect(classes.has('ins-auto-0%-0%')).toBe(true);
48
+ expect(classes.has('p-[40px]')).toBe(true);
49
+ expect(classes.has('tablet:p-[24px]')).toBe(true);
50
+ expect(classes.has('mobile:hidden')).toBe(true);
40
51
  });
41
52
 
42
- test('extracts bgi- classes with % character', () => {
43
- const html = '<div class="bgi-linear-gradient(#0000,-#0f1442-94%)"></div>';
53
+ test('extracts arbitrary-property classes', () => {
54
+ const html = '<div class="[text-overflow:ellipsis]"></div>';
44
55
  const classes = extractUtilityClassesFromHTML(html);
45
- expect(classes.has('bgi-linear-gradient(#0000,-#0f1442-94%)')).toBe(true);
56
+ expect(classes.has('[text-overflow:ellipsis]')).toBe(true);
46
57
  });
47
58
 
48
- test('extracts l- (left) classes correctly without treating as responsive prefix', () => {
49
- const html = '<div class="l-10px l-auto l-0"></div>';
59
+ test('does not extract non-utility authored classes', () => {
60
+ const html = '<div class="nav-open text-wrapper c_hero_title m_label_1abc"></div>';
50
61
  const classes = extractUtilityClassesFromHTML(html);
51
- expect(classes.has('l-10px')).toBe(true);
52
- expect(classes.has('l-auto')).toBe(true);
53
- expect(classes.has('l-0')).toBe(true);
62
+ expect(classes.size).toBe(0);
54
63
  });
55
64
  });
56
65
 
57
66
  describe('generateUtilityCSS end-to-end', () => {
58
- test('generates CSS for ins- classes from HTML', () => {
59
- const html = '<div class="ins-auto-0-0"></div>';
67
+ test('generates CSS for inset classes from HTML', () => {
68
+ const html = '<div class="inset-[auto_0_0]"></div>';
60
69
  const classes = extractUtilityClassesFromHTML(html);
61
70
  const css = generateUtilityCSS(classes);
62
71
  expect(css).toContain('inset: auto 0 0');
63
72
  });
64
73
 
65
- test('generates CSS for bgi- classes from HTML', () => {
66
- const html = '<div class="bgi-linear-gradient(#000,-#fff)"></div>';
74
+ test('generates CSS for background-image classes from HTML', () => {
75
+ const html = '<div class="bg-[linear-gradient(#000,_#fff)]"></div>';
67
76
  const classes = extractUtilityClassesFromHTML(html);
68
77
  const css = generateUtilityCSS(classes);
69
78
  expect(css).toContain('background-image: linear-gradient(#000, #fff)');
70
79
  });
71
80
 
72
- test('generates CSS for l- (left) classes from HTML', () => {
73
- const html = '<div class="l-10px l-auto l-0"></div>';
81
+ test('generates CSS for left classes from HTML', () => {
82
+ const html = '<div class="left-[10px] left-auto left-[0]"></div>';
74
83
  const classes = extractUtilityClassesFromHTML(html);
75
84
  const css = generateUtilityCSS(classes);
76
85
  expect(css).toContain('left: 10px');
77
86
  expect(css).toContain('left: auto');
78
87
  expect(css).toContain('left: 0');
79
88
  });
89
+
90
+ test('generates @media rules for responsive variant classes', () => {
91
+ const css = generateUtilityCSS(new Set(['p-[40px]', 'tablet:p-[24px]', 'mobile:p-[12px]']));
92
+ expect(css).toContain('.p-\\[40px\\] { padding: 40px; }');
93
+ expect(css).toContain('@media (max-width: 1024px)');
94
+ expect(css).toContain('.tablet\\:p-\\[24px\\] { padding: 24px; }');
95
+ expect(css).toContain('@media (max-width: 540px)');
96
+ expect(css).toContain('.mobile\\:p-\\[12px\\] { padding: 12px; }');
97
+ });
98
+
99
+ test('auto-responsive @media keeps shorthand BEFORE longhand so margin-bottom survives', () => {
100
+ // A centered heading: margin:auto (centering) + marginBottom (spacing). The classes can
101
+ // arrive in either order; the longhand must win in EVERY breakpoint block (equal
102
+ // specificity ⇒ source order), so margin:auto can't reset margin-bottom on tablet.
103
+ const scales: ResponsiveScales = {
104
+ enabled: true,
105
+ mode: 'breakpoints',
106
+ baseReference: 16,
107
+ margin: { tablet: 0.7, mobile: 0.45 },
108
+ };
109
+ // Insertion order with the longhand FIRST — the bug only manifests when the shorthand
110
+ // would otherwise be emitted after it inside @media.
111
+ const css = generateUtilityCSS(new Set(['mb-[24px]', 'm-auto']), DEFAULT_BREAKPOINTS, scales);
112
+ const tabletStart = css.indexOf('@media (max-width: 1024px)');
113
+ const tabletBlock = css.slice(tabletStart, css.indexOf('}\n}', tabletStart) + 3);
114
+ const posShorthand = tabletBlock.indexOf('.m-auto');
115
+ const posLonghand = tabletBlock.indexOf('.mb-\\[24px\\]');
116
+ expect(posShorthand).toBeGreaterThanOrEqual(0);
117
+ expect(posLonghand).toBeGreaterThan(posShorthand); // shorthand precedes longhand → mb wins
118
+ });
80
119
  });
81
120
 
82
121
  describe('cssGeneration', () => {
83
122
  describe('generateRuleForClass', () => {
84
- test('border-top generates only width and style (no color)', () => {
85
- const rule = generateRuleForClass('bt-1px-solid');
123
+ test('border-top generates width and style longhands', () => {
124
+ const rule = generateRuleForClass('border-t-[1px_solid]');
86
125
  expect(rule).toBe('border-top-width: 1px; border-top-style: solid;');
87
126
  });
88
127
 
89
- test('border-bottom generates only width and style (no color)', () => {
90
- const rule = generateRuleForClass('bb-2px-dashed');
128
+ test('border-bottom generates width and style longhands', () => {
129
+ const rule = generateRuleForClass('border-b-[2px_dashed]');
91
130
  expect(rule).toBe('border-bottom-width: 2px; border-bottom-style: dashed;');
92
131
  });
93
132
 
94
- test('border-left generates only width and style (no color)', () => {
95
- const rule = generateRuleForClass('bl-1px-dotted');
133
+ test('border-left generates width and style longhands', () => {
134
+ const rule = generateRuleForClass('border-l-[1px_dotted]');
96
135
  expect(rule).toBe('border-left-width: 1px; border-left-style: dotted;');
97
136
  });
98
137
 
99
- test('border-right generates only width and style (no color)', () => {
100
- const rule = generateRuleForClass('border-r-3px-solid');
138
+ test('border-right generates width and style longhands', () => {
139
+ const rule = generateRuleForClass('border-r-[3px_solid]');
101
140
  expect(rule).toBe('border-right-width: 3px; border-right-style: solid;');
102
141
  });
103
142
 
104
- test('border (full) still includes color', () => {
105
- const rule = generateRuleForClass('b-1px-solid-primary');
143
+ test('border side with color emits color as a longhand too', () => {
144
+ const rule = generateRuleForClass('border-t-[1px_solid_red]');
145
+ expect(rule).toBe('border-top-width: 1px; border-top-style: solid; border-top-color: red;');
146
+ });
147
+
148
+ test('border (full shorthand) still includes color', () => {
149
+ const rule = generateRuleForClass('border-[1px_solid_var(--primary)]');
106
150
  expect(rule).toBe('border: 1px solid var(--primary);');
107
151
  });
108
152
 
109
153
  test('border-color with named CSS color outputs directly', () => {
110
- const rule = generateRuleForClass('bc-red');
154
+ const rule = generateRuleForClass('border-[red]');
111
155
  expect(rule).toBe('border-color: red;');
112
156
  });
113
157
 
114
158
  test('border-color with variable uses var()', () => {
115
- const rule = generateRuleForClass('bc-primary');
159
+ const rule = generateRuleForClass('border-(--primary)');
116
160
  expect(rule).toBe('border-color: var(--primary);');
117
161
  });
162
+
163
+ test('border-color with a SPACED color function stays a longhand (not the border shorthand)', () => {
164
+ // Regression: `rgba(0, 0, 0, 0)` carries spaces INSIDE the parens. The old guard
165
+ // (`!value.includes(' ')`) misread it as a `border` shorthand, so a transparent
166
+ // badge border collapsed into `border: rgba(...)` and stopped overriding the
167
+ // `border: 1px solid` width/style — a dark currentColor border leaked through.
168
+ expect(generateRuleForClass('border-[rgba(0,_0,_0,_0)]')).toBe('border-color: rgba(0, 0, 0, 0);');
169
+ expect(generateRuleForClass('border-[rgb(0_0_0_/_50%)]')).toBe('border-color: rgb(0 0 0 / 50%);');
170
+ });
171
+
172
+ test('transparent border-color sorts AFTER the border shorthand so it always wins', () => {
173
+ // The two classes target distinct properties (border vs border-color), so the longhand
174
+ // must follow the shorthand in source order regardless of which was collected first.
175
+ const css = generateUtilityCSS(
176
+ new Set(['border-[rgba(0,_0,_0,_0)]', 'border-[1px_solid]']),
177
+ DEFAULT_BREAKPOINTS,
178
+ { enabled: false } as ResponsiveScales,
179
+ );
180
+ const posShorthand = css.indexOf('border: 1px solid;');
181
+ const posColor = css.indexOf('border-color: rgba(0, 0, 0, 0);');
182
+ expect(posShorthand).toBeGreaterThanOrEqual(0);
183
+ expect(posColor).toBeGreaterThan(posShorthand);
184
+ });
118
185
  });
119
186
 
120
187
  describe('CSS named colors vs variables', () => {
121
188
  test('color with named color outputs directly', () => {
122
- expect(generateRuleForClass('c-red')).toBe('color: red;');
123
- expect(generateRuleForClass('c-blue')).toBe('color: blue;');
124
- expect(generateRuleForClass('c-transparent')).toBe('color: transparent;');
189
+ expect(generateRuleForClass('text-[red]')).toBe('color: red;');
190
+ expect(generateRuleForClass('text-[blue]')).toBe('color: blue;');
191
+ expect(generateRuleForClass('text-transparent')).toBe('color: transparent;');
125
192
  });
126
193
 
127
194
  test('background-color with named color outputs directly', () => {
128
- expect(generateRuleForClass('bgc-green')).toBe('background-color: green;');
129
- expect(generateRuleForClass('bgc-white')).toBe('background-color: white;');
195
+ expect(generateRuleForClass('bg-[green]')).toBe('background-color: green;');
196
+ expect(generateRuleForClass('bg-[white]')).toBe('background-color: white;');
197
+ });
198
+
199
+ test('color with variable shorthand uses var()', () => {
200
+ expect(generateRuleForClass('text-(--primary)')).toBe('color: var(--primary);');
201
+ expect(generateRuleForClass('text-(--text)')).toBe('color: var(--text);');
202
+ expect(generateRuleForClass('bg-(--background)')).toBe('background-color: var(--background);');
130
203
  });
131
204
 
132
- test('color with variable name still uses var()', () => {
133
- expect(generateRuleForClass('c-primary')).toBe('color: var(--primary);');
134
- expect(generateRuleForClass('c-text')).toBe('color: var(--text);');
135
- expect(generateRuleForClass('bgc-background')).toBe('background-color: var(--background);');
205
+ test('font-size variable with length hint', () => {
206
+ expect(generateRuleForClass('text-(length:--font-size-lg)')).toBe('font-size: var(--font-size-lg);');
136
207
  });
137
208
  });
138
209
 
139
- describe('display (d-) prefix for non-special values', () => {
140
- test('d-contents generates display: contents', () => {
141
- expect(generateRuleForClass('d-contents')).toBe('display: contents;');
210
+ describe('display static utilities', () => {
211
+ test('flex generates display: flex', () => {
212
+ expect(generateRuleForClass('flex')).toBe('display: flex;');
213
+ });
214
+
215
+ test('hidden generates display: none', () => {
216
+ expect(generateRuleForClass('hidden')).toBe('display: none;');
217
+ });
218
+
219
+ test('contents generates display: contents', () => {
220
+ expect(generateRuleForClass('contents')).toBe('display: contents;');
142
221
  });
143
222
 
144
- test('d-flow-root generates display: flow-root', () => {
145
- expect(generateRuleForClass('d-flow-root')).toBe('display: flow-root;');
223
+ test('flow-root generates display: flow-root', () => {
224
+ expect(generateRuleForClass('flow-root')).toBe('display: flow-root;');
146
225
  });
147
226
 
148
- test('d-table generates display: table', () => {
149
- expect(generateRuleForClass('d-table')).toBe('display: table;');
227
+ test('table generates display: table', () => {
228
+ expect(generateRuleForClass('table')).toBe('display: table;');
150
229
  });
151
230
  });
152
231
 
153
- describe('bc- and bt- combination', () => {
154
- test('bc- controls color independently from bt-', () => {
155
- // bt- should NOT set color, allowing bc- to control it
156
- const btRule = generateRuleForClass('bt-1px-solid');
157
- const bcRule = generateRuleForClass('bc-primary');
232
+ describe('border-color and border-side combination', () => {
233
+ test('border color class controls color independently from side class', () => {
234
+ // border-t- should NOT set color when absent, allowing border-(--x) to control it
235
+ const sideRule = generateRuleForClass('border-t-[1px_solid]');
236
+ const colorRule = generateRuleForClass('border-(--primary)');
158
237
 
159
- // bt- should only have width and style
160
- expect(btRule).not.toContain('color');
161
- expect(btRule).toContain('border-top-width');
162
- expect(btRule).toContain('border-top-style');
238
+ expect(sideRule).not.toContain('color');
239
+ expect(sideRule).toContain('border-top-width');
240
+ expect(sideRule).toContain('border-top-style');
163
241
 
164
- // bc- should set the color
165
- expect(bcRule).toContain('border-color');
242
+ expect(colorRule).toContain('border-color');
166
243
  });
167
244
  });
168
245
 
169
- describe('inset (ins) prefix', () => {
170
- test('ins-0 generates correct CSS', () => {
171
- expect(generateRuleForClass('ins-0')).toBe('inset: 0;');
246
+ describe('inset root', () => {
247
+ test('inset-[0] generates correct CSS', () => {
248
+ expect(generateRuleForClass('inset-[0]')).toBe('inset: 0;');
172
249
  });
173
250
 
174
- test('ins-auto generates correct CSS', () => {
175
- expect(generateRuleForClass('ins-auto')).toBe('inset: auto;');
251
+ test('inset-auto generates correct CSS', () => {
252
+ expect(generateRuleForClass('inset-auto')).toBe('inset: auto;');
176
253
  });
177
254
 
178
- test('ins-auto-0-0 generates correct CSS with space-separated values', () => {
179
- expect(generateRuleForClass('ins-auto-0-0')).toBe('inset: auto 0 0;');
255
+ test('inset-[auto_0_0] generates correct CSS with space-separated values', () => {
256
+ expect(generateRuleForClass('inset-[auto_0_0]')).toBe('inset: auto 0 0;');
180
257
  });
181
258
 
182
- test('ins-0-0-auto-0 generates correct CSS', () => {
183
- expect(generateRuleForClass('ins-0-0-auto-0')).toBe('inset: 0 0 auto 0;');
259
+ test('inset-[0_0_auto_0] generates correct CSS', () => {
260
+ expect(generateRuleForClass('inset-[0_0_auto_0]')).toBe('inset: 0 0 auto 0;');
184
261
  });
185
262
  });
186
263
 
187
- describe('backgroundImage (bgi) prefix', () => {
188
- test('bgi-linear-gradient generates correct CSS', () => {
189
- // Simple gradient: linear-gradient(#000, #fff)
190
- // Encoded as: bgi-linear-gradient(#000,-#fff)
191
- expect(generateRuleForClass('bgi-linear-gradient(#000,-#fff)')).toBe('background-image: linear-gradient(#000, #fff);');
264
+ describe('background-image via bg root', () => {
265
+ test('bg-[linear-gradient(…)] generates background-image', () => {
266
+ expect(generateRuleForClass('bg-[linear-gradient(#000,_#fff)]')).toBe(
267
+ 'background-image: linear-gradient(#000, #fff);',
268
+ );
269
+ });
270
+
271
+ test('bg-[linear-gradient(…)] with color stop generates background-image', () => {
272
+ expect(generateRuleForClass('bg-[linear-gradient(#0000,_#0f1442_94%)]')).toBe(
273
+ 'background-image: linear-gradient(#0000, #0f1442 94%);',
274
+ );
192
275
  });
193
276
 
194
- test('bgi-linear-gradient with color stop generates correct CSS', () => {
195
- // Gradient with stop: linear-gradient(#0000, #0f1442 94%)
196
- // Encoded as: bgi-linear-gradient(#0000,-#0f1442-94p)
197
- expect(generateRuleForClass('bgi-linear-gradient(#0000,-#0f1442-94p)')).toBe('background-image: linear-gradient(#0000, #0f1442 94%);');
277
+ test('bg-[url(…)] generates background-image and preserves the url', () => {
278
+ expect(generateRuleForClass('bg-[url(/image.jpg)]')).toBe('background-image: url(/image.jpg);');
198
279
  });
199
280
 
200
- test('bgi-url generates correct CSS', () => {
201
- expect(generateRuleForClass('bgi-url(/image.jpg)')).toBe('background-image: url(/image.jpg);');
281
+ test('underscores inside url() are preserved', () => {
282
+ expect(generateRuleForClass('bg-[url(/images/hero_bg.png)]')).toBe('background-image: url(/images/hero_bg.png);');
283
+ });
284
+ });
285
+
286
+ describe('multi-property axis roots', () => {
287
+ test('px-[16px] generates left and right padding', () => {
288
+ expect(generateRuleForClass('px-[16px]')).toBe('padding-left: 16px; padding-right: 16px;');
289
+ });
290
+
291
+ test('my-[8px] generates top and bottom margin', () => {
292
+ expect(generateRuleForClass('my-[8px]')).toBe('margin-top: 8px; margin-bottom: 8px;');
293
+ });
294
+
295
+ test('mx-auto centers horizontally', () => {
296
+ expect(generateRuleForClass('mx-auto')).toBe('margin-left: auto; margin-right: auto;');
202
297
  });
203
298
  });
204
299
 
205
300
  describe('escapeCSSClassName', () => {
206
- test('class names with slash are escaped in CSS selectors', () => {
207
- // If a class with / somehow gets through, it should be escaped in the CSS output
208
- const classes = new Set(['ar-1/1']);
301
+ test('bracket/slash/colon characters are escaped in CSS selectors', () => {
302
+ const classes = new Set(['aspect-[1/1]', 'tablet:w-[50%]']);
209
303
  const css = generateUtilityCSS(classes);
210
- expect(css).toContain('ar-1\\/1');
211
- expect(css).not.toContain('.ar-1/1 ');
304
+ expect(css).toContain('.aspect-\\[1\\/1\\]');
305
+ expect(css).toContain('.tablet\\:w-\\[50\\%\\]');
306
+ expect(css).not.toContain('.aspect-[1/1] ');
212
307
  });
213
308
  });
214
309
 
215
310
  describe('style presets', () => {
216
311
  test('shadow presets generate correct CSS', () => {
217
- expect(generateRuleForClass('sh-0')).toBe('box-shadow: none;');
218
- expect(generateRuleForClass('sh-1')).toBe('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);');
219
- expect(generateRuleForClass('sh-2')).toBe('box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);');
220
- expect(generateRuleForClass('sh-3')).toBe('box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);');
221
- expect(generateRuleForClass('sh-4')).toBe('box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);');
312
+ expect(generateRuleForClass('shadow-0')).toBe('box-shadow: none;');
313
+ expect(generateRuleForClass('shadow-1')).toBe('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);');
314
+ expect(generateRuleForClass('shadow-2')).toBe('box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);');
315
+ expect(generateRuleForClass('shadow-3')).toBe('box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);');
316
+ expect(generateRuleForClass('shadow-4')).toBe('box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);');
222
317
  });
223
318
 
224
319
  test('gradient presets generate correct CSS', () => {
225
- expect(generateRuleForClass('gr-1')).toBe('background: linear-gradient(90deg, rgba(10,22,40,0.85) 0%, rgba(10,22,40,0.2) 100%);');
226
- expect(generateRuleForClass('gr-2')).toBe('background: linear-gradient(0deg, #a9e8fd, #00aee8 25%, #0f1335 72%);');
320
+ expect(generateRuleForClass('bg-gradient-1')).toBe(
321
+ 'background: linear-gradient(90deg, rgba(10,22,40,0.85) 0%, rgba(10,22,40,0.2) 100%);',
322
+ );
323
+ expect(generateRuleForClass('bg-gradient-2')).toBe(
324
+ 'background: linear-gradient(0deg, #a9e8fd, #00aee8 25%, #0f1335 72%);',
325
+ );
227
326
  });
228
327
 
229
328
  test('border presets generate correct CSS', () => {
230
- expect(generateRuleForClass('bd-0')).toBe('border: 1px solid var(--border);');
231
- expect(generateRuleForClass('bd-1')).toBe('border: 1px solid var(--border-light);');
329
+ expect(generateRuleForClass('border-preset-0')).toBe('border: 1px solid var(--border);');
330
+ expect(generateRuleForClass('border-preset-1')).toBe('border: 1px solid var(--border-light);');
331
+ });
332
+ });
333
+
334
+ describe('Tailwind-style keyword and numeric utilities', () => {
335
+ test('size keywords', () => {
336
+ expect(generateRuleForClass('w-full')).toBe('width: 100%;');
337
+ expect(generateRuleForClass('h-screen')).toBe('height: 100vh;');
338
+ expect(generateRuleForClass('max-w-full')).toBe('max-width: 100%;');
339
+ expect(generateRuleForClass('rounded-full')).toBe('border-radius: 9999px;');
340
+ });
341
+
342
+ test('bare numeric utilities', () => {
343
+ expect(generateRuleForClass('z-10')).toBe('z-index: 10;');
344
+ expect(generateRuleForClass('order-2')).toBe('order: 2;');
345
+ expect(generateRuleForClass('opacity-50')).toBe('opacity: 0.5;');
346
+ });
347
+
348
+ test('static flexbox utilities', () => {
349
+ expect(generateRuleForClass('justify-center')).toBe('justify-content: center;');
350
+ expect(generateRuleForClass('items-center')).toBe('align-items: center;');
351
+ expect(generateRuleForClass('flex-col')).toBe('flex-direction: column;');
352
+ expect(generateRuleForClass('grow')).toBe('flex-grow: 1;');
353
+ });
354
+
355
+ test('arbitrary-property classes', () => {
356
+ expect(generateRuleForClass('[text-overflow:ellipsis]')).toBe('text-overflow: ellipsis;');
357
+ expect(generateRuleForClass('[clip-path:circle(50%)]')).toBe('clip-path: circle(50%);');
232
358
  });
233
359
  });
234
360
  });
235
361
 
236
362
  describe('generateSingleClassCSS - responsive classes', () => {
237
363
  test('generates correct media query for tablet-prefixed class', () => {
238
- const css = generateSingleClassCSS('t-fs-132px', DEFAULT_BREAKPOINTS);
364
+ const css = generateSingleClassCSS('tablet:text-[132px]', DEFAULT_BREAKPOINTS);
239
365
  expect(css).toContain('@media (max-width: 1024px)');
240
366
  expect(css).toContain('font-size: 132px');
241
- expect(css).toContain('.t-fs-132px');
367
+ expect(css).toContain('.tablet\\:text-\\[132px\\]');
242
368
  });
243
369
 
244
370
  test('generates correct media query for mobile-prefixed class', () => {
245
- const css = generateSingleClassCSS('mob-fs-24px', DEFAULT_BREAKPOINTS);
371
+ const css = generateSingleClassCSS('mobile:text-[24px]', DEFAULT_BREAKPOINTS);
246
372
  expect(css).toContain('@media (max-width: 540px)');
247
373
  expect(css).toContain('font-size: 24px');
248
- expect(css).toContain('.mob-fs-24px');
374
+ expect(css).toContain('.mobile\\:text-\\[24px\\]');
249
375
  });
250
376
 
251
377
  test('generates base rule for non-prefixed class', () => {
252
- const css = generateSingleClassCSS('fs-48px', DEFAULT_BREAKPOINTS);
253
- expect(css).toContain('.fs-48px');
378
+ const css = generateSingleClassCSS('text-[48px]', DEFAULT_BREAKPOINTS);
379
+ expect(css).toContain('.text-\\[48px\\]');
254
380
  expect(css).toContain('font-size: 48px');
255
381
  expect(css).not.toContain('@media');
256
382
  });
@@ -265,9 +391,7 @@ describe('generateInteractiveCSS — auto-responsive scaling', () => {
265
391
  };
266
392
 
267
393
  test('flat interactive style gets scaled @media overrides', () => {
268
- const styles: InteractiveStyles = [
269
- { prefix: '', postfix: ':hover', style: { fontSize: '100px' } },
270
- ];
394
+ const styles: InteractiveStyles = [{ prefix: '', postfix: ':hover', style: { fontSize: '100px' } }];
271
395
  const css = generateInteractiveCSS('c_heading_text', styles, DEFAULT_BREAKPOINTS, undefined, scalesEnabled);
272
396
  expect(css).toContain('.c_heading_text:hover { font-size: 100px; }');
273
397
  // tablet: 100 + (100-16)*(0.88-1) = 89.92 → 90
@@ -299,9 +423,7 @@ describe('generateInteractiveCSS — auto-responsive scaling', () => {
299
423
  });
300
424
 
301
425
  test('scaling disabled is a no-op', () => {
302
- const styles: InteractiveStyles = [
303
- { prefix: '', postfix: ':hover', style: { fontSize: '100px' } },
304
- ];
426
+ const styles: InteractiveStyles = [{ prefix: '', postfix: ':hover', style: { fontSize: '100px' } }];
305
427
  const cssNoScales = generateInteractiveCSS('c_heading', styles, DEFAULT_BREAKPOINTS);
306
428
  const cssDisabled = generateInteractiveCSS('c_heading', styles, DEFAULT_BREAKPOINTS, undefined, {
307
429
  ...scalesEnabled,
@@ -323,11 +445,11 @@ describe('fluid mode — clamp() emission', () => {
323
445
  };
324
446
 
325
447
  test('generateUtilityCSS emits a single clamp() rule with no @media for auto-responsive class', () => {
326
- const classes = new Set(['fs-32px']);
448
+ const classes = new Set(['text-[32px]']);
327
449
  const css = generateUtilityCSS(classes, DEFAULT_BREAKPOINTS, fluidScales);
328
450
  // mobile.breakpoint=540 is the smallest in DEFAULT_BREAKPOINTS, mobile scale=0.75
329
451
  // so MIN = 32 + (32-16)*(0.75-1) = 32 - 4 = 28; MAX = 32
330
- expect(css).toContain('.fs-32px');
452
+ expect(css).toContain('.text-\\[32px\\]');
331
453
  expect(css).toContain('clamp(28px,');
332
454
  expect(css).toContain(', 32px)');
333
455
  expect(css).not.toContain('@media');
@@ -341,7 +463,7 @@ describe('fluid mode — clamp() emission', () => {
341
463
  });
342
464
 
343
465
  test('generateSingleClassCSS emits clamp() in fluid mode', () => {
344
- const css = generateSingleClassCSS('p-40px', DEFAULT_BREAKPOINTS, fluidScales);
466
+ const css = generateSingleClassCSS('p-[40px]', DEFAULT_BREAKPOINTS, fluidScales);
345
467
  // mobile padding scale=0.5 → MIN = 40 + (40-16)*(0.5-1) = 40 - 12 = 28
346
468
  expect(css).toContain('clamp(28px,');
347
469
  expect(css).toContain(', 40px)');
@@ -350,7 +472,7 @@ describe('fluid mode — clamp() emission', () => {
350
472
 
351
473
  test('breakpoints mode (default when mode is undefined) keeps existing @media behavior', () => {
352
474
  const breakpointScales: ResponsiveScales = { ...fluidScales, mode: undefined };
353
- const classes = new Set(['fs-32px']);
475
+ const classes = new Set(['text-[32px]']);
354
476
  const css = generateUtilityCSS(classes, DEFAULT_BREAKPOINTS, breakpointScales);
355
477
  expect(css).toContain('@media (max-width: 1024px)');
356
478
  expect(css).toContain('@media (max-width: 540px)');
@@ -358,9 +480,7 @@ describe('fluid mode — clamp() emission', () => {
358
480
  });
359
481
 
360
482
  test('generateInteractiveCSS encodes scaling as clamp() on base rule, no @media', () => {
361
- const styles: InteractiveStyles = [
362
- { prefix: '', postfix: ':hover', style: { fontSize: '100px' } },
363
- ];
483
+ const styles: InteractiveStyles = [{ prefix: '', postfix: ':hover', style: { fontSize: '100px' } }];
364
484
  const css = generateInteractiveCSS('c_heading', styles, DEFAULT_BREAKPOINTS, undefined, fluidScales);
365
485
  // mobile fontSize scale=0.75 → MIN = 100 + (100-16)*(0.75-1) = 79
366
486
  expect(css).toContain('clamp(79px,');
@@ -373,7 +493,7 @@ describe('fluid mode — clamp() emission', () => {
373
493
  ...fluidScales,
374
494
  size: { tablet: 0.9, mobile: 0.75 },
375
495
  };
376
- const classes = new Set(['mw-1200px']);
496
+ const classes = new Set(['max-w-[1200px]']);
377
497
  const css = generateUtilityCSS(classes, DEFAULT_BREAKPOINTS, fluidWithSize);
378
498
  // mobile size scale=0.75 → MIN = 1200 + (1200-16)*(0.75-1) = 1200 - 296 = 904
379
499
  expect(css).toContain('clamp(904px,');
@@ -399,27 +519,26 @@ describe('fluid mode — clamp() emission', () => {
399
519
  expect(css).toMatch(/@media \(max-width: 540px\) \{[^}]*font-size:\s*60px/);
400
520
  });
401
521
 
402
- // Hash-fallback utility classes (e.g. `fs-h1glej9a` for `222.3px` the `.`
403
- // is illegal in CSS selectors, so utilityClassMapper generates a hashed name
522
+ // Hash-fallback utility classes (e.g. `text-h1glej9a` for a value that can't
523
+ // be encoded in a class name utilityClassMapper generates a hashed name
404
524
  // and stores the real value in styleValueRegistry). The scaling pipeline
405
- // previously bailed on these because extractPropertyAndValue returns null
406
- // for hashed classes. resolveScalablePropertyValue now consults the registry.
525
+ // resolves them through the registry via resolveScalablePropertyValue.
407
526
  describe('hash-fallback classes', () => {
408
- test('fluid mode: hashed fs- class with px value gets clamp()', () => {
527
+ test('fluid mode: hashed text- class with px value gets clamp()', () => {
409
528
  clearRegistry();
410
- registerStyleValue('fs-h1glej9a', '222.3px');
411
- const css = generateUtilityCSS(new Set(['fs-h1glej9a']), DEFAULT_BREAKPOINTS, fluidScales);
529
+ registerStyleValue('text-h1glej9a', '222.3px');
530
+ const css = generateUtilityCSS(new Set(['text-h1glej9a']), DEFAULT_BREAKPOINTS, fluidScales);
412
531
  // mobile fontSize scale=0.75, baseRef=16
413
532
  // MIN = 222.3 + (222.3 - 16)*(0.75 - 1) = 222.3 - 51.575 = 170.725 → round 171
414
- expect(css).toMatch(/\.fs-h1glej9a\s*\{\s*font-size:\s*clamp\(171px,/);
533
+ expect(css).toMatch(/\.text-h1glej9a\s*\{\s*font-size:\s*clamp\(171px,/);
415
534
  expect(css).toContain(', 222.3px)');
416
535
  expect(css).not.toContain('@media');
417
536
  });
418
537
 
419
538
  test('fluid mode: hashed class at baseReference renders plain (MIN===MAX, no clamp)', () => {
420
539
  clearRegistry();
421
- registerStyleValue('fs-hatref', '16px');
422
- const css = generateUtilityCSS(new Set(['fs-hatref']), DEFAULT_BREAKPOINTS, fluidScales);
540
+ registerStyleValue('text-hatref', '16px');
541
+ const css = generateUtilityCSS(new Set(['text-hatref']), DEFAULT_BREAKPOINTS, fluidScales);
423
542
  // 16 === baseReference → calculateResponsiveValue returns 16 → MIN===MAX → no clamp
424
543
  expect(css).toContain('font-size: 16px');
425
544
  expect(css).not.toContain('clamp(');
@@ -427,8 +546,8 @@ describe('fluid mode — clamp() emission', () => {
427
546
 
428
547
  test('fluid mode: hashed class with fractional px value above baseReference clamps with rounded MIN', () => {
429
548
  clearRegistry();
430
- registerStyleValue('fs-hfrac', '14.4px');
431
- const css = generateUtilityCSS(new Set(['fs-hfrac']), DEFAULT_BREAKPOINTS, fluidScales);
549
+ registerStyleValue('text-hfrac', '14.4px');
550
+ const css = generateUtilityCSS(new Set(['text-hfrac']), DEFAULT_BREAKPOINTS, fluidScales);
432
551
  // 14.4 < baseRef (16) → calculateResponsiveValue floors to round(14.4) = 14
433
552
  // MIN=14, MAX=14.4 → small clamp emitted
434
553
  expect(css).toMatch(/font-size:\s*clamp\(14px,/);
@@ -460,8 +579,8 @@ describe('fluid mode — clamp() emission', () => {
460
579
 
461
580
  test('generateSingleClassCSS handles hashed class in fluid mode', () => {
462
581
  clearRegistry();
463
- registerStyleValue('fs-htest1', '100px');
464
- const css = generateSingleClassCSS('fs-htest1', DEFAULT_BREAKPOINTS, fluidScales);
582
+ registerStyleValue('text-htest1', '100px');
583
+ const css = generateSingleClassCSS('text-htest1', DEFAULT_BREAKPOINTS, fluidScales);
465
584
  // mobile fontSize 0.75 → MIN = 100 + (100-16)*(0.75-1) = 79
466
585
  expect(css).toContain('clamp(79px,');
467
586
  expect(css).toContain(', 100px)');
@@ -512,7 +631,7 @@ describe('applyContainerPattern', () => {
512
631
  test('explicit marginLeft is overwritten with auto (per spec)', () => {
513
632
  const result = applyContainerPattern(
514
633
  { width: '1200px', maxWidth: '1200px', marginLeft: '0', marginRight: '12px' },
515
- true
634
+ true,
516
635
  );
517
636
  expect(result.marginLeft).toBe('auto');
518
637
  expect(result.marginRight).toBe('auto');
@@ -521,7 +640,7 @@ describe('applyContainerPattern', () => {
521
640
  test('preserves unrelated properties (padding, font-size)', () => {
522
641
  const result = applyContainerPattern(
523
642
  { width: '1200px', maxWidth: '1200px', padding: '20px', fontSize: '16px' },
524
- true
643
+ true,
525
644
  );
526
645
  expect(result.padding).toBe('20px');
527
646
  expect(result.fontSize).toBe('16px');
@@ -562,3 +681,25 @@ describe('generateInteractiveCSS — container pattern integration', () => {
562
681
  expect(css).toContain('max-width: 1200px');
563
682
  });
564
683
  });
684
+
685
+ describe('styleObjectToCSS', () => {
686
+ test('kebab-cases properties and joins declarations', () => {
687
+ expect(styleObjectToCSS({ paddingTop: '10px', fontSize: 16 })).toBe('padding-top: 10px; font-size: 16');
688
+ });
689
+
690
+ test('wraps bare color tokens in var(); leaves hex/rgb/var/functions intact', () => {
691
+ expect(styleObjectToCSS({ backgroundColor: 'primary' })).toBe('background-color: var(--primary)');
692
+ expect(styleObjectToCSS({ color: '#fff' })).toBe('color: #fff');
693
+ expect(styleObjectToCSS({ borderColor: 'var(--accent)' })).toBe('border-color: var(--accent)');
694
+ expect(styleObjectToCSS({ color: 'rgb(0,0,0)' })).toBe('color: rgb(0,0,0)');
695
+ });
696
+
697
+ test('skips _mapping (binding) values — bound props yield empty/partial output', () => {
698
+ expect(styleObjectToCSS({ color: { _mapping: { prop: 'x' } } as never })).toBe('');
699
+ expect(styleObjectToCSS({ color: { _mapping: { prop: 'x' } } as never, padding: '4px' })).toBe('padding: 4px');
700
+ });
701
+
702
+ test('empty object yields an empty string', () => {
703
+ expect(styleObjectToCSS({})).toBe('');
704
+ });
705
+ });