meno-core 1.0.53 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +1 -3
- package/bin/cli.ts +66 -261
- package/build-astro.ts +150 -128
- package/dist/bin/cli.js +42 -185
- package/dist/bin/cli.js.map +2 -2
- package/dist/chunks/chunk-2AR55GYH.js +42 -0
- package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
- package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
- package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
- package/dist/chunks/chunk-4ZRU52J2.js +169 -0
- package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
- package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
- package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
- package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
- package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
- package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
- package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
- package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
- package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
- package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
- package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
- package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
- package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
- package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
- package/dist/lib/client/index.js +1003 -475
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +14393 -6371
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +342 -101
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +64 -80
- package/dist/lib/test-utils/index.js.map +2 -2
- package/entries/client-router.tsx +10 -8
- package/lib/client/ClientInitializer.ts +8 -8
- package/lib/client/ErrorBoundary.test.tsx +246 -186
- package/lib/client/ErrorBoundary.tsx +183 -132
- package/lib/client/componentRegistry.test.ts +96 -108
- package/lib/client/componentRegistry.ts +1 -3
- package/lib/client/contexts/ThemeContext.tsx +3 -2
- package/lib/client/core/ComponentBuilder.test.ts +663 -560
- package/lib/client/core/ComponentBuilder.ts +427 -155
- package/lib/client/core/ComponentRenderer.test.tsx +2 -4
- package/lib/client/core/ComponentRenderer.tsx +46 -33
- package/lib/client/core/builders/embedBuilder.ts +242 -54
- package/lib/client/core/builders/linkBuilder.ts +72 -45
- package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
- package/lib/client/core/builders/listBuilder.ts +155 -89
- package/lib/client/core/builders/localeListBuilder.ts +97 -62
- package/lib/client/core/builders/types.ts +5 -5
- package/lib/client/core/cmsTemplateProcessor.ts +8 -9
- package/lib/client/elementRegistry.ts +3 -3
- package/lib/client/fontFamiliesService.test.ts +2 -10
- package/lib/client/fontFamiliesService.ts +3 -3
- package/lib/client/hmr/HMRManager.tsx +47 -30
- package/lib/client/hmrCssReload.ts +49 -14
- package/lib/client/hmrWebSocket.ts +14 -19
- package/lib/client/hooks/useColorVariables.test.ts +21 -21
- package/lib/client/hooks/useColorVariables.ts +23 -18
- package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
- package/lib/client/hooks/useVariables.ts +6 -4
- package/lib/client/hydration/HydrationUtils.test.ts +24 -25
- package/lib/client/hydration/HydrationUtils.ts +3 -4
- package/lib/client/i18nConfigService.test.ts +2 -7
- package/lib/client/i18nConfigService.ts +2 -2
- package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
- package/lib/client/meno-filter/MenoFilter.ts +14 -15
- package/lib/client/meno-filter/bindings.ts +18 -43
- package/lib/client/meno-filter/init.ts +4 -4
- package/lib/client/meno-filter/renderer.ts +30 -32
- package/lib/client/meno-filter/script.generated.ts +1 -3
- package/lib/client/meno-filter/ui.ts +4 -6
- package/lib/client/meno-filter/updates.ts +18 -23
- package/lib/client/meno-filter/utils.ts +1 -1
- package/lib/client/navigation.test.ts +174 -178
- package/lib/client/navigation.ts +0 -1
- package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
- package/lib/client/responsiveStyleResolver.test.ts +234 -232
- package/lib/client/responsiveStyleResolver.ts +74 -20
- package/lib/client/routing/RouteLoader.test.ts +24 -25
- package/lib/client/routing/RouteLoader.ts +27 -35
- package/lib/client/routing/Router.tsx +210 -153
- package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
- package/lib/client/scripts/ScriptExecutor.ts +18 -36
- package/lib/client/services/PrefetchService.test.ts +3 -3
- package/lib/client/services/PrefetchService.ts +23 -29
- package/lib/client/styleProcessor.test.ts +9 -9
- package/lib/client/styleProcessor.ts +24 -15
- package/lib/client/styles/StyleInjector.test.ts +122 -115
- package/lib/client/styles/StyleInjector.ts +10 -7
- package/lib/client/styles/UtilityClassCollector.ts +62 -28
- package/lib/client/styles/cspNonce.test.ts +2 -5
- package/lib/client/templateEngine.test.ts +554 -488
- package/lib/client/templateEngine.ts +203 -73
- package/lib/client/theme.test.ts +1 -1
- package/lib/client/theme.ts +0 -1
- package/lib/client/utils/toast.ts +0 -1
- package/lib/server/__integration__/api-routes.test.ts +8 -4
- package/lib/server/__integration__/cms-integration.test.ts +1 -4
- package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
- package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
- package/lib/server/__integration__/static-assets.test.ts +3 -3
- package/lib/server/__integration__/test-helpers.ts +87 -74
- package/lib/server/ab/generateFunctions.ts +12 -10
- package/lib/server/astro/cmsPageEmitter.ts +26 -42
- package/lib/server/astro/componentEmitter.ts +35 -43
- package/lib/server/astro/cssCollector.ts +10 -26
- package/lib/server/astro/nodeToAstro.test.ts +1754 -34
- package/lib/server/astro/nodeToAstro.ts +232 -216
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
- package/lib/server/astro/pageEmitter.ts +9 -13
- package/lib/server/astro/tailwindMapper.test.ts +10 -37
- package/lib/server/astro/tailwindMapper.ts +33 -40
- package/lib/server/astro/templateTransformer.ts +17 -20
- package/lib/server/createServer.ts +7 -19
- package/lib/server/cssGenerator.test.ts +35 -44
- package/lib/server/cssGenerator.ts +8 -19
- package/lib/server/fileWatcher.test.ts +164 -13
- package/lib/server/fileWatcher.ts +209 -124
- package/lib/server/index.ts +14 -17
- package/lib/server/jsonLoader.test.ts +39 -2
- package/lib/server/jsonLoader.ts +39 -34
- package/lib/server/middleware/cors.test.ts +21 -21
- package/lib/server/middleware/cors.ts +28 -4
- package/lib/server/middleware/errorHandler.test.ts +7 -5
- package/lib/server/middleware/errorHandler.ts +3 -8
- package/lib/server/middleware/index.ts +0 -1
- package/lib/server/middleware/logger.test.ts +10 -8
- package/lib/server/middleware/logger.ts +17 -29
- package/lib/server/migrateTemplates.ts +3 -3
- package/lib/server/pageCache.test.ts +76 -77
- package/lib/server/pageCache.ts +0 -1
- package/lib/server/projectContext.ts +5 -4
- package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
- package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
- package/lib/server/routes/api/cms.test.ts +26 -14
- package/lib/server/routes/api/cms.ts +9 -14
- package/lib/server/routes/api/colors.test.ts +2 -2
- package/lib/server/routes/api/components.ts +31 -35
- package/lib/server/routes/api/config.ts +3 -4
- package/lib/server/routes/api/core-routes.ts +49 -76
- package/lib/server/routes/api/enums.test.ts +2 -2
- package/lib/server/routes/api/functions.ts +9 -17
- package/lib/server/routes/api/index.ts +3 -6
- package/lib/server/routes/api/pages.ts +8 -11
- package/lib/server/routes/api/shared.test.ts +1 -1
- package/lib/server/routes/api/shared.ts +12 -9
- package/lib/server/routes/api/variables.test.ts +3 -5
- package/lib/server/routes/api/variables.ts +1 -1
- package/lib/server/routes/index.ts +25 -28
- package/lib/server/routes/pages.ts +35 -31
- package/lib/server/routes/static.ts +17 -6
- package/lib/server/runtime/bundler.ts +98 -61
- package/lib/server/runtime/fs.ts +16 -26
- package/lib/server/runtime/httpServer.ts +8 -12
- package/lib/server/services/ColorService.test.ts +3 -3
- package/lib/server/services/ColorService.ts +37 -29
- package/lib/server/services/EnumService.test.ts +4 -8
- package/lib/server/services/EnumService.ts +11 -3
- package/lib/server/services/VariableService.test.ts +8 -12
- package/lib/server/services/VariableService.ts +11 -3
- package/lib/server/services/cmsService.test.ts +158 -120
- package/lib/server/services/cmsService.ts +36 -146
- package/lib/server/services/componentService.test.ts +125 -45
- package/lib/server/services/componentService.ts +70 -46
- package/lib/server/services/configService.test.ts +22 -45
- package/lib/server/services/configService.ts +50 -27
- package/lib/server/services/fileWatcherService.ts +42 -8
- package/lib/server/services/index.ts +0 -1
- package/lib/server/services/pageService.test.ts +8 -10
- package/lib/server/services/pageService.ts +32 -18
- package/lib/server/ssr/attributeBuilder.ts +18 -8
- package/lib/server/ssr/buildErrorOverlay.ts +11 -12
- package/lib/server/ssr/clientDataInjector.ts +7 -21
- package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
- package/lib/server/ssr/cssCollector.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +1 -1
- package/lib/server/ssr/errorOverlay.ts +3 -9
- package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
- package/lib/server/ssr/htmlGenerator.test.ts +120 -44
- package/lib/server/ssr/htmlGenerator.ts +126 -91
- package/lib/server/ssr/imageMetadata.test.ts +3 -1
- package/lib/server/ssr/imageMetadata.ts +27 -21
- package/lib/server/ssr/jsCollector.test.ts +8 -18
- package/lib/server/ssr/jsCollector.ts +3 -8
- package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
- package/lib/server/ssr/metaTagGenerator.ts +2 -2
- package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
- package/lib/server/ssr/ssrRenderer.test.ts +316 -256
- package/lib/server/ssr/ssrRenderer.ts +795 -243
- package/lib/server/ssrRenderer.test.ts +1048 -954
- package/lib/server/utils/jsonLineMapper.test.ts +28 -28
- package/lib/server/utils/jsonLineMapper.ts +7 -7
- package/lib/server/validateStyleCoverage.ts +18 -21
- package/lib/server/websocketManager.ts +28 -19
- package/lib/shared/attributeNodeUtils.test.ts +15 -15
- package/lib/shared/attributeNodeUtils.ts +5 -12
- package/lib/shared/breakpoints.test.ts +5 -5
- package/lib/shared/breakpoints.ts +4 -11
- package/lib/shared/cmsQuery.test.ts +81 -0
- package/lib/shared/cmsQuery.ts +92 -0
- package/lib/shared/cmsQueryParser.test.ts +50 -42
- package/lib/shared/cmsQueryParser.ts +60 -38
- package/lib/shared/colorConversions.ts +9 -9
- package/lib/shared/colorVariableUtils.test.ts +27 -5
- package/lib/shared/colorVariableUtils.ts +21 -12
- package/lib/shared/componentRefs.ts +1 -5
- package/lib/shared/constants.test.ts +3 -3
- package/lib/shared/constants.ts +8 -8
- package/lib/shared/cssGeneration.test.ts +285 -144
- package/lib/shared/cssGeneration.ts +217 -531
- package/lib/shared/cssNamedColors.ts +152 -30
- package/lib/shared/cssProperties.test.ts +69 -70
- package/lib/shared/cssProperties.ts +298 -114
- package/lib/shared/elementClassName.test.ts +109 -109
- package/lib/shared/elementClassName.ts +3 -3
- package/lib/shared/elementUtils.ts +12 -16
- package/lib/shared/errorLogger.test.ts +12 -12
- package/lib/shared/errorLogger.ts +2 -10
- package/lib/shared/errors.test.ts +2 -13
- package/lib/shared/errors.ts +2 -8
- package/lib/shared/expressionEvaluator.test.ts +145 -0
- package/lib/shared/expressionEvaluator.ts +52 -24
- package/lib/shared/fontLoader.test.ts +32 -18
- package/lib/shared/fontLoader.ts +2 -2
- package/lib/shared/friendlyError.ts +2 -3
- package/lib/shared/gradientUtils.test.ts +3 -7
- package/lib/shared/gradientUtils.ts +17 -14
- package/lib/shared/hrefRefs.test.ts +2 -2
- package/lib/shared/hrefRefs.ts +2 -10
- package/lib/shared/i18n.test.ts +4 -4
- package/lib/shared/i18n.ts +18 -38
- package/lib/shared/index.ts +27 -0
- package/lib/shared/inlineSvgStyleRules.ts +14 -13
- package/lib/shared/interactiveStyleMappings.test.ts +32 -54
- package/lib/shared/interactiveStyleMappings.ts +11 -24
- package/lib/shared/interactiveStyles.test.ts +165 -188
- package/lib/shared/interfaces/contentProvider.ts +1 -1
- package/lib/shared/itemTemplateUtils.test.ts +11 -17
- package/lib/shared/itemTemplateUtils.ts +27 -39
- package/lib/shared/jsonRepair.ts +16 -10
- package/lib/shared/libraryLoader.test.ts +21 -55
- package/lib/shared/libraryLoader.ts +7 -22
- package/lib/shared/linkUtils.ts +1 -1
- package/lib/shared/logger.test.ts +66 -0
- package/lib/shared/logger.ts +94 -0
- package/lib/shared/markdown.ts +25 -0
- package/lib/shared/netlifyLocale404.test.ts +179 -0
- package/lib/shared/netlifyLocale404.ts +110 -0
- package/lib/shared/nodeUtils.test.ts +28 -16
- package/lib/shared/nodeUtils.ts +76 -23
- package/lib/shared/pathArrayUtils.test.ts +1 -2
- package/lib/shared/pathArrayUtils.ts +1 -1
- package/lib/shared/pathSecurity.ts +2 -2
- package/lib/shared/pathUtils.test.ts +4 -6
- package/lib/shared/pathUtils.ts +42 -49
- package/lib/shared/paths/Path.test.ts +2 -2
- package/lib/shared/paths/Path.ts +0 -1
- package/lib/shared/paths/PathConverter.test.ts +1 -1
- package/lib/shared/paths/PathConverter.ts +17 -20
- package/lib/shared/paths/PathUtils.ts +13 -13
- package/lib/shared/paths/PathValidator.test.ts +2 -15
- package/lib/shared/paths/PathValidator.ts +12 -10
- package/lib/shared/paths/index.ts +1 -2
- package/lib/shared/permissions.test.ts +168 -0
- package/lib/shared/permissions.ts +162 -0
- package/lib/shared/propResolver.test.ts +240 -244
- package/lib/shared/propResolver.ts +15 -26
- package/lib/shared/pxToRem.test.ts +7 -6
- package/lib/shared/pxToRem.ts +2 -5
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
- package/lib/shared/registry/ClientRegistry.ts +0 -2
- package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
- package/lib/shared/registry/ComponentRegistry.ts +12 -9
- package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
- package/lib/shared/registry/RegistryManager.ts +1 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -2
- package/lib/shared/registry/createNodeType.ts +7 -9
- package/lib/shared/registry/defineNodeType.ts +2 -6
- package/lib/shared/registry/fieldPresets.ts +3 -1
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
- package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
- package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
- package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
- package/lib/shared/registry/nodeTypes/index.ts +26 -3
- package/lib/shared/responsiveScaling.test.ts +16 -34
- package/lib/shared/responsiveScaling.ts +61 -42
- package/lib/shared/responsiveStyleUtils.test.ts +0 -1
- package/lib/shared/responsiveStyleUtils.ts +11 -13
- package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
- package/lib/shared/richtext/htmlToTiptap.ts +2 -4
- package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
- package/lib/shared/richtext/types.ts +1 -8
- package/lib/shared/slugTranslator.test.ts +13 -13
- package/lib/shared/slugTranslator.ts +12 -16
- package/lib/shared/slugify.ts +9 -15
- package/lib/shared/styleNodeUtils.test.ts +8 -8
- package/lib/shared/styleNodeUtils.ts +18 -20
- package/lib/shared/styleUtils.test.ts +87 -61
- package/lib/shared/styleUtils.ts +5 -6
- package/lib/shared/themeDefaults.test.ts +11 -14
- package/lib/shared/themeDefaults.ts +3 -4
- package/lib/shared/tree/PathBuilder.test.ts +63 -145
- package/lib/shared/tree/PathBuilder.ts +59 -47
- package/lib/shared/treePathUtils.test.ts +51 -12
- package/lib/shared/treePathUtils.ts +91 -60
- package/lib/shared/types/api.ts +105 -8
- package/lib/shared/types/cms.test.ts +34 -0
- package/lib/shared/types/cms.ts +90 -34
- package/lib/shared/types/comment.ts +49 -16
- package/lib/shared/types/components.ts +51 -25
- package/lib/shared/types/errors.test.ts +1 -6
- package/lib/shared/types/errors.ts +3 -7
- package/lib/shared/types/experiments.ts +28 -28
- package/lib/shared/types/index.ts +25 -2
- package/lib/shared/types/permissions.ts +139 -0
- package/lib/shared/types/styles.ts +0 -1
- package/lib/shared/types/variables.test.ts +4 -13
- package/lib/shared/types/variables.ts +51 -30
- package/lib/shared/types.ts +1 -2
- package/lib/shared/utilityClassConfig.ts +751 -321
- package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
- package/lib/shared/utilityClassMapper.test.ts +433 -95
- package/lib/shared/utilityClassMapper.ts +322 -249
- package/lib/shared/utilityClassNames.test.ts +38 -0
- package/lib/shared/utilityClassNames.ts +458 -0
- package/lib/shared/utils.test.ts +2 -10
- package/lib/shared/utils.ts +19 -10
- package/lib/shared/validation/cmsValidators.ts +2 -1
- package/lib/shared/validation/commentValidators.test.ts +53 -0
- package/lib/shared/validation/commentValidators.ts +12 -1
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/validation/permissionsValidators.test.ts +49 -0
- package/lib/shared/validation/permissionsValidators.ts +49 -0
- package/lib/shared/validation/propValidator.test.ts +19 -21
- package/lib/shared/validation/propValidator.ts +13 -18
- package/lib/shared/validation/schemas.test.ts +64 -32
- package/lib/shared/validation/schemas.ts +616 -345
- package/lib/shared/validation/validators.test.ts +3 -8
- package/lib/shared/validation/validators.ts +89 -68
- package/lib/shared/viewportUnits.integration.test.ts +14 -10
- package/lib/shared/viewportUnits.test.ts +24 -23
- package/lib/shared/viewportUnits.ts +12 -5
- package/lib/test-utils/dom-setup.ts +1 -1
- package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
- package/lib/test-utils/factories/DomMockFactory.ts +17 -23
- package/lib/test-utils/factories/EventMockFactory.ts +7 -13
- package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
- package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
- package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
- package/lib/test-utils/fixtures.ts +45 -45
- package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
- package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
- package/lib/test-utils/helpers.ts +1 -5
- package/lib/test-utils/index.ts +0 -4
- package/lib/test-utils/mockFactories.ts +13 -19
- package/lib/test-utils/mocks.ts +6 -4
- package/package.json +3 -5
- package/scripts/build-for-publish.mjs +2 -4
- package/scripts/build-meno-filter.ts +4 -7
- package/tsconfig.json +1 -0
- package/vite.config.ts +4 -4
- package/build-next.ts +0 -1361
- package/build-static.test.ts +0 -424
- package/build-static.ts +0 -1074
- package/dist/build-static.js +0 -36
- package/dist/chunks/chunk-2MHDV5BF.js +0 -251
- package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
- package/dist/chunks/chunk-AZQYF6KE.js +0 -559
- package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
- package/dist/chunks/chunk-GZHGVVW3.js +0 -322
- package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
- package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
- package/dist/chunks/chunk-I7YIGZXT.js +0 -467
- package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
- package/dist/chunks/chunk-IGYR22T6.js +0 -6192
- package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
- package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
- package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
- package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
- package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
- package/dist/chunks/chunk-QB2LNO4W.js +0 -77
- package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
- package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
- package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
- package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
- package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
- package/dist/chunks/chunk-X754AHS5.js.map +0 -7
- package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
- package/dist/chunks/configService-R3OGU2UD.js +0 -13
- package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
- package/dist/chunks/constants-STK2YBIW.js +0 -46
- package/dist/chunks/constants-STK2YBIW.js.map +0 -7
- package/dist/chunks/fs-JGINUXGL.js.map +0 -7
- package/dist/entries/server-router.js +0 -67
- package/dist/entries/server-router.js.map +0 -7
- package/entries/server-router.tsx +0 -81
- package/lib/server/providers/fileSystemPageProvider.ts +0 -160
- package/lib/server/webflow/buildWebflow.ts +0 -635
- package/lib/server/webflow/index.ts +0 -23
- package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
- package/lib/server/webflow/nodeToWebflow.ts +0 -2435
- package/lib/server/webflow/styleMapper.test.ts +0 -389
- package/lib/server/webflow/styleMapper.ts +0 -695
- package/lib/server/webflow/templateWrapper.ts +0 -49
- package/lib/server/webflow/types.ts +0 -396
- /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { encodeArbitraryValue, decodeArbitraryValue } from './utilityClassNames';
|
|
3
|
+
|
|
4
|
+
describe('encodeArbitraryValue / decodeArbitraryValue', () => {
|
|
5
|
+
describe('reversible (decode ∘ encode === identity)', () => {
|
|
6
|
+
const cases: Array<[string, string]> = [
|
|
7
|
+
['single token', '24px'],
|
|
8
|
+
['spaces → underscores', '92px 0'],
|
|
9
|
+
['multi-token shorthand', '1px solid red'],
|
|
10
|
+
['literal underscore escaped', 'slide_in'],
|
|
11
|
+
['underscore + spaces', 'fade_in 2s ease'],
|
|
12
|
+
['underscore inside url() preserved', 'url(a_b.png)'],
|
|
13
|
+
['calc with spaces', 'calc(100% - 24px)'],
|
|
14
|
+
];
|
|
15
|
+
for (const [name, value] of cases) {
|
|
16
|
+
test(name, () => {
|
|
17
|
+
const encoded = encodeArbitraryValue(value);
|
|
18
|
+
expect(encoded).not.toBeNull();
|
|
19
|
+
// no raw spaces / quotes leak into the class token
|
|
20
|
+
expect(encoded!).not.toMatch(/[\s"']/);
|
|
21
|
+
expect(decodeArbitraryValue(encoded!)).toBe(value);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('literal underscore encodes via the \\_ escape', () => {
|
|
26
|
+
expect(encodeArbitraryValue('slide_in')).toBe('slide\\_in');
|
|
27
|
+
expect(encodeArbitraryValue('fade_in 2s ease')).toBe('fade\\_in_2s_ease');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('un-encodable → null (defer to side-channel)', () => {
|
|
32
|
+
for (const value of ['"a a"', "'x'", '[grid]', 'a\\b', 'url("a b.png")']) {
|
|
33
|
+
test(JSON.stringify(value), () => {
|
|
34
|
+
expect(encodeArbitraryValue(value)).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind-style utility class name parsing & value encoding.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the forward mapper (utilityClassMapper.ts) and the CSS generator
|
|
5
|
+
* (cssGeneration.ts) so class names encode and decode identically everywhere.
|
|
6
|
+
*
|
|
7
|
+
* Class name grammar (after stripping an optional `breakpoint:` variant):
|
|
8
|
+
* - static utilities: `flex`, `hidden`, `justify-center`, `uppercase`, …
|
|
9
|
+
* - presets: `shadow-2`, `bg-gradient-1`, `border-preset-0`
|
|
10
|
+
* - arbitrary values: `p-[40px]`, `w-[50%]`, `m-[92px_0]` (spaces → `_`)
|
|
11
|
+
* - CSS variables: `bg-(--primary)`, `text-(length:--font-size-lg)`
|
|
12
|
+
* - named keywords: `w-full`, `h-screen`, `m-auto`, `rounded-full`
|
|
13
|
+
* - bare numbers: `z-10`, `order-2`, `opacity-50` (= 0.5)
|
|
14
|
+
* - arbitrary properties: `[clip-path:circle(50%)]`, `[background:…]`
|
|
15
|
+
* - hash fallback: `pt-h1y9pr6i` (value lives in the style-value registry)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
prefixToCSSProperty,
|
|
20
|
+
staticUtilityReverse,
|
|
21
|
+
presetClassReverse,
|
|
22
|
+
keywordValues,
|
|
23
|
+
genericKeywordRoots,
|
|
24
|
+
integerValueRoots,
|
|
25
|
+
propertyOrder,
|
|
26
|
+
SPACING_SCALE,
|
|
27
|
+
SPACING_SCALE_ROOTS,
|
|
28
|
+
COLOR_TOKEN_ROOTS,
|
|
29
|
+
} from './utilityClassConfig';
|
|
30
|
+
import { isCssNamedColor } from './cssNamedColors';
|
|
31
|
+
|
|
32
|
+
/** Class roots sorted longest-first so `border-t-…` wins over `border-…`. */
|
|
33
|
+
export const SORTED_ROOTS: readonly string[] = Object.keys(prefixToCSSProperty).sort((a, b) => b.length - a.length);
|
|
34
|
+
|
|
35
|
+
/** Convert a camelCase style property to its kebab-case CSS name. */
|
|
36
|
+
export function camelToKebab(prop: string): string {
|
|
37
|
+
return prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// kebab-case CSS property → camelCase style property, for all known properties.
|
|
41
|
+
const kebabToCamelMap: Record<string, string> = (() => {
|
|
42
|
+
const map: Record<string, string> = {};
|
|
43
|
+
for (const prop of propertyOrder) {
|
|
44
|
+
map[camelToKebab(prop)] = prop;
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
})();
|
|
48
|
+
|
|
49
|
+
/** Convert a kebab-case CSS property back to its camelCase style property. */
|
|
50
|
+
export function cssPropertyToCamel(property: string): string {
|
|
51
|
+
return kebabToCamelMap[property] ?? property.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Roots that decode to a different camelCase property than their kebab CSS
|
|
56
|
+
* property suggests (px → paddingInline, not padding).
|
|
57
|
+
*/
|
|
58
|
+
export const ROOT_CAMEL_OVERRIDES: Record<string, string> = {
|
|
59
|
+
px: 'paddingInline',
|
|
60
|
+
py: 'paddingBlock',
|
|
61
|
+
mx: 'marginInline',
|
|
62
|
+
my: 'marginBlock',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Arbitrary value encoding (Tailwind bracket convention: spaces → underscores)
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apply `transform` to every part of `input` that is NOT inside a `url(...)`
|
|
71
|
+
* segment. Tailwind preserves underscores inside url() — so do we, in both
|
|
72
|
+
* encode and decode directions.
|
|
73
|
+
*/
|
|
74
|
+
function transformOutsideUrl(input: string, transform: (segment: string) => string): string {
|
|
75
|
+
let result = '';
|
|
76
|
+
let i = 0;
|
|
77
|
+
const lower = input.toLowerCase();
|
|
78
|
+
while (i < input.length) {
|
|
79
|
+
const idx = lower.indexOf('url(', i);
|
|
80
|
+
if (idx === -1) {
|
|
81
|
+
result += transform(input.slice(i));
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
result += transform(input.slice(i, idx));
|
|
85
|
+
// Copy the url(...) segment verbatim up to its matching close paren
|
|
86
|
+
let depth = 0;
|
|
87
|
+
let j = idx + 3; // position of '('
|
|
88
|
+
for (; j < input.length; j++) {
|
|
89
|
+
if (input[j] === '(') depth++;
|
|
90
|
+
else if (input[j] === ')') {
|
|
91
|
+
depth--;
|
|
92
|
+
if (depth === 0) {
|
|
93
|
+
j++;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
result += input.slice(idx, j);
|
|
99
|
+
i = j;
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Encode a CSS value for a Tailwind arbitrary-value class: spaces become
|
|
106
|
+
* underscores (`92px 0` → `92px_0`), and literal underscores are escaped as
|
|
107
|
+
* `\_` (`slide_in 2s` → `slide\_in_2s`) so they survive the decode — the `\_`
|
|
108
|
+
* escape is CSS-selector-safe (`escapeCSSClassName` turns the `\` into `\\`).
|
|
109
|
+
*
|
|
110
|
+
* Returns null when the value still can't be encoded reversibly — quotes,
|
|
111
|
+
* brackets, stray backslashes, or whitespace/quotes inside url(). Those callers
|
|
112
|
+
* fall back to a hash class + style-value registry entry (ephemeral; for the
|
|
113
|
+
* class-string-as-source migration they must move to a structured remainder —
|
|
114
|
+
* see docs/meno-class-styling-migration.md §5).
|
|
115
|
+
*/
|
|
116
|
+
export function encodeArbitraryValue(value: string): string | null {
|
|
117
|
+
let ok = true;
|
|
118
|
+
const encoded = transformOutsideUrl(value, (segment) => {
|
|
119
|
+
// Quotes/brackets/backslash can't live safely in a class attribute or the
|
|
120
|
+
// `[..]` value — reject (→ side-channel). Underscores ARE representable via
|
|
121
|
+
// the `\_` escape, so they're allowed.
|
|
122
|
+
if (/["'\\[\]]/.test(segment)) ok = false;
|
|
123
|
+
// Escape literal underscores BEFORE collapsing whitespace, so the
|
|
124
|
+
// underscores we introduce for spaces aren't themselves escaped.
|
|
125
|
+
return segment.replace(/_/g, '\\_').replace(/\s+/g, '_');
|
|
126
|
+
});
|
|
127
|
+
if (!ok) return null;
|
|
128
|
+
// Strip our own `\_` escapes, then reject anything still problematic — those
|
|
129
|
+
// chars can only have survived verbatim from inside a url(...) segment.
|
|
130
|
+
const guardTarget = encoded.replace(/^\[|\]$/g, '').replace(/\\_/g, '');
|
|
131
|
+
if (/[\s"'\\[\]]/.test(guardTarget)) return null;
|
|
132
|
+
return encoded;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Decode a Tailwind arbitrary value: `\_` → literal underscore and bare `_` →
|
|
137
|
+
* space, in a single pass (so an escaped underscore isn't first read as a
|
|
138
|
+
* space). Content inside url(...) is left verbatim.
|
|
139
|
+
*/
|
|
140
|
+
export function decodeArbitraryValue(encoded: string): string {
|
|
141
|
+
return transformOutsideUrl(encoded, (segment) => segment.replace(/\\_|_/g, (m) => (m === '\\_' ? '_' : ' ')));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Value-type classification (for roots shared by several properties)
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
const FONT_SIZE_KEYWORDS = new Set([
|
|
149
|
+
'xx-small',
|
|
150
|
+
'x-small',
|
|
151
|
+
'small',
|
|
152
|
+
'medium',
|
|
153
|
+
'large',
|
|
154
|
+
'x-large',
|
|
155
|
+
'xx-large',
|
|
156
|
+
'xxx-large',
|
|
157
|
+
'larger',
|
|
158
|
+
'smaller',
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
/** True when a value reads as a color (hex, color function, named color, var()). */
|
|
162
|
+
export function isColorValue(value: string): boolean {
|
|
163
|
+
if (value.startsWith('#') || value.startsWith('var(')) return true;
|
|
164
|
+
if (/^(rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color|color-mix)\(/i.test(value)) return true;
|
|
165
|
+
if (value === 'transparent' || value === 'currentColor' || value === 'currentcolor') return true;
|
|
166
|
+
return isCssNamedColor(value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* True for a bare Meno color token (e.g. `primary`, `background-light`) that
|
|
171
|
+
* resolves to `var(--token)`. Rejects: named CSS colors / hex / color functions
|
|
172
|
+
* / `transparent` / `currentColor` (those are real colors, not tokens), and the
|
|
173
|
+
* Tailwind palette shape `word-<number>` (`gray-500`, `blue-600`) so foreign
|
|
174
|
+
* Tailwind classes carried verbatim in `class=""` aren't misread as Meno tokens.
|
|
175
|
+
*
|
|
176
|
+
* Shared by the forward mapper (token → `bg-primary`) and inverse parser
|
|
177
|
+
* (`bg-primary` → `var(--primary)`) so the two stay symmetric.
|
|
178
|
+
*/
|
|
179
|
+
export function isBareColorTokenName(value: string): boolean {
|
|
180
|
+
if (!/^[a-zA-Z][\w-]*$/.test(value)) return false;
|
|
181
|
+
if (isColorValue(value)) return false;
|
|
182
|
+
if (/^[a-z]+-\d/i.test(value)) return false;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** True when a `text-…` value reads as a font-size (length/number/keyword). */
|
|
187
|
+
function isFontSizeValue(value: string): boolean {
|
|
188
|
+
if (/^[\d.+-]/.test(value)) return true;
|
|
189
|
+
if (/^(calc|clamp|min|max)\(/i.test(value)) return true;
|
|
190
|
+
return FONT_SIZE_KEYWORDS.has(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** True when a `font-…` value reads as a font-weight (numeric). */
|
|
194
|
+
function isFontWeightValue(value: string): boolean {
|
|
195
|
+
return /^\d+(\.\d+)?$/.test(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** True when a `bg-…` value reads as a background-image (url()/gradients). */
|
|
199
|
+
function isImageValue(value: string): boolean {
|
|
200
|
+
return /^(url|linear-gradient|radial-gradient|conic-gradient|repeating-linear-gradient|repeating-radial-gradient|repeating-conic-gradient|image-set|cross-fade|element)\(/i.test(
|
|
201
|
+
value,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** True for a single length token like `2px`, `0.5rem`, `-4px`. */
|
|
206
|
+
function isLengthValue(value: string): boolean {
|
|
207
|
+
return /^-?[\d.]+[a-z%]*$/i.test(value);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* True when the value contains whitespace OUTSIDE any parentheses, i.e. it's a
|
|
212
|
+
* multi-token shorthand (`1px solid red`) rather than a single token. A color
|
|
213
|
+
* function like `rgba(0, 0, 0, 0)` or `color-mix(in srgb, …)` carries internal
|
|
214
|
+
* spaces but is still ONE token — those must not read as a shorthand, otherwise
|
|
215
|
+
* a transparent `border-color` collapses into the `border` shorthand and stops
|
|
216
|
+
* overriding the width/style declaration independently.
|
|
217
|
+
*/
|
|
218
|
+
function hasTopLevelWhitespace(value: string): boolean {
|
|
219
|
+
let depth = 0;
|
|
220
|
+
for (let i = 0; i < value.length; i++) {
|
|
221
|
+
const ch = value[i] ?? '';
|
|
222
|
+
if (ch === '(') depth++;
|
|
223
|
+
else if (ch === ')') depth = Math.max(0, depth - 1);
|
|
224
|
+
else if (depth === 0 && /\s/.test(ch)) return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Resolve the CSS property for a class root, disambiguating Tailwind's shared
|
|
231
|
+
* roots by value type (`text-[14px]` → font-size, `text-[#fff]` → color, …).
|
|
232
|
+
*/
|
|
233
|
+
export function resolveRootProperty(root: string, value: string): string {
|
|
234
|
+
switch (root) {
|
|
235
|
+
case 'text':
|
|
236
|
+
return isFontSizeValue(value) ? 'font-size' : 'color';
|
|
237
|
+
case 'font':
|
|
238
|
+
return isFontWeightValue(value) ? 'font-weight' : 'font-family';
|
|
239
|
+
case 'bg':
|
|
240
|
+
return isImageValue(value) ? 'background-image' : 'background-color';
|
|
241
|
+
case 'border':
|
|
242
|
+
return isColorValue(value) && !hasTopLevelWhitespace(value) ? 'border-color' : 'border';
|
|
243
|
+
case 'outline': {
|
|
244
|
+
if (hasTopLevelWhitespace(value) || value === 'none') return 'outline';
|
|
245
|
+
if (isColorValue(value)) return 'outline-color';
|
|
246
|
+
if (isLengthValue(value)) return 'outline-width';
|
|
247
|
+
return 'outline';
|
|
248
|
+
}
|
|
249
|
+
default:
|
|
250
|
+
return prefixToCSSProperty[root] ?? root;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Breakpoint variant prefix (`tablet:p-[24px]`)
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Split an optional `breakpoint:` variant prefix off a class name.
|
|
260
|
+
* Arbitrary-property classes (`[grid-area:hero]`) never match — their colon
|
|
261
|
+
* sits inside brackets, and the prefix segment must be a bare identifier.
|
|
262
|
+
*/
|
|
263
|
+
export function splitVariantPrefix(className: string): { breakpoint: string | null; base: string } {
|
|
264
|
+
const match = className.match(/^([a-z][a-z0-9_-]*):(.+)$/);
|
|
265
|
+
if (match) {
|
|
266
|
+
return { breakpoint: match[1] ?? null, base: match[2] ?? className };
|
|
267
|
+
}
|
|
268
|
+
return { breakpoint: null, base: className };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Tailwind's desktop-first (max-width) variant marker. Meno breakpoints cascade desktop→mobile
|
|
273
|
+
* (`base` = widest, narrower breakpoints override via `max-width`), which is exactly Tailwind's
|
|
274
|
+
* `max-*` variant direction — so the class form is `max-<name>:` (`max-lg:p-4`), reading
|
|
275
|
+
* unambiguously as "this width and narrower". A bare prefix would read as Tailwind's mobile-first
|
|
276
|
+
* MIN-width (the opposite cascade), so the explicit `max-` keeps the class legible.
|
|
277
|
+
*/
|
|
278
|
+
export const DESKTOP_FIRST_VARIANT_PREFIX = 'max-';
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Class-name aliases for Meno's two default breakpoints. The breakpoint IDENTITY stays `tablet`/
|
|
282
|
+
* `mobile` everywhere (style model, UI labels, responsive-scales config) — only the emitted/parsed
|
|
283
|
+
* CLASS STRING uses Tailwind's canonical scale names: `tablet` (max-width 1024px) ⇄ `lg` (Tailwind's
|
|
284
|
+
* lg = 1024px, an exact match), `mobile` (max-width 540px) ⇄ `sm`. NB Meno keeps its 540px mobile
|
|
285
|
+
* threshold even though Tailwind's `sm` is 640px. Custom breakpoints have no alias (use their own
|
|
286
|
+
* name). Codec-only — nothing else in the system is renamed.
|
|
287
|
+
*/
|
|
288
|
+
const BREAKPOINT_NAME_TO_CLASS: Record<string, string> = { tablet: 'lg', mobile: 'sm' };
|
|
289
|
+
const CLASS_NAME_TO_BREAKPOINT: Record<string, string> = { lg: 'tablet', sm: 'mobile' };
|
|
290
|
+
|
|
291
|
+
/** Build the class variant prefix for a breakpoint name: `tablet` → `max-lg:`, `mobile` → `max-sm:`. */
|
|
292
|
+
export function breakpointClassPrefix(breakpointName: string): string {
|
|
293
|
+
const lower = breakpointName.toLowerCase();
|
|
294
|
+
const aliased = BREAKPOINT_NAME_TO_CLASS[lower] ?? lower;
|
|
295
|
+
return `${DESKTOP_FIRST_VARIANT_PREFIX}${aliased}:`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* All class prefixes that resolve to a breakpoint, for READ-side recognition (write paths emit only
|
|
300
|
+
* the canonical `breakpointClassPrefix`). For `tablet` this is the canonical `max-lg:`, the
|
|
301
|
+
* un-aliased `max-tablet:` (emitted briefly before the alias landed), and the legacy bare `tablet:`
|
|
302
|
+
* (pre-`max-` projects). Deliberately NOT the bare `lg:`/`sm:` — Meno never emitted those, and a bare
|
|
303
|
+
* `lg:` is a foreign real-Tailwind (min-width) token we must not absorb.
|
|
304
|
+
*/
|
|
305
|
+
export function breakpointClassPrefixes(breakpointName: string): string[] {
|
|
306
|
+
const lower = breakpointName.toLowerCase();
|
|
307
|
+
const aliased = BREAKPOINT_NAME_TO_CLASS[lower];
|
|
308
|
+
const prefixes = [`${DESKTOP_FIRST_VARIANT_PREFIX}${lower}:`, `${lower}:`]; // max-tablet:, tablet:
|
|
309
|
+
if (aliased) prefixes.unshift(`${DESKTOP_FIRST_VARIANT_PREFIX}${aliased}:`); // max-lg: (canonical, first)
|
|
310
|
+
return prefixes;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Normalize a parsed variant segment (from {@link splitVariantPrefix}) to the INTERNAL breakpoint
|
|
315
|
+
* name. The alias only applies behind the `max-` marker (a definite Meno desktop-first class):
|
|
316
|
+
* `max-lg`/`max-sm` → `tablet`/`mobile`, `max-tablet` → `tablet`, `max-<custom>` → `<custom>`. Bare
|
|
317
|
+
* segments pass through untouched — legacy Meno `tablet`/`mobile` map to themselves, and a foreign
|
|
318
|
+
* bare `lg`/`hover` is left alone (so it stays a non-modeled / foreign token, never mis-absorbed).
|
|
319
|
+
*/
|
|
320
|
+
export function normalizeBreakpointVariant(variant: string | null): string | null {
|
|
321
|
+
if (variant?.startsWith(DESKTOP_FIRST_VARIANT_PREFIX)) {
|
|
322
|
+
const bare = variant.slice(DESKTOP_FIRST_VARIANT_PREFIX.length);
|
|
323
|
+
return CLASS_NAME_TO_BREAKPOINT[bare] ?? bare;
|
|
324
|
+
}
|
|
325
|
+
return variant;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Class name parsing
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
export interface ParsedUtilityClass {
|
|
333
|
+
/** kebab-case CSS property the class targets */
|
|
334
|
+
property: string;
|
|
335
|
+
/** Decoded CSS value; null for hash classes (resolve via style-value registry) */
|
|
336
|
+
value: string | null;
|
|
337
|
+
/** The class root (`p`, `text`, `bg`, …); null for statics/presets/arbitrary properties */
|
|
338
|
+
root: string | null;
|
|
339
|
+
kind: 'static' | 'preset' | 'arbitrary' | 'var' | 'keyword' | 'numeric' | 'hash' | 'arbitrary-property';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const HASH_VALUE_RE = /^h[0-9a-z]+$/;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Parse a single (unprefixed) utility class name into its CSS property and
|
|
346
|
+
* decoded value. Returns null for class names that aren't Meno utilities.
|
|
347
|
+
*
|
|
348
|
+
* `knownTokens` is the project's defined design-token names (color + variable
|
|
349
|
+
* names). It gates the bare color-token form (`bg-primary` → `var(--primary)`):
|
|
350
|
+
* `bg-<name>` is only read as a token when `<name>` is in this set. Without it,
|
|
351
|
+
* `bg-<word>` stays UNRECOGNIZED — so foreign / authored classes (`text-wrapper`,
|
|
352
|
+
* imported `bg-dark`) are never misclassified. The `var(--…)` shorthand form
|
|
353
|
+
* (`bg-(--primary)`) is self-describing and needs no token set.
|
|
354
|
+
*/
|
|
355
|
+
export function parseUtilityClass(className: string, knownTokens?: ReadonlySet<string>): ParsedUtilityClass | null {
|
|
356
|
+
const stat = staticUtilityReverse[className];
|
|
357
|
+
if (stat) {
|
|
358
|
+
return { property: camelToKebab(stat.prop), value: stat.value, root: null, kind: 'static' };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const preset = presetClassReverse[className];
|
|
362
|
+
if (preset) {
|
|
363
|
+
return { property: camelToKebab(preset.prop), value: preset.value, root: null, kind: 'preset' };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Arbitrary property: [clip-path:circle(50%)], [background:linear-gradient(…)]
|
|
367
|
+
const arbProp = className.match(/^\[([a-z-]+):(.+)\]$/);
|
|
368
|
+
if (arbProp) {
|
|
369
|
+
return {
|
|
370
|
+
property: arbProp[1] ?? '',
|
|
371
|
+
value: decodeArbitraryValue(arbProp[2] ?? ''),
|
|
372
|
+
root: null,
|
|
373
|
+
kind: 'arbitrary-property',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const root of SORTED_ROOTS) {
|
|
378
|
+
if (!className.startsWith(`${root}-`)) continue;
|
|
379
|
+
const rest = className.slice(root.length + 1);
|
|
380
|
+
if (!rest) continue;
|
|
381
|
+
const parsed = parseRootValue(root, rest, knownTokens);
|
|
382
|
+
if (parsed) return parsed;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function parseRootValue(root: string, rest: string, knownTokens?: ReadonlySet<string>): ParsedUtilityClass | null {
|
|
389
|
+
// Arbitrary value: root-[value]
|
|
390
|
+
if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
|
|
391
|
+
const value = decodeArbitraryValue(rest.slice(1, -1));
|
|
392
|
+
return { property: resolveRootProperty(root, value), value, root, kind: 'arbitrary' };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// CSS variable: root-(--name) / root-(length:--name)
|
|
396
|
+
const varMatch = rest.match(/^\((?:([a-z-]+):)?(--[\w-]+)\)$/);
|
|
397
|
+
if (varMatch) {
|
|
398
|
+
const hint = varMatch[1];
|
|
399
|
+
const value = `var(${varMatch[2]})`;
|
|
400
|
+
let property: string;
|
|
401
|
+
if (root === 'text') {
|
|
402
|
+
property = hint === 'length' ? 'font-size' : 'color';
|
|
403
|
+
} else if (root === 'font') {
|
|
404
|
+
// `font-*` is shared by font-weight and font-family; a var value is ambiguous, so the
|
|
405
|
+
// emitter tags weight vars with a `number:` hint (see utilityClassMapper). Default to
|
|
406
|
+
// font-family (the original behavior for an un-hinted var like `font-(--l-ff)`).
|
|
407
|
+
property = hint === 'number' ? 'font-weight' : 'font-family';
|
|
408
|
+
} else {
|
|
409
|
+
property = resolveRootProperty(root, value);
|
|
410
|
+
}
|
|
411
|
+
return { property, value, root, kind: 'var' };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Named keyword: w-full, m-auto, rounded-full
|
|
415
|
+
const keyword = keywordValues[root]?.[rest];
|
|
416
|
+
if (keyword !== undefined) {
|
|
417
|
+
return { property: resolveRootProperty(root, keyword), value: keyword, root, kind: 'keyword' };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Generic keyword roots: cursor-pointer, align-middle, origin-center, …
|
|
421
|
+
if (genericKeywordRoots.has(root) && /^[a-z][a-z-]*$/.test(rest)) {
|
|
422
|
+
return { property: resolveRootProperty(root, rest), value: rest, root, kind: 'keyword' };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Named spacing scale: p-4 → 16px, gap-6 → 24px (Tailwind's step table).
|
|
426
|
+
if (SPACING_SCALE_ROOTS.has(root)) {
|
|
427
|
+
const scaleVal = SPACING_SCALE[rest];
|
|
428
|
+
if (scaleVal !== undefined) {
|
|
429
|
+
return { property: resolveRootProperty(root, scaleVal), value: scaleVal, root, kind: 'keyword' };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Bare numbers: z-10, order-2, flex-2; opacity uses Tailwind's 0–100 scale
|
|
434
|
+
if (integerValueRoots.has(root) && /^-?\d+$/.test(rest)) {
|
|
435
|
+
return { property: prefixToCSSProperty[root] ?? root, value: rest, root, kind: 'numeric' };
|
|
436
|
+
}
|
|
437
|
+
if (root === 'opacity' && /^\d{1,3}$/.test(rest)) {
|
|
438
|
+
return { property: 'opacity', value: String(Number(rest) / 100), root, kind: 'numeric' };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Bare design-token color (Meno convention): bg-primary → var(--primary),
|
|
442
|
+
// text-text → var(--text), border-border → var(--border). Gated on the
|
|
443
|
+
// project's `knownTokens` so `bg-<word>` is read as a token ONLY for a DEFINED
|
|
444
|
+
// token name — foreign / authored classes (`text-wrapper`, imported `bg-dark`)
|
|
445
|
+
// are left unrecognized. The token gate also resolves the `h…`-token vs
|
|
446
|
+
// hash-class ambiguity (a real hash class's `rest` is never a defined token).
|
|
447
|
+
if (COLOR_TOKEN_ROOTS.has(root) && knownTokens?.has(rest)) {
|
|
448
|
+
const value = `var(--${rest})`;
|
|
449
|
+
return { property: resolveRootProperty(root, value), value, root, kind: 'var' };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Hash fallback: value lives in the style-value registry
|
|
453
|
+
if (HASH_VALUE_RE.test(rest)) {
|
|
454
|
+
return { property: prefixToCSSProperty[root] ?? root, value: null, root, kind: 'hash' };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return null;
|
|
458
|
+
}
|
package/lib/shared/utils.test.ts
CHANGED
|
@@ -244,18 +244,10 @@ describe('utils', () => {
|
|
|
244
244
|
|
|
245
245
|
test('should handle complex children array', () => {
|
|
246
246
|
const node = {
|
|
247
|
-
children: [
|
|
248
|
-
{ element: 'div' },
|
|
249
|
-
{ element: 'span' },
|
|
250
|
-
'text',
|
|
251
|
-
],
|
|
247
|
+
children: [{ element: 'div' }, { element: 'span' }, 'text'],
|
|
252
248
|
};
|
|
253
249
|
normalizeChildrenArray(node);
|
|
254
|
-
expect(node.children).toEqual([
|
|
255
|
-
{ element: 'div' },
|
|
256
|
-
{ element: 'span' },
|
|
257
|
-
'text',
|
|
258
|
-
]);
|
|
250
|
+
expect(node.children).toEqual([{ element: 'div' }, { element: 'span' }, 'text']);
|
|
259
251
|
});
|
|
260
252
|
});
|
|
261
253
|
});
|
package/lib/shared/utils.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function deepClone<T>(obj: T): T {
|
|
|
20
20
|
* @param ms - Milliseconds to wait
|
|
21
21
|
*/
|
|
22
22
|
export function wait(ms: number): Promise<void> {
|
|
23
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
23
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -50,7 +50,20 @@ export function isEditableElement(): boolean {
|
|
|
50
50
|
// For inputs, only block shortcuts for text-accepting types
|
|
51
51
|
if (activeElement.tagName === 'INPUT') {
|
|
52
52
|
const inputType = (activeElement as HTMLInputElement).type.toLowerCase();
|
|
53
|
-
const textInputTypes = [
|
|
53
|
+
const textInputTypes = [
|
|
54
|
+
'text',
|
|
55
|
+
'password',
|
|
56
|
+
'email',
|
|
57
|
+
'number',
|
|
58
|
+
'search',
|
|
59
|
+
'tel',
|
|
60
|
+
'url',
|
|
61
|
+
'date',
|
|
62
|
+
'time',
|
|
63
|
+
'datetime-local',
|
|
64
|
+
'month',
|
|
65
|
+
'week',
|
|
66
|
+
];
|
|
54
67
|
// Allow shortcuts for: checkbox, radio, button, submit, reset, color, file, range, hidden
|
|
55
68
|
return textInputTypes.includes(inputType);
|
|
56
69
|
}
|
|
@@ -59,10 +72,7 @@ export function isEditableElement(): boolean {
|
|
|
59
72
|
// This handles rich text editors where focus may be on an internal element
|
|
60
73
|
let element: Element | null = activeElement;
|
|
61
74
|
while (element) {
|
|
62
|
-
if (
|
|
63
|
-
element.hasAttribute('contenteditable') ||
|
|
64
|
-
(element as HTMLElement).isContentEditable
|
|
65
|
-
) {
|
|
75
|
+
if (element.hasAttribute('contenteditable') || (element as HTMLElement).isContentEditable) {
|
|
66
76
|
return true;
|
|
67
77
|
}
|
|
68
78
|
element = element.parentElement;
|
|
@@ -75,7 +85,7 @@ export function isEditableElement(): boolean {
|
|
|
75
85
|
* Extract the parent path from a tree path
|
|
76
86
|
* Example: root_0_children_0 -> root_0, root_0_children_0_children_1 -> root_0_children_0
|
|
77
87
|
* Also accepts array paths: [0,0] -> "root_0", [0,0,1] -> "root_0_children_0"
|
|
78
|
-
*
|
|
88
|
+
*
|
|
79
89
|
* @param treePath - The tree path (string or Path array)
|
|
80
90
|
* @param rootPath - The root path constant (default: 'root_0')
|
|
81
91
|
* @returns The parent path, or rootPath if no parent found
|
|
@@ -83,12 +93,12 @@ export function isEditableElement(): boolean {
|
|
|
83
93
|
export function getParentPathFromTreePath(treePath: string | Path, rootPath: string = ROOT_0_STRING): string {
|
|
84
94
|
// Convert to array if needed
|
|
85
95
|
const pathArray = typeof treePath === 'string' ? stringToPath(treePath) : treePath;
|
|
86
|
-
|
|
96
|
+
|
|
87
97
|
// Handle root paths
|
|
88
98
|
if (isRootPathArray(pathArray)) {
|
|
89
99
|
return rootPath;
|
|
90
100
|
}
|
|
91
|
-
|
|
101
|
+
|
|
92
102
|
// Use array operations to get parent path
|
|
93
103
|
const parentArray = getParentPath(pathArray);
|
|
94
104
|
return pathToLegacyString(parentArray);
|
|
@@ -109,4 +119,3 @@ export function normalizeChildrenArray(node: { children?: any[] | any }): void {
|
|
|
109
119
|
}
|
|
110
120
|
// If already an array, no change needed
|
|
111
121
|
}
|
|
112
|
-
|
|
@@ -14,7 +14,8 @@ export const CMS_COLLECTION_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
|
14
14
|
/**
|
|
15
15
|
* Zod schema for CMS collection ID validation
|
|
16
16
|
*/
|
|
17
|
-
export const CMSCollectionIdSchema = z
|
|
17
|
+
export const CMSCollectionIdSchema = z
|
|
18
|
+
.string()
|
|
18
19
|
.min(1, 'Collection ID is required')
|
|
19
20
|
.regex(CMS_COLLECTION_ID_REGEX, 'Collection ID must contain only letters, numbers, underscores, and hyphens');
|
|
20
21
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { validateComment } from './commentValidators';
|
|
3
|
+
|
|
4
|
+
/** A minimal valid comment; callers override `anchor` to exercise schema variants. */
|
|
5
|
+
function baseComment(anchor: Record<string, unknown>): Record<string, unknown> {
|
|
6
|
+
return {
|
|
7
|
+
_id: 'c1',
|
|
8
|
+
_filename: 'c1.json',
|
|
9
|
+
_pagePath: 'index',
|
|
10
|
+
_seq: 1,
|
|
11
|
+
_createdAt: '2026-01-01T00:00:00.000Z',
|
|
12
|
+
_updatedAt: '2026-01-01T00:00:00.000Z',
|
|
13
|
+
_commitSha: null,
|
|
14
|
+
anchor,
|
|
15
|
+
status: 'open',
|
|
16
|
+
thread: [
|
|
17
|
+
{
|
|
18
|
+
id: 't1',
|
|
19
|
+
author: { login: 'local' },
|
|
20
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
21
|
+
text: 'hi',
|
|
22
|
+
statusChange: null,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const pageLevelAnchor = {
|
|
29
|
+
nodePath: [0, 1],
|
|
30
|
+
nodeIdentity: { kind: 'node', name: 'div' },
|
|
31
|
+
offsetXPercent: 0.5,
|
|
32
|
+
offsetYPercent: 0.5,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('validateComment — element anchor identity', () => {
|
|
36
|
+
test('accepts a comment WITHOUT a cmsItemIndexPath (element not in a list)', () => {
|
|
37
|
+
const result = validateComment(baseComment(pageLevelAnchor));
|
|
38
|
+
expect(result.valid).toBe(true);
|
|
39
|
+
if (result.valid) expect(result.data.anchor.cmsItemIndexPath).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('accepts and round-trips a comment WITH a cmsItemIndexPath', () => {
|
|
43
|
+
const cmsItemIndexPath = [0, 2];
|
|
44
|
+
const result = validateComment(baseComment({ ...pageLevelAnchor, cmsItemIndexPath }));
|
|
45
|
+
expect(result.valid).toBe(true);
|
|
46
|
+
if (result.valid) expect(result.data.anchor.cmsItemIndexPath).toEqual(cmsItemIndexPath);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('rejects a malformed cmsItemIndexPath (non-number entries)', () => {
|
|
50
|
+
const result = validateComment(baseComment({ ...pageLevelAnchor, cmsItemIndexPath: ['a', 'b'] }));
|
|
51
|
+
expect(result.valid).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -23,8 +23,19 @@ export const CommentNodeIdentitySchema = z.object({
|
|
|
23
23
|
label: z.string().optional(),
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
export const CommentNodeRefSegmentSchema = z.object({
|
|
27
|
+
kind: z.enum(['component', 'node']),
|
|
28
|
+
name: z.string().min(1),
|
|
29
|
+
nth: z.number().int().nonnegative(),
|
|
30
|
+
});
|
|
31
|
+
|
|
26
32
|
export const CommentAnchorSchema = z.object({
|
|
33
|
+
// Type + occurrence path (move-resilient source of truth). Must be declared
|
|
34
|
+
// (z.object strips unknown keys) so it round-trips to disk.
|
|
35
|
+
nodeRef: z.array(CommentNodeRefSegmentSchema).optional(),
|
|
27
36
|
nodePath: z.array(z.number().int().nonnegative()),
|
|
37
|
+
// CMS-list item path disambiguating which list copy the element belongs to.
|
|
38
|
+
cmsItemIndexPath: z.array(z.number().int().nonnegative()).optional(),
|
|
28
39
|
nodeIdentity: CommentNodeIdentitySchema,
|
|
29
40
|
offsetXPercent: z.number().min(0).max(1),
|
|
30
41
|
offsetYPercent: z.number().min(0).max(1),
|
|
@@ -53,7 +64,7 @@ export const CommentSchema = z.object({
|
|
|
53
64
|
});
|
|
54
65
|
|
|
55
66
|
function zodToValidationErrors(zodError: ZodError): ValidationError[] {
|
|
56
|
-
return zodError.errors.map(err => ({
|
|
67
|
+
return zodError.errors.map((err) => ({
|
|
57
68
|
path: err.path.join('.') || 'root',
|
|
58
69
|
message: err.message,
|
|
59
70
|
code: 'INVALID_TYPE' as const,
|