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.
- package/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- 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
|
+
};
|