meno-core 1.0.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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,268 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ calculateResponsiveValue,
4
+ getScaleMultiplier,
5
+ parseMultiValue,
6
+ scaleValue,
7
+ scalePropertyValue,
8
+ getResponsiveValues,
9
+ DEFAULT_RESPONSIVE_SCALES,
10
+ type ResponsiveScales,
11
+ type CSSPropertyType,
12
+ } from './responsiveScaling';
13
+
14
+ describe('responsiveScaling', () => {
15
+ describe('calculateResponsiveValue', () => {
16
+ test('should calculate responsive value using formula', () => {
17
+ // responsive_value = base_value + (base_value - base_reference) * (scale - 1)
18
+ // 67 + (67 - 16) * (0.88 - 1) = 67 + 51 * (-0.12) = 67 - 6.12 = 60.88 -> 61
19
+ const result = calculateResponsiveValue(67, 16, 0.88);
20
+ expect(result).toBe(61);
21
+ });
22
+
23
+ test('should scale down with scale < 1', () => {
24
+ const result = calculateResponsiveValue(20, 16, 0.75);
25
+ // 20 + (20 - 16) * (0.75 - 1) = 20 + 4 * (-0.25) = 20 - 1 = 19
26
+ expect(result).toBe(19);
27
+ });
28
+
29
+ test('should scale up with scale > 1', () => {
30
+ const result = calculateResponsiveValue(20, 16, 1.5);
31
+ // 20 + (20 - 16) * (1.5 - 1) = 20 + 4 * 0.5 = 22
32
+ expect(result).toBe(22);
33
+ });
34
+
35
+ test('should return same value when scale is 1', () => {
36
+ const result = calculateResponsiveValue(20, 16, 1);
37
+ expect(result).toBe(20);
38
+ });
39
+
40
+ test('should handle value equal to base reference', () => {
41
+ const result = calculateResponsiveValue(16, 16, 0.75);
42
+ // 16 + (16 - 16) * (0.75 - 1) = 16 + 0 = 16
43
+ expect(result).toBe(16);
44
+ });
45
+ });
46
+
47
+ describe('getScaleMultiplier', () => {
48
+ test('should get tablet scale for fontSize', () => {
49
+ const scale = getScaleMultiplier(DEFAULT_RESPONSIVE_SCALES, 'fontSize', 'tablet');
50
+ expect(scale).toBe(0.88);
51
+ });
52
+
53
+ test('should get mobile scale for padding', () => {
54
+ const scale = getScaleMultiplier(DEFAULT_RESPONSIVE_SCALES, 'padding', 'mobile');
55
+ expect(scale).toBe(0.5);
56
+ });
57
+
58
+ test('should map paddingTop to padding category', () => {
59
+ const scale = getScaleMultiplier(DEFAULT_RESPONSIVE_SCALES, 'paddingTop', 'tablet');
60
+ expect(scale).toBe(0.75);
61
+ });
62
+
63
+ test('should map marginRight to margin category', () => {
64
+ const scale = getScaleMultiplier(DEFAULT_RESPONSIVE_SCALES, 'marginRight', 'mobile');
65
+ expect(scale).toBe(0.45);
66
+ });
67
+
68
+ test('should map rowGap to gap category', () => {
69
+ const scale = getScaleMultiplier(DEFAULT_RESPONSIVE_SCALES, 'rowGap', 'tablet');
70
+ expect(scale).toBe(0.65);
71
+ });
72
+
73
+ test('should return null for missing category', () => {
74
+ const scales: ResponsiveScales = { enabled: true, baseReference: 16 };
75
+ const scale = getScaleMultiplier(scales, 'fontSize', 'tablet');
76
+ expect(scale).toBeNull();
77
+ });
78
+
79
+ test('should return null for missing breakpoint', () => {
80
+ const scales: ResponsiveScales = {
81
+ enabled: true,
82
+ baseReference: 16,
83
+ fontSize: { tablet: 0.88 }
84
+ };
85
+ const scale = getScaleMultiplier(scales, 'fontSize', 'mobile');
86
+ expect(scale).toBeNull();
87
+ });
88
+ });
89
+
90
+ describe('parseMultiValue', () => {
91
+ test('should parse space-separated values', () => {
92
+ const result = parseMultiValue('20px 40px');
93
+ expect(result).toEqual(['20px', '40px']);
94
+ });
95
+
96
+ test('should parse hyphen-separated values', () => {
97
+ const result = parseMultiValue('0-80px');
98
+ expect(result).toEqual(['0', '80px']);
99
+ });
100
+
101
+ test('should handle single value', () => {
102
+ const result = parseMultiValue('20px');
103
+ expect(result).toEqual(['20px']);
104
+ });
105
+
106
+ test('should handle auto value', () => {
107
+ const result = parseMultiValue('auto-20px');
108
+ expect(result).toEqual(['auto', '20px']);
109
+ });
110
+
111
+ test('should handle inherit value', () => {
112
+ const result = parseMultiValue('inherit');
113
+ expect(result).toEqual(['inherit']);
114
+ });
115
+
116
+ test('should filter empty values', () => {
117
+ const result = parseMultiValue('20px 40px');
118
+ expect(result).toEqual(['20px', '40px']);
119
+ });
120
+ });
121
+
122
+ describe('scaleValue', () => {
123
+ test('should scale pixel value', () => {
124
+ const result = scaleValue('67px', 16, 0.88);
125
+ expect(result).toBe('61px');
126
+ });
127
+
128
+ test('should scale rem value', () => {
129
+ const result = scaleValue('2rem', 16, 0.75);
130
+ // 2 + (2 - 16) * (0.75 - 1) = 2 + (-14) * (-0.25) = 2 + 3.5 = 5.5 -> 6
131
+ expect(result).toBe('6rem');
132
+ });
133
+
134
+ test('should return null for value without unit', () => {
135
+ const result = scaleValue('0', 16, 0.88);
136
+ expect(result).toBeNull();
137
+ });
138
+
139
+ test('should return null for auto', () => {
140
+ const result = scaleValue('auto', 16, 0.88);
141
+ expect(result).toBeNull();
142
+ });
143
+
144
+ test('should scale percentage value', () => {
145
+ const result = scaleValue('100%', 16, 0.5);
146
+ // 100 + (100 - 16) * (0.5 - 1) = 100 + 84 * (-0.5) = 100 - 42 = 58
147
+ expect(result).toBe('58%');
148
+ });
149
+ });
150
+
151
+ describe('scalePropertyValue', () => {
152
+ test('should scale single value', () => {
153
+ const result = scalePropertyValue('67px', 16, 0.88);
154
+ expect(result).toBe('61px');
155
+ });
156
+
157
+ test('should scale multi-value property', () => {
158
+ const result = scalePropertyValue('20px 40px', 16, 0.75);
159
+ // 20 + (20 - 16) * (0.75 - 1) = 20 + 4 * (-0.25) = 19
160
+ // 40 + (40 - 16) * (0.75 - 1) = 40 + 24 * (-0.25) = 34
161
+ expect(result).toBe('19px 34px');
162
+ });
163
+
164
+ test('should scale hyphen-separated values', () => {
165
+ const result = scalePropertyValue('0-80px', 16, 0.5);
166
+ // 0 stays 0, 80 + (80 - 16) * (0.5 - 1) = 80 - 32 = 48
167
+ expect(result).toBe('0 48px');
168
+ });
169
+
170
+ test('should keep unitless values as-is', () => {
171
+ const result = scalePropertyValue('0 auto 20px', 16, 0.75);
172
+ expect(result).toContain('0');
173
+ expect(result).toContain('auto');
174
+ });
175
+
176
+ test('should return null for empty string', () => {
177
+ const result = scalePropertyValue('', 16, 0.88);
178
+ expect(result).toBeNull();
179
+ });
180
+
181
+ test('should handle inherit value', () => {
182
+ const result = scalePropertyValue('inherit', 16, 0.88);
183
+ expect(result).toBe('inherit');
184
+ });
185
+ });
186
+
187
+ describe('getResponsiveValues', () => {
188
+ test('should return only base value when disabled', () => {
189
+ const scales: ResponsiveScales = { enabled: false, baseReference: 16 };
190
+ const result = getResponsiveValues('20px', 'fontSize', scales);
191
+ expect(result).toEqual({ base: '20px' });
192
+ });
193
+
194
+ test('should calculate tablet and mobile values', () => {
195
+ const scales = { ...DEFAULT_RESPONSIVE_SCALES, enabled: true };
196
+ const result = getResponsiveValues('67px', 'fontSize', scales);
197
+ expect(result.base).toBe('67px');
198
+ expect(result.tablet).toBe('61px'); // 0.88 scale
199
+ expect(result.mobile).toBe('54px'); // 0.75 scale
200
+ });
201
+
202
+ test('should handle padding property', () => {
203
+ const scales = { ...DEFAULT_RESPONSIVE_SCALES, enabled: true };
204
+ const result = getResponsiveValues('20px', 'padding', scales);
205
+ expect(result.base).toBe('20px');
206
+ expect(result.tablet).toBe('19px'); // 0.75 scale
207
+ expect(result.mobile).toBe('18px'); // 0.5 scale
208
+ });
209
+
210
+ test('should handle margin property', () => {
211
+ const scales = { ...DEFAULT_RESPONSIVE_SCALES, enabled: true };
212
+ const result = getResponsiveValues('20px', 'margin', scales);
213
+ expect(result.base).toBe('20px');
214
+ expect(result.tablet).toBe('19px'); // 0.7 scale
215
+ expect(result.mobile).toBe('18px'); // 0.45 scale
216
+ });
217
+
218
+ test('should skip breakpoint if scale not configured', () => {
219
+ const scales: ResponsiveScales = {
220
+ enabled: true,
221
+ baseReference: 16,
222
+ fontSize: { tablet: 0.88 }
223
+ };
224
+ const result = getResponsiveValues('20px', 'fontSize', scales);
225
+ expect(result.base).toBe('20px');
226
+ expect(result.tablet).toBeDefined();
227
+ expect(result.mobile).toBeUndefined();
228
+ });
229
+ });
230
+
231
+ describe('DEFAULT_RESPONSIVE_SCALES', () => {
232
+ test('should be disabled by default', () => {
233
+ expect(DEFAULT_RESPONSIVE_SCALES.enabled).toBe(false);
234
+ });
235
+
236
+ test('should have base reference of 16', () => {
237
+ expect(DEFAULT_RESPONSIVE_SCALES.baseReference).toBe(16);
238
+ });
239
+
240
+ test('should have fontSize scales', () => {
241
+ expect(DEFAULT_RESPONSIVE_SCALES.fontSize).toEqual({
242
+ tablet: 0.88,
243
+ mobile: 0.75,
244
+ });
245
+ });
246
+
247
+ test('should have padding scales', () => {
248
+ expect(DEFAULT_RESPONSIVE_SCALES.padding).toEqual({
249
+ tablet: 0.75,
250
+ mobile: 0.5,
251
+ });
252
+ });
253
+
254
+ test('should have margin scales', () => {
255
+ expect(DEFAULT_RESPONSIVE_SCALES.margin).toEqual({
256
+ tablet: 0.7,
257
+ mobile: 0.45,
258
+ });
259
+ });
260
+
261
+ test('should have gap scales', () => {
262
+ expect(DEFAULT_RESPONSIVE_SCALES.gap).toEqual({
263
+ tablet: 0.65,
264
+ mobile: 0.4,
265
+ });
266
+ });
267
+ });
268
+ });
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Responsive Scaling Calculator
3
+ * Automatically calculates responsive values for different breakpoints
4
+ * using configured scale multipliers
5
+ */
6
+
7
+ export interface ResponsiveScales {
8
+ enabled: boolean;
9
+ baseReference: number;
10
+ fontSize?: {
11
+ tablet?: number;
12
+ mobile?: number;
13
+ };
14
+ padding?: {
15
+ tablet?: number;
16
+ mobile?: number;
17
+ };
18
+ margin?: {
19
+ tablet?: number;
20
+ mobile?: number;
21
+ };
22
+ gap?: {
23
+ tablet?: number;
24
+ mobile?: number;
25
+ };
26
+ [key: string]: any;
27
+ }
28
+
29
+ export type CSSPropertyType = 'fontSize' | 'padding' | 'margin' | 'gap' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft' | 'marginTop' | 'marginRight' | 'marginBottom' | 'marginLeft' | 'rowGap' | 'columnGap';
30
+
31
+ /**
32
+ * Map CSS property to its scale category
33
+ * e.g., paddingTop -> padding, marginRight -> margin
34
+ */
35
+ function getScaleCategory(property: CSSPropertyType): keyof ResponsiveScales | null {
36
+ if (property === 'fontSize') return 'fontSize';
37
+ if (property.startsWith('padding') || property === 'paddingInline' || property === 'paddingBlock') return 'padding';
38
+ if (property.startsWith('margin') || property === 'marginInline' || property === 'marginBlock') return 'margin';
39
+ if (property === 'gap' || property === 'rowGap' || property === 'columnGap') return 'gap';
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Extract numeric value and unit from a CSS value string
45
+ * e.g., "67px" -> { value: 67, unit: "px" }
46
+ */
47
+ function parseValue(valueStr: string): { value: number; unit: string } | null {
48
+ const match = valueStr.trim().match(/^([\d.]+)(px|rem|em|%|pt)$/);
49
+ if (!match) return null;
50
+ return {
51
+ value: parseFloat(match[1]),
52
+ unit: match[2],
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Calculate responsive value using the formula:
58
+ * responsive_value = base_value + (base_value - base_reference) * (scale - 1)
59
+ */
60
+ export function calculateResponsiveValue(
61
+ baseValue: number,
62
+ baseReference: number,
63
+ scale: number
64
+ ): number {
65
+ const scaled = baseValue + (baseValue - baseReference) * (scale - 1);
66
+ return Math.round(scaled);
67
+ }
68
+
69
+ /**
70
+ * Get the scale multiplier for a specific property and breakpoint
71
+ */
72
+ export function getScaleMultiplier(
73
+ scales: ResponsiveScales,
74
+ property: CSSPropertyType,
75
+ breakpoint: 'tablet' | 'mobile'
76
+ ): number | null {
77
+ const category = getScaleCategory(property);
78
+ if (!category || !scales[category]) return null;
79
+
80
+ const scaleConfig = scales[category] as Record<string, number> | undefined;
81
+ return scaleConfig?.[breakpoint] ?? null;
82
+ }
83
+
84
+ /**
85
+ * Parse multi-value CSS property (e.g., "20px 40px" for padding)
86
+ * Handles both space-separated ("20px 40px") and hyphen-separated ("20px-40px") formats
87
+ * Returns array of individual values
88
+ */
89
+ export function parseMultiValue(valueStr: string): string[] {
90
+ // First, convert hyphen separators to spaces (for class names like "p-0-80px" → "0-80px")
91
+ // But only convert hyphens between values (digit/px followed by hyphen followed by digit/px)
92
+ const normalized = valueStr.replace(/-(?=\d|auto|inherit|initial|unset)/g, ' ');
93
+ return normalized.trim().split(/\s+/).filter(v => v.length > 0);
94
+ }
95
+
96
+ /**
97
+ * Scale a single CSS value using the responsive scale
98
+ * Returns null if the value cannot be parsed (no unit)
99
+ */
100
+ export function scaleValue(
101
+ valueStr: string,
102
+ baseReference: number,
103
+ scale: number
104
+ ): string | null {
105
+ const parsed = parseValue(valueStr);
106
+ if (!parsed) return null;
107
+
108
+ const scaledValue = calculateResponsiveValue(parsed.value, baseReference, scale);
109
+ return `${scaledValue}${parsed.unit}`;
110
+ }
111
+
112
+ /**
113
+ * Scale a potentially multi-value CSS property
114
+ * e.g., "20px 40px" -> "18px 36px" (both values scaled independently)
115
+ * Unitless values (0, auto, inherit) are kept as-is
116
+ */
117
+ export function scalePropertyValue(
118
+ valueStr: string,
119
+ baseReference: number,
120
+ scale: number
121
+ ): string | null {
122
+ const parts = parseMultiValue(valueStr);
123
+ if (parts.length === 0) return null;
124
+
125
+ const scaledParts = parts.map(part => {
126
+ // Try to scale the value
127
+ const scaled = scaleValue(part, baseReference, scale);
128
+ if (scaled !== null) {
129
+ return scaled;
130
+ }
131
+ // If it can't be scaled, keep it as-is (for unitless values like 0, auto, inherit)
132
+ return part;
133
+ });
134
+
135
+ return scaledParts.join(' ');
136
+ }
137
+
138
+ /**
139
+ * Get responsive values for all breakpoints
140
+ * Returns object with calculated values for each breakpoint
141
+ */
142
+ export function getResponsiveValues(
143
+ baseValue: string,
144
+ property: CSSPropertyType,
145
+ scales: ResponsiveScales
146
+ ): Record<string, string | null> {
147
+ if (!scales.enabled) {
148
+ return { base: baseValue };
149
+ }
150
+
151
+ const result: Record<string, string | null> = {
152
+ base: baseValue,
153
+ };
154
+
155
+ const baseRef = scales.baseReference || 16;
156
+
157
+ // Calculate tablet value
158
+ const tabletScale = getScaleMultiplier(scales, property, 'tablet');
159
+ if (tabletScale !== null) {
160
+ result.tablet = scalePropertyValue(baseValue, baseRef, tabletScale);
161
+ }
162
+
163
+ // Calculate mobile value
164
+ const mobileScale = getScaleMultiplier(scales, property, 'mobile');
165
+ if (mobileScale !== null) {
166
+ result.mobile = scalePropertyValue(baseValue, baseRef, mobileScale);
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ /**
173
+ * Default responsive scales configuration
174
+ */
175
+ export const DEFAULT_RESPONSIVE_SCALES: ResponsiveScales = {
176
+ enabled: false,
177
+ baseReference: 16,
178
+ fontSize: {
179
+ tablet: 0.88,
180
+ mobile: 0.75,
181
+ },
182
+ padding: {
183
+ tablet: 0.75,
184
+ mobile: 0.5,
185
+ },
186
+ margin: {
187
+ tablet: 0.7,
188
+ mobile: 0.45,
189
+ },
190
+ gap: {
191
+ tablet: 0.65,
192
+ mobile: 0.4,
193
+ },
194
+ };