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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants used across the application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Read PORT from environment variable, fallback to 3000
|
|
6
|
+
// Safe for browser environments where process is not defined
|
|
7
|
+
export const SERVER_PORT = typeof process !== 'undefined' && process.env?.PORT
|
|
8
|
+
? parseInt(process.env.PORT, 10)
|
|
9
|
+
: 3000;
|
|
10
|
+
|
|
11
|
+
export const API_ROUTES = {
|
|
12
|
+
PAGES: '/api/pages',
|
|
13
|
+
COMPONENTS: '/api/components',
|
|
14
|
+
YAML: '/api/yaml', // Legacy route name, now serves JSON
|
|
15
|
+
PAGE_DATA: '/api/page-data', // Returns parsed JSON for a specific page
|
|
16
|
+
COMPONENT_DATA: '/api/component-data', // Returns parsed JSON for a specific component
|
|
17
|
+
SAVE_PAGE: '/api/save-page',
|
|
18
|
+
SAVE_COMPONENT: '/api/save-component',
|
|
19
|
+
SAVE_COMPONENT_JS: '/api/save-component-js', // Save JavaScript to .js file
|
|
20
|
+
SAVE_COMPONENT_CSS: '/api/save-component-css', // Save CSS to .css file
|
|
21
|
+
COMPONENT_JS: '/api/component-js', // Get JavaScript from .js file
|
|
22
|
+
CONFIG: '/api/config', // Get project config
|
|
23
|
+
SAVE_CONFIG: '/api/save-config', // Save project config
|
|
24
|
+
SELECTION: '/api/selection', // Editor selection state (for AI integration)
|
|
25
|
+
// CMS API routes
|
|
26
|
+
CMS_COLLECTIONS: '/api/cms/collections', // List all CMS collections
|
|
27
|
+
CMS_BASE: '/api/cms', // Base path for CMS item operations
|
|
28
|
+
// Colors API routes
|
|
29
|
+
COLORS_CONFIG: '/api/colors-config', // Get full colors config
|
|
30
|
+
SAVE_COLORS: '/api/save-colors', // Save colors config
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export const HMR_ROUTE = '/hmr';
|
|
34
|
+
|
|
35
|
+
export const FILE_PATTERNS = {
|
|
36
|
+
PAGES: './pages',
|
|
37
|
+
COMPONENTS: './components',
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export const DEFAULT_TIMEOUT = 5000;
|
|
41
|
+
|
|
42
|
+
export const WEBSOCKET_STATES = {
|
|
43
|
+
CONNECTING: 0,
|
|
44
|
+
OPEN: 1,
|
|
45
|
+
CLOSING: 2,
|
|
46
|
+
CLOSED: 3,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
// Timeout constants
|
|
50
|
+
export const NOT_FOUND_TIMEOUT_MS = 300;
|
|
51
|
+
export const TAB_SWITCH_DELAY_MS = 100;
|
|
52
|
+
export const IFRAME_HIGHLIGHT_DELAY_MS = 100;
|
|
53
|
+
export const TREE_SCROLL_DELAY_MS = 200;
|
|
54
|
+
export const HOVER_HIGHLIGHT_DELAY_MS = 50; // Delay before highlighting tree item after expansion
|
|
55
|
+
|
|
56
|
+
// Server configuration
|
|
57
|
+
export const MAX_PORT_ATTEMPTS = 10;
|
|
58
|
+
|
|
59
|
+
// Message types for iframe-parent communication
|
|
60
|
+
export const IFRAME_MESSAGE_TYPES = {
|
|
61
|
+
DELETE_ELEMENT: 'DELETE_ELEMENT',
|
|
62
|
+
TOGGLE_SELECTING_MODE: 'TOGGLE_SELECTING_MODE',
|
|
63
|
+
SET_TAB: 'SET_TAB',
|
|
64
|
+
COPY_ELEMENT: 'COPY_ELEMENT',
|
|
65
|
+
PASTE_ELEMENT: 'PASTE_ELEMENT',
|
|
66
|
+
ARROW_UP: 'ARROW_UP',
|
|
67
|
+
ARROW_DOWN: 'ARROW_DOWN',
|
|
68
|
+
ARROW_LEFT: 'ARROW_LEFT',
|
|
69
|
+
ARROW_RIGHT: 'ARROW_RIGHT',
|
|
70
|
+
OPEN_COMMAND_PALETTE: 'OPEN_COMMAND_PALETTE',
|
|
71
|
+
EDIT_COMPONENT: 'EDIT_COMPONENT',
|
|
72
|
+
NAVIGATE_BACK: 'NAVIGATE_BACK',
|
|
73
|
+
TOGGLE_ADDING_STYLE: 'TOGGLE_ADDING_STYLE',
|
|
74
|
+
MOVE_ELEMENT_UP: 'MOVE_ELEMENT_UP',
|
|
75
|
+
MOVE_ELEMENT_DOWN: 'MOVE_ELEMENT_DOWN',
|
|
76
|
+
SELECTION_CHANGED: 'SELECTION_CHANGED',
|
|
77
|
+
CMS_CONTEXT_UPDATE: 'CMS_CONTEXT_UPDATE',
|
|
78
|
+
CMS_CONTEXT_REQUEST: 'CMS_CONTEXT_REQUEST',
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
// Component node type constants
|
|
82
|
+
export const NODE_TYPE = {
|
|
83
|
+
NODE: 'node',
|
|
84
|
+
COMPONENT: 'component',
|
|
85
|
+
SLOT: 'slot',
|
|
86
|
+
EMBED: 'embed',
|
|
87
|
+
OBJECT_LINK: 'object-link',
|
|
88
|
+
LOCALE_LIST: 'locale-list',
|
|
89
|
+
TEXT: 'text',
|
|
90
|
+
} as const;
|
|
91
|
+
|
|
92
|
+
export type NodeType = typeof NODE_TYPE[keyof typeof NODE_TYPE];
|
|
93
|
+
|
|
94
|
+
// Special path identifiers for component editing
|
|
95
|
+
export const SPECIAL_PATHS = {
|
|
96
|
+
COMPONENT_INTERFACE: 'component_interface',
|
|
97
|
+
COMPONENT_JAVASCRIPT: 'component_javascript',
|
|
98
|
+
COMPONENT_CSS: 'component_css',
|
|
99
|
+
STRUCTURE_STYLE: 'structure_style',
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
102
|
+
// Component type configuration
|
|
103
|
+
export interface ComponentTypeConfig {
|
|
104
|
+
id: string;
|
|
105
|
+
label: string;
|
|
106
|
+
color: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const DEFAULT_COMPONENT_TYPES: ComponentTypeConfig[] = [
|
|
110
|
+
{ id: 'ui', label: 'UI', color: '#3b82f6' },
|
|
111
|
+
{ id: 'layout', label: 'Layout', color: '#10b981' },
|
|
112
|
+
{ id: 'content', label: 'Content', color: '#f59e0b' },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
export const DEFAULT_ICON_COLOR = '#6b7280';
|
|
116
|
+
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CSS Generation Module
|
|
3
|
+
* Unified CSS generation logic for both editor and static render systems
|
|
4
|
+
* Used by both client and server to ensure consistent CSS output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { prefixToCSSProperty, propertyMap } from './utilityClassConfig';
|
|
8
|
+
import type { BreakpointConfig } from './breakpoints';
|
|
9
|
+
import { DEFAULT_BREAKPOINTS } from './breakpoints';
|
|
10
|
+
import type { ResponsiveScales } from './responsiveScaling';
|
|
11
|
+
import { scalePropertyValue } from './responsiveScaling';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Escape special characters in CSS class names using backslash notation
|
|
15
|
+
* Characters that have special meaning in CSS selectors need to be escaped
|
|
16
|
+
*/
|
|
17
|
+
function escapeCSSClassName(className: string): string {
|
|
18
|
+
// Escape special characters with backslash
|
|
19
|
+
// This includes: . # [ ] ( ) { } ; : , > + ~ etc.
|
|
20
|
+
return className.replace(/[.#[\](){};<>+~:,\s]/g, '\\$&');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Define all possible utility class rules
|
|
24
|
+
const utilityClassRules: Record<string, string> = {
|
|
25
|
+
// Display utilities (short forms from specialValueMappings)
|
|
26
|
+
f: 'display: flex;',
|
|
27
|
+
'fd-col': 'flex-direction: column;',
|
|
28
|
+
'fd-row': 'flex-direction: row;',
|
|
29
|
+
g: 'display: grid;',
|
|
30
|
+
b: 'display: block;',
|
|
31
|
+
i: 'display: inline;',
|
|
32
|
+
ib: 'display: inline-block;',
|
|
33
|
+
h: 'display: none;',
|
|
34
|
+
|
|
35
|
+
// Justify Content (short forms)
|
|
36
|
+
'jc-c': 'justify-content: center;',
|
|
37
|
+
'jc-s': 'justify-content: flex-start;',
|
|
38
|
+
'jc-e': 'justify-content: flex-end;',
|
|
39
|
+
'jc-b': 'justify-content: space-between;',
|
|
40
|
+
'jc-a': 'justify-content: space-around;',
|
|
41
|
+
|
|
42
|
+
// Align Items (short forms)
|
|
43
|
+
'ai-c': 'align-items: center;',
|
|
44
|
+
'ai-s': 'align-items: flex-start;',
|
|
45
|
+
'ai-e': 'align-items: flex-end;',
|
|
46
|
+
'ai-b': 'align-items: baseline;',
|
|
47
|
+
|
|
48
|
+
// Overflow (short forms)
|
|
49
|
+
'o-h': 'overflow: hidden;',
|
|
50
|
+
'o-a': 'overflow: auto;',
|
|
51
|
+
'o-s': 'overflow: scroll;',
|
|
52
|
+
'o-v': 'overflow: visible;',
|
|
53
|
+
|
|
54
|
+
// Cursor (full forms)
|
|
55
|
+
'cursor-pointer': 'cursor: pointer;',
|
|
56
|
+
'cursor-default': 'cursor: default;',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract CSS property and value from a utility class
|
|
61
|
+
* Returns { property: CSS property name, value: CSS value } or null if not a dynamic class
|
|
62
|
+
*/
|
|
63
|
+
function extractPropertyAndValue(className: string): { property: string; value: string } | null {
|
|
64
|
+
// Parse prefix-value pattern (e.g., "p-10px", "fs-48px")
|
|
65
|
+
const knownPrefixes = Object.keys(prefixToCSSProperty).sort((a, b) => b.length - a.length);
|
|
66
|
+
|
|
67
|
+
for (const knownPrefix of knownPrefixes) {
|
|
68
|
+
if (className.startsWith(knownPrefix + '-')) {
|
|
69
|
+
const classValue = className.substring(knownPrefix.length + 1);
|
|
70
|
+
const cssProp = prefixToCSSProperty[knownPrefix];
|
|
71
|
+
if (cssProp) {
|
|
72
|
+
return { property: cssProp, value: classValue };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate CSS rule for a utility class
|
|
82
|
+
* Handles dynamic classes like p-10px, m-20px, fs-48px, etc.
|
|
83
|
+
*/
|
|
84
|
+
export function generateRuleForClass(className: string): string | null {
|
|
85
|
+
// Check if it's a predefined rule
|
|
86
|
+
if (utilityClassRules[className]) {
|
|
87
|
+
return utilityClassRules[className];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Parse prefix-value pattern (e.g., "p-10px", "fs-48px", "p-92px-0", "bgc-background-light")
|
|
91
|
+
// Try to match known prefixes first by checking longest prefixes first
|
|
92
|
+
const knownPrefixes = Object.keys(prefixToCSSProperty).sort((a, b) => b.length - a.length);
|
|
93
|
+
|
|
94
|
+
let prefix = '';
|
|
95
|
+
let classValue = '';
|
|
96
|
+
|
|
97
|
+
for (const knownPrefix of knownPrefixes) {
|
|
98
|
+
if (className.startsWith(knownPrefix + '-')) {
|
|
99
|
+
prefix = knownPrefix;
|
|
100
|
+
classValue = className.substring(knownPrefix.length + 1); // +1 for the hyphen
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!prefix || !classValue) return null;
|
|
106
|
+
|
|
107
|
+
// Look up the CSS property from prefix
|
|
108
|
+
const cssProp = prefixToCSSProperty[prefix];
|
|
109
|
+
if (!cssProp) return null;
|
|
110
|
+
|
|
111
|
+
// Handle border with special syntax BEFORE hyphen conversion (e.g., b-1px-solid-text)
|
|
112
|
+
// This needs the original hyphenated format to parse correctly
|
|
113
|
+
if (prefix === 'b' && classValue.includes('-')) {
|
|
114
|
+
const parts = classValue.split('-');
|
|
115
|
+
const width = parts[0];
|
|
116
|
+
const borderStyle = parts[1] || 'solid';
|
|
117
|
+
const borderColor = parts[2] ? `var(--${parts[2]})` : 'currentColor';
|
|
118
|
+
return `border: ${width} ${borderStyle} ${borderColor};`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle border-radius with CSS variables (e.g., br-background)
|
|
122
|
+
if (prefix === 'br' && classValue.includes('-') && !classValue.match(/^\d+px$/)) {
|
|
123
|
+
return `border-radius: var(--${classValue});`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Convert hyphenated values back to spaces, but preserve negative numbers
|
|
127
|
+
// Handle cases like:
|
|
128
|
+
// - "92px-0" → "92px 0" (space between values)
|
|
129
|
+
// - "-0.015" → "-0.015" (negative number)
|
|
130
|
+
// - "0-5px" → "0 5px" (space between values)
|
|
131
|
+
// - "1px-solid" → "1px solid" (border shorthand)
|
|
132
|
+
let value = classValue;
|
|
133
|
+
|
|
134
|
+
// Pattern to match CSS values that should be preceded by a space instead of hyphen
|
|
135
|
+
// Includes: digits, auto, inherit, initial, unset, border style keywords, and CSS function names
|
|
136
|
+
const valuePattern = /\d|auto|inherit|initial|unset|solid|dashed|dotted|double|groove|ridge|inset|outset|none|hidden|minmax|repeat|clamp|calc|min|max|fit-content|var/;
|
|
137
|
+
|
|
138
|
+
// If value starts with hyphen, preserve it as a negative number
|
|
139
|
+
if (value.startsWith('-')) {
|
|
140
|
+
// For negative values, only replace hyphens that come after the first character
|
|
141
|
+
// This way "-0.015" stays as is, but "-10px-5px" becomes "-10px 5px"
|
|
142
|
+
value = value[0] + value.substring(1).replace(new RegExp(`-(?=${valuePattern.source})`, 'g'), ' ');
|
|
143
|
+
} else {
|
|
144
|
+
// For positive values, replace hyphens normally
|
|
145
|
+
value = value.replace(new RegExp(`-(?=${valuePattern.source})`, 'g'), ' ');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Convert 'p' suffix to '%' for percentage values (e.g., "50p" → "50%")
|
|
149
|
+
value = value.replace(/(\d+)p(?!\w)/g, '$1%');
|
|
150
|
+
|
|
151
|
+
// Handle special multi-property prefixes
|
|
152
|
+
if (prefix === 'px') {
|
|
153
|
+
// padding-left and padding-right
|
|
154
|
+
return `padding-left: ${value}; padding-right: ${value};`;
|
|
155
|
+
}
|
|
156
|
+
if (prefix === 'py') {
|
|
157
|
+
// padding-top and padding-bottom
|
|
158
|
+
return `padding-top: ${value}; padding-bottom: ${value};`;
|
|
159
|
+
}
|
|
160
|
+
if (prefix === 'mx') {
|
|
161
|
+
// margin-left and margin-right
|
|
162
|
+
return `margin-left: ${value}; margin-right: ${value};`;
|
|
163
|
+
}
|
|
164
|
+
if (prefix === 'my') {
|
|
165
|
+
// margin-top and margin-bottom
|
|
166
|
+
return `margin-top: ${value}; margin-bottom: ${value};`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle CSS variables for color properties
|
|
170
|
+
// For color (c), background-color (bgc), and border-color (bc), treat as CSS variables
|
|
171
|
+
// unless it's a hex color (#...) or RGB value
|
|
172
|
+
if ((prefix === 'bgc' || prefix === 'c' || prefix === 'bc')) {
|
|
173
|
+
// If it's not a hex color, RGB value, or pixel value, treat it as a CSS variable name
|
|
174
|
+
if (!value.startsWith('#') && !value.includes('rgb') && !value.includes('px')) {
|
|
175
|
+
return `${cssProp}: var(--${value});`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Standard case: prefix-value (e.g., p-10px, fs-48px, ta-center)
|
|
180
|
+
return `${cssProp}: ${value};`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generate CSS for all utility classes used in the application
|
|
185
|
+
* Scans through all classes and generates the necessary CSS rules
|
|
186
|
+
* Optionally applies responsive scaling based on configuration
|
|
187
|
+
*/
|
|
188
|
+
export function generateUtilityCSS(
|
|
189
|
+
usedClasses: Set<string>,
|
|
190
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS,
|
|
191
|
+
responsiveScales?: ResponsiveScales
|
|
192
|
+
): string {
|
|
193
|
+
const css: string[] = [];
|
|
194
|
+
const baseClasses = new Set<string>();
|
|
195
|
+
const autoResponsiveClasses = new Set<string>(); // Classes that should get auto-scaling
|
|
196
|
+
|
|
197
|
+
// Create a map for responsive breakpoint classes
|
|
198
|
+
// Map from prefix (e.g., 't', 'm') to the class name and breakpoint info
|
|
199
|
+
type BreakpointClassMap = Record<string, { classes: Set<string>; breakpointName: string; value: number }>;
|
|
200
|
+
const responsiveClasses: BreakpointClassMap = {};
|
|
201
|
+
|
|
202
|
+
// Initialize responsive class sets for each breakpoint
|
|
203
|
+
for (const [breakpointName, breakpointValue] of Object.entries(breakpoints)) {
|
|
204
|
+
// Generate prefix from breakpoint name, avoiding conflicts with property prefixes
|
|
205
|
+
// For 'mobile', use 'mob' to avoid conflict with 'margin' (m-), etc.
|
|
206
|
+
let prefix = breakpointName.charAt(0).toLowerCase();
|
|
207
|
+
if (breakpointName.toLowerCase() === 'mobile') {
|
|
208
|
+
prefix = 'mob'; // Use 'mob' instead of 'm' to avoid margin conflict
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
responsiveClasses[prefix] = {
|
|
212
|
+
classes: new Set<string>(),
|
|
213
|
+
breakpointName,
|
|
214
|
+
value: breakpointValue
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Separate classes by breakpoint
|
|
219
|
+
for (const className of usedClasses) {
|
|
220
|
+
let matched = false;
|
|
221
|
+
|
|
222
|
+
// Try to match against any responsive breakpoint prefix
|
|
223
|
+
for (const prefix of Object.keys(responsiveClasses)) {
|
|
224
|
+
if (className.startsWith(`${prefix}-`) && className.length > prefix.length + 1) {
|
|
225
|
+
const potentialClass = className.substring(prefix.length + 1);
|
|
226
|
+
const rule = generateRuleForClass(potentialClass);
|
|
227
|
+
|
|
228
|
+
// If it generates a valid rule and doesn't look like a margin value, treat as breakpoint class
|
|
229
|
+
// Margin values are typically: auto, 0, or pixel/percentage values (like 10px, 92px, 50p)
|
|
230
|
+
if (rule && !potentialClass.match(/^(auto|0|[\d.]+px|[\d.]+p)$/)) {
|
|
231
|
+
responsiveClasses[prefix].classes.add(potentialClass);
|
|
232
|
+
matched = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!matched) {
|
|
239
|
+
baseClasses.add(className);
|
|
240
|
+
|
|
241
|
+
// Check if this class should get auto-responsive scaling
|
|
242
|
+
if (responsiveScales?.enabled) {
|
|
243
|
+
const propValue = extractPropertyAndValue(className);
|
|
244
|
+
if (propValue) {
|
|
245
|
+
// Check if this property type has scaling configured
|
|
246
|
+
const typeMap: Record<string, string> = {
|
|
247
|
+
'padding': 'padding',
|
|
248
|
+
'padding-left': 'padding',
|
|
249
|
+
'padding-right': 'padding',
|
|
250
|
+
'padding-top': 'padding',
|
|
251
|
+
'padding-bottom': 'padding',
|
|
252
|
+
'margin': 'margin',
|
|
253
|
+
'margin-left': 'margin',
|
|
254
|
+
'margin-right': 'margin',
|
|
255
|
+
'margin-top': 'margin',
|
|
256
|
+
'margin-bottom': 'margin',
|
|
257
|
+
'font-size': 'fontSize',
|
|
258
|
+
'gap': 'gap',
|
|
259
|
+
'row-gap': 'gap',
|
|
260
|
+
'column-gap': 'gap',
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const category = typeMap[propValue.property];
|
|
264
|
+
if (category && responsiveScales[category as keyof ResponsiveScales]) {
|
|
265
|
+
autoResponsiveClasses.add(className);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Generate base rules
|
|
273
|
+
for (const className of baseClasses) {
|
|
274
|
+
const rule = generateRuleForClass(className);
|
|
275
|
+
if (rule) {
|
|
276
|
+
// Escape special characters in class name for CSS selector
|
|
277
|
+
const escapedClassName = escapeCSSClassName(className);
|
|
278
|
+
css.push(`.${escapedClassName} { ${rule} }`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Map to collect auto-responsive media queries by breakpoint
|
|
283
|
+
type MediaQueryMap = Record<string, { classes: Array<{ className: string; rule: string }>; value: number }>;
|
|
284
|
+
const autoResponsiveMediaQueries: MediaQueryMap = {};
|
|
285
|
+
|
|
286
|
+
// Generate auto-responsive rules for classes with enabled scaling
|
|
287
|
+
if (responsiveScales?.enabled) {
|
|
288
|
+
const typeMap: Record<string, string> = {
|
|
289
|
+
'padding': 'padding',
|
|
290
|
+
'padding-left': 'padding',
|
|
291
|
+
'padding-right': 'padding',
|
|
292
|
+
'padding-top': 'padding',
|
|
293
|
+
'padding-bottom': 'padding',
|
|
294
|
+
'margin': 'margin',
|
|
295
|
+
'margin-left': 'margin',
|
|
296
|
+
'margin-right': 'margin',
|
|
297
|
+
'margin-top': 'margin',
|
|
298
|
+
'margin-bottom': 'margin',
|
|
299
|
+
'font-size': 'fontSize',
|
|
300
|
+
'gap': 'gap',
|
|
301
|
+
'row-gap': 'gap',
|
|
302
|
+
'column-gap': 'gap',
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
for (const className of autoResponsiveClasses) {
|
|
306
|
+
const propValue = extractPropertyAndValue(className);
|
|
307
|
+
if (!propValue) continue;
|
|
308
|
+
|
|
309
|
+
const category = typeMap[propValue.property];
|
|
310
|
+
if (!category) continue;
|
|
311
|
+
|
|
312
|
+
const scaleConfig = responsiveScales[category as keyof ResponsiveScales] as any;
|
|
313
|
+
if (!scaleConfig) continue;
|
|
314
|
+
|
|
315
|
+
const baseRef = responsiveScales.baseReference || 16;
|
|
316
|
+
const escapedClassName = escapeCSSClassName(className);
|
|
317
|
+
|
|
318
|
+
// Generate scaled rules for each breakpoint
|
|
319
|
+
for (const [breakpointName, breakpointValue] of Object.entries(breakpoints)) {
|
|
320
|
+
const scaleType = breakpointName === 'mobile' ? 'mobile' : 'tablet';
|
|
321
|
+
const scale = scaleConfig[scaleType];
|
|
322
|
+
if (!scale) continue;
|
|
323
|
+
|
|
324
|
+
const scaledValue = scalePropertyValue(propValue.value, baseRef, scale);
|
|
325
|
+
if (!scaledValue) continue;
|
|
326
|
+
|
|
327
|
+
// Initialize media query entry if not exists
|
|
328
|
+
if (!autoResponsiveMediaQueries[breakpointName]) {
|
|
329
|
+
autoResponsiveMediaQueries[breakpointName] = {
|
|
330
|
+
classes: [],
|
|
331
|
+
value: breakpointValue
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
autoResponsiveMediaQueries[breakpointName].classes.push({
|
|
336
|
+
className: escapedClassName,
|
|
337
|
+
rule: `${propValue.property}: ${scaledValue};`
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Generate media queries in two separate sections:
|
|
344
|
+
// 1. Auto-responsive classes FIRST (baseline responsive behavior)
|
|
345
|
+
// 2. Manual breakpoint classes AFTER (explicit overrides)
|
|
346
|
+
// This ensures manual t-, mob- classes always override auto-responsive rules
|
|
347
|
+
|
|
348
|
+
// Sort auto-responsive media queries by breakpoint value (descending)
|
|
349
|
+
const sortedAutoResponsive = Object.entries(autoResponsiveMediaQueries)
|
|
350
|
+
.sort(([, a], [, b]) => b.value - a.value);
|
|
351
|
+
|
|
352
|
+
// Generate auto-responsive media queries first
|
|
353
|
+
for (const [breakpointName, mq] of sortedAutoResponsive) {
|
|
354
|
+
if (mq.classes.length === 0) continue;
|
|
355
|
+
|
|
356
|
+
css.push(`@media (max-width: ${mq.value}px) {`);
|
|
357
|
+
for (const { className, rule } of mq.classes) {
|
|
358
|
+
css.push(` .${className} { ${rule} }`);
|
|
359
|
+
}
|
|
360
|
+
css.push('}');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Sort manual responsive classes by breakpoint value (descending)
|
|
364
|
+
const sortedManualResponsive = Object.entries(responsiveClasses)
|
|
365
|
+
.filter(([, info]) => info.classes.size > 0)
|
|
366
|
+
.sort(([, a], [, b]) => b.value - a.value);
|
|
367
|
+
|
|
368
|
+
// Generate manual breakpoint media queries after
|
|
369
|
+
for (const [prefix, breakpointInfo] of sortedManualResponsive) {
|
|
370
|
+
const rules: string[] = [];
|
|
371
|
+
|
|
372
|
+
for (const className of breakpointInfo.classes) {
|
|
373
|
+
const rule = generateRuleForClass(className);
|
|
374
|
+
if (rule) {
|
|
375
|
+
const escapedClassName = escapeCSSClassName(className);
|
|
376
|
+
rules.push(` .${prefix}-${escapedClassName} { ${rule} }`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (rules.length > 0) {
|
|
381
|
+
css.push(`@media (max-width: ${breakpointInfo.value}px) {`);
|
|
382
|
+
css.push(...rules);
|
|
383
|
+
css.push('}');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return css.join('\n');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Extract all utility classes from a rendered HTML string
|
|
392
|
+
* Identifies utility classes by checking against known prefixes from propertyMap
|
|
393
|
+
*/
|
|
394
|
+
export function extractUtilityClassesFromHTML(html: string): Set<string> {
|
|
395
|
+
const classes = new Set<string>();
|
|
396
|
+
const classRegex = /class="([^"]*)"/g;
|
|
397
|
+
let match;
|
|
398
|
+
|
|
399
|
+
// Get all known utility prefixes from propertyMap
|
|
400
|
+
const knownPrefixes = new Set(Object.values(propertyMap));
|
|
401
|
+
// Also include special/short form prefixes from utilityClassRules
|
|
402
|
+
const specialPrefixes = new Set([
|
|
403
|
+
// Display short forms
|
|
404
|
+
'f', 'fd-col', 'fd-row', 'g', 'b', 'i', 'ib', 'h',
|
|
405
|
+
// Justify content
|
|
406
|
+
'jc-c', 'jc-s', 'jc-e', 'jc-b', 'jc-a',
|
|
407
|
+
// Align items
|
|
408
|
+
'ai-c', 'ai-s', 'ai-e', 'ai-b',
|
|
409
|
+
// Overflow
|
|
410
|
+
'o-h', 'o-a', 'o-s', 'o-v',
|
|
411
|
+
// Cursor
|
|
412
|
+
'cursor-pointer', 'cursor-default',
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
while ((match = classRegex.exec(html)) !== null) {
|
|
416
|
+
const classString = match[1];
|
|
417
|
+
const classList = classString.split(/\s+/);
|
|
418
|
+
|
|
419
|
+
for (const className of classList) {
|
|
420
|
+
// Skip empty class names
|
|
421
|
+
if (!className || className.length === 0) continue;
|
|
422
|
+
|
|
423
|
+
// Check if it's a special/short form class
|
|
424
|
+
if (specialPrefixes.has(className)) {
|
|
425
|
+
classes.add(className);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check for responsive prefix (like 't-', 'mob-', 's-', etc.) and process accordingly
|
|
430
|
+
let classToCheck = className;
|
|
431
|
+
let hasResponsivePrefix = false;
|
|
432
|
+
|
|
433
|
+
// Extract responsive prefix if present
|
|
434
|
+
// Check for multi-letter prefix first (e.g., 'mob-' for mobile)
|
|
435
|
+
if (className.startsWith('mob-') && className.length > 4) {
|
|
436
|
+
classToCheck = className.substring(4); // Remove 'mob-' prefix
|
|
437
|
+
hasResponsivePrefix = true;
|
|
438
|
+
}
|
|
439
|
+
// Then check for single-letter prefixes (t-, s-, l-, x-, u-)
|
|
440
|
+
else if (className.length > 2 && className.charAt(1) === '-' && className.match(/^[a-z]-/)) {
|
|
441
|
+
const firstChar = className.charAt(0);
|
|
442
|
+
// Only treat as responsive prefix if it looks like a breakpoint indicator
|
|
443
|
+
// Common breakpoint prefixes: t (tablet), s (small), l (large), x (extra), u (ultra)
|
|
444
|
+
// NOTE: We exclude 'm' because it conflicts with margin prefix - use 'mob' instead
|
|
445
|
+
if (['t', 's', 'l', 'x', 'u'].includes(firstChar)) {
|
|
446
|
+
classToCheck = className.substring(2); // Remove responsive prefix
|
|
447
|
+
hasResponsivePrefix = true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Check if it starts with a known prefix
|
|
452
|
+
// Handle both single-letter (like 'p-', 'w-') and multi-letter (like 'jc-', 'ai-')
|
|
453
|
+
for (const prefix of knownPrefixes) {
|
|
454
|
+
if (classToCheck === prefix) {
|
|
455
|
+
// Exact match (like 'f' for flex, 'g' for grid)
|
|
456
|
+
classes.add(className);
|
|
457
|
+
break;
|
|
458
|
+
} else if (classToCheck.startsWith(prefix + '-')) {
|
|
459
|
+
// Prefix with hyphen separator (like 'p-10px', 'fs-16px', or 't-p-10px')
|
|
460
|
+
classes.add(className);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Also check special prefixes for responsive classes
|
|
466
|
+
if (hasResponsivePrefix && !classes.has(className)) {
|
|
467
|
+
for (const specialPrefix of specialPrefixes) {
|
|
468
|
+
if (classToCheck === specialPrefix) {
|
|
469
|
+
classes.add(className);
|
|
470
|
+
break;
|
|
471
|
+
} else if (classToCheck.startsWith(specialPrefix + '-')) {
|
|
472
|
+
classes.add(className);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return classes;
|
|
481
|
+
}
|