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,102 @@
1
+ /**
2
+ * Utilities for working with styles in ComponentNode structures
3
+ * Provides helper functions for applying and merging styles based on node type
4
+ */
5
+
6
+ import type { ComponentNode, HtmlNode, ComponentInstanceNode, StyleValue, ResponsiveStyleObject } from './types';
7
+ import { isComponentNode, isHtmlNode, isEmbedNode } from './nodeUtils';
8
+ import { isResponsiveStyle } from './styleUtils';
9
+
10
+ /**
11
+ * Apply styles to a ComponentNode based on its type
12
+ * - HTML nodes: styles go at top level (node.style)
13
+ * - Component instances: styles go in props.style
14
+ */
15
+ export function applyStylesToNode(
16
+ node: ComponentNode,
17
+ styles: StyleValue | Record<string, string | number> | null | undefined,
18
+ viewportWidth?: number
19
+ ): ComponentNode {
20
+ if (!styles) return node;
21
+
22
+ if (isComponentNode(node)) {
23
+ // Component instance: put styles in props.style
24
+ if (!node.props) {
25
+ node.props = {};
26
+ }
27
+
28
+ node.props.style = { ...(node.props.style || {}), ...styles };
29
+ } else if (isHtmlNode(node) || isEmbedNode(node)) {
30
+ // HTML node and Embed node: put styles at top level
31
+ node.style = styles as StyleValue;
32
+ }
33
+
34
+ return node;
35
+ }
36
+
37
+ /**
38
+ * Merge instance styles with structure styles for a ComponentNode
39
+ * Instance styles override structure styles
40
+ */
41
+ export function mergeNodeStyles(
42
+ node: ComponentNode,
43
+ instanceStyles: StyleValue | Record<string, string | number> | null | undefined,
44
+ viewportWidth?: number
45
+ ): ComponentNode {
46
+ if (!instanceStyles) return node;
47
+
48
+ if (isHtmlNode(node) || isEmbedNode(node)) {
49
+ // For HTML nodes and Embed nodes: merge instance styles with existing top-level styles
50
+ const existingStyle = node.style;
51
+ if (existingStyle && typeof existingStyle === 'object') {
52
+ node.style = {
53
+ ...(existingStyle as Record<string, any>),
54
+ ...(instanceStyles as Record<string, any>)
55
+ } as StyleValue;
56
+ } else {
57
+ node.style = instanceStyles as StyleValue;
58
+ }
59
+ } else if (isComponentNode(node)) {
60
+ // For component instances: merge into props.style
61
+ if (!node.props) {
62
+ node.props = {};
63
+ }
64
+ const existingStyle = node.props.style;
65
+ if (existingStyle && typeof existingStyle === 'object') {
66
+ node.props.style = {
67
+ ...(existingStyle as Record<string, any>),
68
+ ...(instanceStyles as Record<string, any>)
69
+ };
70
+ } else {
71
+ node.props.style = instanceStyles;
72
+ }
73
+ }
74
+
75
+ return node;
76
+ }
77
+
78
+ /**
79
+ * Extract styles from a ComponentNode based on its type
80
+ * Returns a flat style object ready for React props
81
+ */
82
+ export function extractStylesFromNode(
83
+ node: ComponentNode,
84
+ viewportWidth?: number
85
+ ): Record<string, string | number> {
86
+ if (isComponentNode(node)) {
87
+ // Component instance: styles are in props.style
88
+ const style = node.props?.style;
89
+ if (!style) return {};
90
+
91
+ return style as Record<string, string | number>;
92
+ } else if (isHtmlNode(node) || isEmbedNode(node)) {
93
+ // HTML node and Embed node: styles are at top level
94
+ const style = node.style;
95
+ if (!style) return {};
96
+
97
+ return style as Record<string, string | number>;
98
+ }
99
+
100
+ return {};
101
+ }
102
+
@@ -0,0 +1,238 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { isResponsiveStyle, isStyleObject, isStyleValue } from './styleUtils';
3
+
4
+ /**
5
+ * styleUtils Tests
6
+ * Tests style type guards and utilities
7
+ */
8
+
9
+ describe('styleUtils', () => {
10
+ describe('isResponsiveStyle', () => {
11
+ test('should return true for object with base key', () => {
12
+ expect(isResponsiveStyle({ base: { color: 'red' } })).toBe(true);
13
+ });
14
+
15
+ test('should return true for object with tablet key', () => {
16
+ expect(isResponsiveStyle({ tablet: { color: 'blue' } })).toBe(true);
17
+ });
18
+
19
+ test('should return true for object with mobile key', () => {
20
+ expect(isResponsiveStyle({ mobile: { color: 'green' } })).toBe(true);
21
+ });
22
+
23
+ test('should return true for object with multiple responsive keys', () => {
24
+ expect(isResponsiveStyle({
25
+ base: { color: 'red' },
26
+ tablet: { color: 'blue' },
27
+ mobile: { color: 'green' },
28
+ })).toBe(true);
29
+ });
30
+
31
+ test('should return false for regular style object', () => {
32
+ expect(isResponsiveStyle({ color: 'red', fontSize: '16px' })).toBe(false);
33
+ });
34
+
35
+ test('should return false for null', () => {
36
+ expect(isResponsiveStyle(null)).toBe(false);
37
+ });
38
+
39
+ test('should return false for undefined', () => {
40
+ expect(isResponsiveStyle(undefined)).toBe(false);
41
+ });
42
+
43
+ test('should return false for string', () => {
44
+ expect(isResponsiveStyle('red')).toBe(false);
45
+ });
46
+
47
+ test('should return false for number', () => {
48
+ expect(isResponsiveStyle(16)).toBe(false);
49
+ });
50
+
51
+ test('should return false for array', () => {
52
+ expect(isResponsiveStyle([])).toBe(false);
53
+ expect(isResponsiveStyle([{ color: 'red' }])).toBe(false);
54
+ });
55
+
56
+ test('should return false for empty object', () => {
57
+ expect(isResponsiveStyle({})).toBe(false);
58
+ });
59
+
60
+ test('should handle object with base key and other properties', () => {
61
+ expect(isResponsiveStyle({
62
+ base: { color: 'red' },
63
+ customProp: 'value',
64
+ })).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe('isStyleObject', () => {
69
+ test('should return true for simple style object', () => {
70
+ expect(isStyleObject({ color: 'red', fontSize: '16px' })).toBe(true);
71
+ });
72
+
73
+ test('should return true for style object with string values', () => {
74
+ expect(isStyleObject({
75
+ color: 'red',
76
+ backgroundColor: 'blue',
77
+ padding: '10px',
78
+ })).toBe(true);
79
+ });
80
+
81
+ test('should return true for style object with number values', () => {
82
+ expect(isStyleObject({
83
+ width: 100,
84
+ height: 200,
85
+ opacity: 0.5,
86
+ })).toBe(true);
87
+ });
88
+
89
+ test('should return true for style object with mixed string and number', () => {
90
+ expect(isStyleObject({
91
+ color: 'red',
92
+ width: 100,
93
+ fontSize: '16px',
94
+ opacity: 0.8,
95
+ })).toBe(true);
96
+ });
97
+
98
+ test('should return true for style object with _mapping property', () => {
99
+ expect(isStyleObject({
100
+ color: { _mapping: 'theme.colors.primary' },
101
+ })).toBe(true);
102
+ });
103
+
104
+ test('should return false for responsive style object', () => {
105
+ expect(isStyleObject({
106
+ base: { color: 'red' },
107
+ tablet: { color: 'blue' },
108
+ })).toBe(false);
109
+ });
110
+
111
+ test('should return false for object with only base key', () => {
112
+ expect(isStyleObject({ base: { color: 'red' } })).toBe(false);
113
+ });
114
+
115
+ test('should return false for null', () => {
116
+ expect(isStyleObject(null)).toBe(false);
117
+ });
118
+
119
+ test('should return false for undefined', () => {
120
+ expect(isStyleObject(undefined)).toBe(false);
121
+ });
122
+
123
+ test('should return false for string', () => {
124
+ expect(isStyleObject('red')).toBe(false);
125
+ });
126
+
127
+ test('should return false for number', () => {
128
+ expect(isStyleObject(100)).toBe(false);
129
+ });
130
+
131
+ test('should return false for array', () => {
132
+ expect(isStyleObject([])).toBe(false);
133
+ expect(isStyleObject([{ color: 'red' }])).toBe(false);
134
+ });
135
+
136
+ test('should return false for empty object', () => {
137
+ expect(isStyleObject({})).toBe(true); // Empty object is technically a valid StyleObject
138
+ });
139
+
140
+ test('should return false for object with invalid value types', () => {
141
+ expect(isStyleObject({
142
+ color: 'red',
143
+ invalid: [],
144
+ })).toBe(false);
145
+ });
146
+
147
+ test('should return false for object with nested objects without _mapping', () => {
148
+ expect(isStyleObject({
149
+ color: 'red',
150
+ nested: { prop: 'value' },
151
+ })).toBe(false);
152
+ });
153
+
154
+ test('should handle complex StyleMapping objects', () => {
155
+ expect(isStyleObject({
156
+ color: { _mapping: 'colors.primary' },
157
+ fontSize: '16px',
158
+ width: 100,
159
+ })).toBe(true);
160
+ });
161
+ });
162
+
163
+ describe('isStyleValue', () => {
164
+ test('should return true for simple style object', () => {
165
+ expect(isStyleValue({ color: 'red', fontSize: '16px' })).toBe(true);
166
+ });
167
+
168
+ test('should return true for responsive style object', () => {
169
+ expect(isStyleValue({
170
+ base: { color: 'red' },
171
+ tablet: { color: 'blue' },
172
+ })).toBe(true);
173
+ });
174
+
175
+ test('should return true for object with base key', () => {
176
+ expect(isStyleValue({ base: { color: 'red' } })).toBe(true);
177
+ });
178
+
179
+ test('should return true for object with tablet key', () => {
180
+ expect(isStyleValue({ tablet: { color: 'blue' } })).toBe(true);
181
+ });
182
+
183
+ test('should return true for object with mobile key', () => {
184
+ expect(isStyleValue({ mobile: { color: 'green' } })).toBe(true);
185
+ });
186
+
187
+ test('should return true for style object with numbers', () => {
188
+ expect(isStyleValue({ width: 100, height: 200 })).toBe(true);
189
+ });
190
+
191
+ test('should return true for style object with _mapping', () => {
192
+ expect(isStyleValue({ color: { _mapping: 'theme.primary' } })).toBe(true);
193
+ });
194
+
195
+ test('should return false for null', () => {
196
+ expect(isStyleValue(null)).toBe(false);
197
+ });
198
+
199
+ test('should return false for undefined', () => {
200
+ expect(isStyleValue(undefined)).toBe(false);
201
+ });
202
+
203
+ test('should return false for string', () => {
204
+ expect(isStyleValue('red')).toBe(false);
205
+ });
206
+
207
+ test('should return false for number', () => {
208
+ expect(isStyleValue(100)).toBe(false);
209
+ });
210
+
211
+ test('should return false for array', () => {
212
+ expect(isStyleValue([])).toBe(false);
213
+ expect(isStyleValue([{ color: 'red' }])).toBe(false);
214
+ });
215
+
216
+ test('should return true for empty object', () => {
217
+ expect(isStyleValue({})).toBe(true);
218
+ });
219
+
220
+ test('should handle complex responsive styles', () => {
221
+ expect(isStyleValue({
222
+ base: { color: 'red', fontSize: '18px' },
223
+ tablet: { color: 'blue', fontSize: '16px' },
224
+ mobile: { color: 'green', fontSize: '14px' },
225
+ })).toBe(true);
226
+ });
227
+
228
+ test('should handle complex style objects', () => {
229
+ expect(isStyleValue({
230
+ color: 'red',
231
+ fontSize: '16px',
232
+ width: 100,
233
+ height: 200,
234
+ opacity: 0.8,
235
+ })).toBe(true);
236
+ });
237
+ });
238
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared Style Utilities
3
+ */
4
+
5
+ import type { ResponsiveStyleObject, StyleValue, StyleObject } from './types';
6
+
7
+ /**
8
+ * Check if a style value is a responsive style object
9
+ */
10
+ export function isResponsiveStyle(style: StyleValue): style is ResponsiveStyleObject {
11
+ if (!style || typeof style !== 'object' || Array.isArray(style)) {
12
+ return false;
13
+ }
14
+
15
+ // Check if it has base, tablet, or mobile keys (indicating responsive format)
16
+ return 'base' in style || 'tablet' in style || 'mobile' in style;
17
+ }
18
+
19
+ /**
20
+ * Type guard to check if a value is a StyleObject
21
+ */
22
+ export function isStyleObject(style: unknown): style is StyleObject {
23
+ if (!style || typeof style !== 'object' || Array.isArray(style)) {
24
+ return false;
25
+ }
26
+
27
+ // StyleObject is a record of string keys to string | number | StyleMapping values
28
+ // Check that it's not a ResponsiveStyleObject (which has base/tablet/mobile keys)
29
+ // Use proper type narrowing instead of assertion
30
+ if (!('base' in style) && !('tablet' in style) && !('mobile' in style)) {
31
+ // All values should be string, number, or objects with _mapping property
32
+ const obj = style as Record<string, unknown>;
33
+ return Object.values(obj).every(value => {
34
+ if (typeof value === 'string' || typeof value === 'number') {
35
+ return true;
36
+ }
37
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
38
+ return '_mapping' in value; // StyleMapping
39
+ }
40
+ return false;
41
+ });
42
+ }
43
+
44
+ return false; // This is a ResponsiveStyleObject, not a StyleObject
45
+ }
46
+
47
+ /**
48
+ * Type guard to check if a value is a StyleValue (either StyleObject or ResponsiveStyleObject)
49
+ */
50
+ export function isStyleValue(style: unknown): style is StyleValue {
51
+ if (!style || typeof style !== 'object' || Array.isArray(style)) {
52
+ return false;
53
+ }
54
+
55
+ // Check if it's a ResponsiveStyleObject
56
+ if (isResponsiveStyle(style)) {
57
+ return true;
58
+ }
59
+
60
+ // Check if it's a StyleObject
61
+ return isStyleObject(style);
62
+ }
63
+
@@ -0,0 +1,113 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ DEFAULT_PROPS_PANEL_SPACING,
4
+ DEFAULT_INPUT_STYLE,
5
+ DEFAULT_TEXTAREA_STYLE,
6
+ DEFAULT_PROPS_PANEL_COLORS,
7
+ type PropsPanelColors,
8
+ type PropsPanelSpacing,
9
+ type PropsPanelInputStyle,
10
+ } from './themeDefaults';
11
+
12
+ describe('themeDefaults', () => {
13
+ describe('DEFAULT_PROPS_PANEL_SPACING', () => {
14
+ test('should have tight spacing of 2px', () => {
15
+ expect(DEFAULT_PROPS_PANEL_SPACING.tight).toBe('2px');
16
+ });
17
+
18
+ test('should have normal spacing of 4px', () => {
19
+ expect(DEFAULT_PROPS_PANEL_SPACING.normal).toBe('4px');
20
+ });
21
+
22
+ test('should have loose spacing of 8px', () => {
23
+ expect(DEFAULT_PROPS_PANEL_SPACING.loose).toBe('8px');
24
+ });
25
+ });
26
+
27
+ describe('DEFAULT_INPUT_STYLE', () => {
28
+ test('should have Consolas monospace font', () => {
29
+ expect(DEFAULT_INPUT_STYLE.fontFamily).toBe('Consolas, monospace');
30
+ });
31
+
32
+ test('should have 12px font size', () => {
33
+ expect(DEFAULT_INPUT_STYLE.fontSize).toBe('12px');
34
+ });
35
+
36
+ test('should have 4px 6px padding', () => {
37
+ expect(DEFAULT_INPUT_STYLE.padding).toBe('4px 6px');
38
+ });
39
+
40
+ test('should have 3px border radius', () => {
41
+ expect(DEFAULT_INPUT_STYLE.borderRadius).toBe('3px');
42
+ });
43
+
44
+ test('should have no outline', () => {
45
+ expect(DEFAULT_INPUT_STYLE.outline).toBe('none');
46
+ });
47
+ });
48
+
49
+ describe('DEFAULT_TEXTAREA_STYLE', () => {
50
+ test('should inherit font family', () => {
51
+ expect(DEFAULT_TEXTAREA_STYLE.fontFamily).toBe('inherit');
52
+ });
53
+
54
+ test('should have 13px font size', () => {
55
+ expect(DEFAULT_TEXTAREA_STYLE.fontSize).toBe('13px');
56
+ });
57
+
58
+ test('should have 4px 6px padding', () => {
59
+ expect(DEFAULT_TEXTAREA_STYLE.padding).toBe('4px 6px');
60
+ });
61
+
62
+ test('should have 3px border radius', () => {
63
+ expect(DEFAULT_TEXTAREA_STYLE.borderRadius).toBe('3px');
64
+ });
65
+
66
+ test('should have no outline', () => {
67
+ expect(DEFAULT_TEXTAREA_STYLE.outline).toBe('none');
68
+ });
69
+ });
70
+
71
+ describe('DEFAULT_PROPS_PANEL_COLORS', () => {
72
+ test('should have dark theme background colors', () => {
73
+ expect(DEFAULT_PROPS_PANEL_COLORS.background).toBe('#1e1e1e');
74
+ expect(DEFAULT_PROPS_PANEL_COLORS.backgroundSecondary).toBe('#2d2d2d');
75
+ expect(DEFAULT_PROPS_PANEL_COLORS.backgroundTertiary).toBe('#252525');
76
+ });
77
+
78
+ test('should have border colors', () => {
79
+ expect(DEFAULT_PROPS_PANEL_COLORS.border).toBe('#333333');
80
+ expect(DEFAULT_PROPS_PANEL_COLORS.borderSecondary).toBe('#444444');
81
+ });
82
+
83
+ test('should have text colors', () => {
84
+ expect(DEFAULT_PROPS_PANEL_COLORS.text).toBe('#cccccc');
85
+ expect(DEFAULT_PROPS_PANEL_COLORS.textSecondary).toBe('#cccccc');
86
+ expect(DEFAULT_PROPS_PANEL_COLORS.textMuted).toBe('#888888');
87
+ });
88
+
89
+ test('should have code syntax colors', () => {
90
+ expect(DEFAULT_PROPS_PANEL_COLORS.codeString).toBe('#ce9178');
91
+ expect(DEFAULT_PROPS_PANEL_COLORS.codeNumber).toBe('#b5cea8');
92
+ expect(DEFAULT_PROPS_PANEL_COLORS.codeKey).toBe('#9cdcfe');
93
+ expect(DEFAULT_PROPS_PANEL_COLORS.codeType).toBe('#4ec9b0');
94
+ });
95
+
96
+ test('should have button colors', () => {
97
+ expect(DEFAULT_PROPS_PANEL_COLORS.buttonPrimary).toBe('#007acc');
98
+ expect(DEFAULT_PROPS_PANEL_COLORS.buttonPrimaryHover).toBe('#0098ff');
99
+ expect(DEFAULT_PROPS_PANEL_COLORS.buttonSecondary).toBe('#007acc');
100
+ expect(DEFAULT_PROPS_PANEL_COLORS.buttonDanger).toBe('#f48771');
101
+ expect(DEFAULT_PROPS_PANEL_COLORS.buttonDangerHover).toBe('#ff6b6b');
102
+ });
103
+
104
+ test('should have input colors', () => {
105
+ expect(DEFAULT_PROPS_PANEL_COLORS.inputBackground).toBe('#1e1e1e');
106
+ expect(DEFAULT_PROPS_PANEL_COLORS.inputBorder).toBe('#444444');
107
+ });
108
+
109
+ test('should have hover background color', () => {
110
+ expect(DEFAULT_PROPS_PANEL_COLORS.hoverBackground).toBe('#2a2d2e');
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Shared theme color defaults
3
+ * Used by PropsPanel and StyleEditor to avoid duplication
4
+ */
5
+
6
+ export interface PropsPanelColors {
7
+ background: string;
8
+ backgroundSecondary: string;
9
+ backgroundTertiary: string;
10
+ border: string;
11
+ borderSecondary: string;
12
+ text: string;
13
+ textSecondary: string;
14
+ textMuted: string;
15
+ codeString: string;
16
+ codeNumber: string;
17
+ codeKey: string;
18
+ codeType: string;
19
+ buttonPrimary: string;
20
+ buttonPrimaryHover: string;
21
+ buttonSecondary: string;
22
+ buttonDanger: string;
23
+ buttonDangerHover: string;
24
+ inputBackground: string;
25
+ inputBorder: string;
26
+ hoverBackground: string;
27
+ }
28
+
29
+ /**
30
+ * Spacing constants for props panel components
31
+ */
32
+ export interface PropsPanelSpacing {
33
+ tight: string; // 2px - for label:colon:input gaps
34
+ normal: string; // 4px - for standard gaps
35
+ loose: string; // 8px - for larger gaps
36
+ }
37
+
38
+ export const DEFAULT_PROPS_PANEL_SPACING: PropsPanelSpacing = {
39
+ tight: '2px',
40
+ normal: '4px',
41
+ loose: '8px',
42
+ };
43
+
44
+ /**
45
+ * Base input style properties
46
+ */
47
+ export interface PropsPanelInputStyle {
48
+ fontFamily: string;
49
+ fontSize: string;
50
+ padding: string;
51
+ borderRadius: string;
52
+ outline: string;
53
+ border?: string;
54
+ }
55
+
56
+ /**
57
+ * Base input style defaults
58
+ */
59
+ export const DEFAULT_INPUT_STYLE: Omit<PropsPanelInputStyle, 'border' | 'color' | 'background'> = {
60
+ fontFamily: 'Consolas, monospace',
61
+ fontSize: '12px',
62
+ padding: '4px 6px',
63
+ borderRadius: '3px',
64
+ outline: 'none',
65
+ };
66
+
67
+ /**
68
+ * Base textarea style defaults
69
+ */
70
+ export const DEFAULT_TEXTAREA_STYLE: Omit<PropsPanelInputStyle, 'border' | 'color' | 'background'> = {
71
+ fontFamily: 'inherit',
72
+ fontSize: '13px',
73
+ padding: '4px 6px',
74
+ borderRadius: '3px',
75
+ outline: 'none',
76
+ };
77
+
78
+ /**
79
+ * Default dark theme colors for props panel
80
+ */
81
+ export const DEFAULT_PROPS_PANEL_COLORS: PropsPanelColors = {
82
+ background: '#1e1e1e',
83
+ backgroundSecondary: '#2d2d2d',
84
+ backgroundTertiary: '#252525',
85
+ border: '#333333',
86
+ borderSecondary: '#444444',
87
+ text: '#cccccc',
88
+ textSecondary: '#cccccc',
89
+ textMuted: '#888888',
90
+ codeString: '#ce9178',
91
+ codeNumber: '#b5cea8',
92
+ codeKey: '#9cdcfe',
93
+ codeType: '#4ec9b0',
94
+ buttonPrimary: '#007acc',
95
+ buttonPrimaryHover: '#0098ff',
96
+ buttonSecondary: '#007acc',
97
+ buttonDanger: '#f48771',
98
+ buttonDangerHover: '#ff6b6b',
99
+ inputBackground: '#1e1e1e',
100
+ inputBorder: '#444444',
101
+ hoverBackground: '#2a2d2e',
102
+ };
103
+