meno-core 1.0.48 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/build-static.js +7 -7
  2. package/dist/chunks/{chunk-UUA5LEWF.js → chunk-6IVUG7FY.js} +138 -7
  3. package/dist/chunks/chunk-6IVUG7FY.js.map +7 -0
  4. package/dist/chunks/{chunk-B2RTLDXY.js → chunk-AZQYF6KE.js} +132 -1
  5. package/dist/chunks/chunk-AZQYF6KE.js.map +7 -0
  6. package/dist/chunks/{chunk-NKUV77SR.js → chunk-CHD5UCFF.js} +21 -9
  7. package/dist/chunks/{chunk-NKUV77SR.js.map → chunk-CHD5UCFF.js.map} +2 -2
  8. package/dist/chunks/{chunk-TPQ7APVQ.js → chunk-EQYDSPBB.js} +418 -62
  9. package/dist/chunks/chunk-EQYDSPBB.js.map +7 -0
  10. package/dist/chunks/{chunk-RQSTH2BS.js → chunk-H4JSCDNW.js} +2 -2
  11. package/dist/chunks/{chunk-EK4KESLU.js → chunk-J23ZX5AP.js} +8 -2
  12. package/dist/chunks/{chunk-EK4KESLU.js.map → chunk-J23ZX5AP.js.map} +2 -2
  13. package/dist/chunks/{chunk-D5E3OKSL.js → chunk-JER5NQVM.js} +5 -5
  14. package/dist/chunks/{chunk-BJRKEPMP.js → chunk-KPU2XHOS.js} +5 -2
  15. package/dist/chunks/{chunk-BJRKEPMP.js.map → chunk-KPU2XHOS.js.map} +2 -2
  16. package/dist/chunks/{chunk-NP76N4HQ.js → chunk-LKAGAQ3M.js} +2 -2
  17. package/dist/chunks/{chunk-3FHJUHAS.js → chunk-S2CX6HFM.js} +260 -25
  18. package/dist/chunks/chunk-S2CX6HFM.js.map +7 -0
  19. package/dist/chunks/{configService-IGJEC3MC.js → configService-CCA6AIDI.js} +3 -3
  20. package/dist/entries/server-router.js +9 -9
  21. package/dist/entries/server-router.js.map +2 -2
  22. package/dist/lib/client/index.js +54 -20
  23. package/dist/lib/client/index.js.map +3 -3
  24. package/dist/lib/server/index.js +9 -9
  25. package/dist/lib/shared/index.js +46 -10
  26. package/dist/lib/shared/index.js.map +3 -3
  27. package/entries/server-router.tsx +6 -2
  28. package/lib/client/core/ComponentBuilder.ts +8 -1
  29. package/lib/client/core/builders/embedBuilder.ts +15 -2
  30. package/lib/client/core/builders/linkNodeBuilder.ts +15 -2
  31. package/lib/client/core/builders/localeListBuilder.ts +17 -6
  32. package/lib/client/styles/StyleInjector.ts +3 -2
  33. package/lib/client/theme.ts +4 -4
  34. package/lib/server/cssGenerator.test.ts +64 -1
  35. package/lib/server/cssGenerator.ts +48 -9
  36. package/lib/server/providers/fileSystemCMSProvider.test.ts +163 -0
  37. package/lib/server/providers/fileSystemCMSProvider.ts +200 -11
  38. package/lib/server/routes/index.ts +1 -1
  39. package/lib/server/routes/pages.ts +23 -1
  40. package/lib/server/services/cmsService.test.ts +246 -0
  41. package/lib/server/services/cmsService.ts +122 -5
  42. package/lib/server/services/configService.ts +5 -0
  43. package/lib/server/ssr/attributeBuilder.ts +41 -0
  44. package/lib/server/ssr/htmlGenerator.test.ts +113 -0
  45. package/lib/server/ssr/htmlGenerator.ts +51 -4
  46. package/lib/server/ssr/liveReloadIntegration.test.ts +209 -0
  47. package/lib/server/ssr/ssrRenderer.test.ts +306 -0
  48. package/lib/server/ssr/ssrRenderer.ts +182 -44
  49. package/lib/shared/cssGeneration.test.ts +267 -1
  50. package/lib/shared/cssGeneration.ts +240 -18
  51. package/lib/shared/cssProperties.test.ts +247 -1
  52. package/lib/shared/cssProperties.ts +196 -6
  53. package/lib/shared/interfaces/contentProvider.ts +39 -6
  54. package/lib/shared/pathSecurity.ts +16 -0
  55. package/lib/shared/responsiveScaling.test.ts +143 -0
  56. package/lib/shared/responsiveScaling.ts +253 -2
  57. package/lib/shared/themeDefaults.test.ts +3 -3
  58. package/lib/shared/themeDefaults.ts +3 -3
  59. package/lib/shared/types/cms.ts +28 -3
  60. package/lib/shared/types/index.ts +1 -0
  61. package/lib/shared/utilityClassConfig.ts +3 -0
  62. package/lib/shared/utilityClassMapper.test.ts +123 -0
  63. package/lib/shared/utilityClassMapper.ts +179 -8
  64. package/lib/shared/validation/schemas.ts +15 -1
  65. package/lib/shared/validation/validators.ts +26 -1
  66. package/package.json +1 -1
  67. package/dist/chunks/chunk-3FHJUHAS.js.map +0 -7
  68. package/dist/chunks/chunk-B2RTLDXY.js.map +0 -7
  69. package/dist/chunks/chunk-TPQ7APVQ.js.map +0 -7
  70. package/dist/chunks/chunk-UUA5LEWF.js.map +0 -7
  71. /package/dist/chunks/{chunk-RQSTH2BS.js.map → chunk-H4JSCDNW.js.map} +0 -0
  72. /package/dist/chunks/{chunk-D5E3OKSL.js.map → chunk-JER5NQVM.js.map} +0 -0
  73. /package/dist/chunks/{chunk-NP76N4HQ.js.map → chunk-LKAGAQ3M.js.map} +0 -0
  74. /package/dist/chunks/{configService-IGJEC3MC.js.map → configService-CCA6AIDI.js.map} +0 -0
@@ -43,6 +43,7 @@ mock.module('./imageMetadata', () => ({
43
43
  mock.module('../services/configService', () => ({
44
44
  configService: {
45
45
  getImageFormat: () => mockImageFormat,
46
+ getResponsiveScales: () => ({ enabled: false, baseReference: 16 }),
46
47
  load: async () => {},
47
48
  isLoaded: () => true,
48
49
  reset: () => {},
@@ -3109,6 +3110,203 @@ describe('ssrRenderer', () => {
3109
3110
  expect(html).toContain('Author a1');
3110
3111
  expect(html).toContain('Author a2');
3111
3112
  });
3113
+
3114
+ test('inner list items dotted-ref resolves against host component prop', async () => {
3115
+ // {{config.0.tagIds}} is a dotted template — processStructure leaves it literal
3116
+ // (hasItemTemplates returns true for `\w+\.\w+`). The SSR items resolver must
3117
+ // see the host component's `config` prop in its scope.
3118
+ const calledWith: string[][] = [];
3119
+ const mockCmsService = {
3120
+ getItemsByIds: async (_collection: string, ids: string[]) => {
3121
+ calledWith.push(ids);
3122
+ return ids.map(id => ({ _id: id, name: `Tag ${id}` }));
3123
+ },
3124
+ getSchema: () => undefined,
3125
+ };
3126
+
3127
+ const Card: ComponentDefinition = {
3128
+ component: {
3129
+ interface: {
3130
+ config: { type: 'list' as any, default: [] },
3131
+ },
3132
+ structure: {
3133
+ type: 'list',
3134
+ sourceType: 'collection',
3135
+ source: 'tags',
3136
+ itemAs: 'tag',
3137
+ items: '{{config.0.tagIds}}',
3138
+ children: [
3139
+ { type: 'node', tag: 'span', children: '{{tag.name}}' },
3140
+ ],
3141
+ },
3142
+ },
3143
+ };
3144
+
3145
+ const node = {
3146
+ type: 'component',
3147
+ component: 'Card',
3148
+ props: {
3149
+ config: [{ tagIds: ['t1', 't2'] }],
3150
+ },
3151
+ };
3152
+
3153
+ const html = await render(node, {
3154
+ globalComponents: { Card },
3155
+ cmsService: mockCmsService,
3156
+ });
3157
+
3158
+ expect(calledWith).toEqual([['t1', 't2']]);
3159
+ expect(html).toContain('Tag t1');
3160
+ expect(html).toContain('Tag t2');
3161
+ });
3162
+
3163
+ test('inner list filter dotted-ref resolves against host component prop', async () => {
3164
+ // {{config.0.collection}} is a dotted template — processStructure leaves it literal.
3165
+ // SSR's resolveFilterTemplates must see the host component's `config` prop.
3166
+ const queries: any[] = [];
3167
+ const mockCmsService = {
3168
+ queryItems: async (q: any) => {
3169
+ queries.push(q);
3170
+ return [{ _id: 'p1', title: 'Hello' }];
3171
+ },
3172
+ getSchema: () => undefined,
3173
+ };
3174
+
3175
+ const Card: ComponentDefinition = {
3176
+ component: {
3177
+ interface: {
3178
+ config: { type: 'list' as any, default: [] },
3179
+ },
3180
+ structure: {
3181
+ type: 'list',
3182
+ sourceType: 'collection',
3183
+ source: 'posts',
3184
+ itemAs: 'post',
3185
+ filter: { category: '{{config.0.collection}}' },
3186
+ children: [
3187
+ { type: 'node', tag: 'div', children: '{{post.title}}' },
3188
+ ],
3189
+ },
3190
+ },
3191
+ };
3192
+
3193
+ const node = {
3194
+ type: 'component',
3195
+ component: 'Card',
3196
+ props: {
3197
+ config: [{ collection: 'news' }],
3198
+ },
3199
+ };
3200
+
3201
+ const html = await render(node, {
3202
+ globalComponents: { Card },
3203
+ cmsService: mockCmsService,
3204
+ });
3205
+
3206
+ expect(queries.length).toBe(1);
3207
+ expect(queries[0].filter).toEqual({ category: 'news' });
3208
+ expect(html).toContain('Hello');
3209
+ });
3210
+
3211
+ test('parent list loop variable wins over a colliding host component prop', async () => {
3212
+ // Outer CMS list iterates with itemAs: 'tag', producing a templateContext entry
3213
+ // named `tag` set to the current item. The host Card also receives a prop named
3214
+ // `tag` set to a fake value. The inner list's items: {{tag.id}} must resolve to
3215
+ // the parent loop variable's id (precedence: parent loop variable > component props).
3216
+ const calledWith: string[][] = [];
3217
+ const mockCmsService = {
3218
+ // Outer iteration: itemAs: 'tag' makes templateContext.tag = item, so the
3219
+ // inner template `{{tag.id}}` looks up item.id directly.
3220
+ queryItems: async () => [
3221
+ { _id: 'p1', id: 'real-1' },
3222
+ { _id: 'p2', id: 'real-2' },
3223
+ ],
3224
+ getItemsByIds: async (_collection: string, ids: string[]) => {
3225
+ calledWith.push(ids);
3226
+ return ids.map(id => ({ _id: id, name: `Item ${id}` }));
3227
+ },
3228
+ getSchema: () => undefined,
3229
+ };
3230
+
3231
+ const Card: ComponentDefinition = {
3232
+ component: {
3233
+ interface: {
3234
+ // type: 'list' so the array prop value passes validation. The prop
3235
+ // `tag` will land in componentResolvedProps; the parent loop var
3236
+ // `tag` (set to the current outer item) will land in templateContext.
3237
+ tag: { type: 'list' as any, default: [] },
3238
+ },
3239
+ structure: {
3240
+ type: 'list',
3241
+ sourceType: 'collection',
3242
+ source: 'tags',
3243
+ itemAs: 'inner',
3244
+ items: '{{tag.id}}',
3245
+ children: [
3246
+ { type: 'node', tag: 'span', children: '{{inner.name}}' },
3247
+ ],
3248
+ },
3249
+ },
3250
+ };
3251
+
3252
+ const node = {
3253
+ type: 'list',
3254
+ sourceType: 'collection',
3255
+ source: 'posts',
3256
+ itemAs: 'tag',
3257
+ children: [
3258
+ {
3259
+ type: 'component',
3260
+ component: 'Card',
3261
+ props: {
3262
+ // Static fake value for the colliding prop; the inner template
3263
+ // must resolve to the parent loop var, not this.
3264
+ tag: [{ id: 'fake-prop-id' }],
3265
+ },
3266
+ },
3267
+ ],
3268
+ };
3269
+
3270
+ await render(node, {
3271
+ globalComponents: { Card },
3272
+ cmsService: mockCmsService,
3273
+ });
3274
+
3275
+ expect(calledWith).toEqual([['real-1'], ['real-2']]);
3276
+ });
3277
+
3278
+ test('page-root filter does not silently resolve {{cms.X}} (regression)', async () => {
3279
+ // Without a host component, ctx.componentResolvedProps is undefined and
3280
+ // ctx.templateContext is undefined → buildListResolutionScope returns
3281
+ // undefined → resolveFilterTemplates leaves filter values literal.
3282
+ // This pins down the documented behavior: the cmsContext namespace does
3283
+ // NOT silently merge into filter scope.
3284
+ const queries: any[] = [];
3285
+ const mockCmsService = {
3286
+ queryItems: async (q: any) => {
3287
+ queries.push(q);
3288
+ return [];
3289
+ },
3290
+ getSchema: () => undefined,
3291
+ };
3292
+
3293
+ const node = {
3294
+ type: 'list',
3295
+ sourceType: 'collection',
3296
+ source: 'posts',
3297
+ filter: { category: '{{cms.something}}' },
3298
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
3299
+ };
3300
+
3301
+ // cmsContext is provided, but page-root filter must not pull from it.
3302
+ await render(node, {
3303
+ cmsService: mockCmsService,
3304
+ cmsContext: { cms: { something: 'shouldNotBeUsed' } },
3305
+ });
3306
+
3307
+ expect(queries.length).toBe(1);
3308
+ expect(queries[0].filter).toEqual({ category: '{{cms.something}}' });
3309
+ });
3112
3310
  });
3113
3311
 
3114
3312
  // -----------------------------------------------------------------------
@@ -4019,4 +4217,112 @@ describe('ssrRenderer', () => {
4019
4217
  expect(html).toContain('href="/fr/a-propos"');
4020
4218
  });
4021
4219
  });
4220
+
4221
+ // Editor attrs are emitted only when buildComponentHTML's injectEditorAttrs flag is on.
4222
+ // These attributes (data-element-path, data-cms-item-index, data-cms-context,
4223
+ // data-component-root, data-parent-component, data-component-context) drive
4224
+ // XRayOverlay and the click-to-select handler in static (SSR) preview mode.
4225
+ describe('buildComponentHTML - editor attrs (preview-only)', () => {
4226
+ test('omits data-element-path by default', async () => {
4227
+ const node = { type: 'node', tag: 'div', children: ['hello'] };
4228
+ const result = await buildComponentHTML(node as any);
4229
+ expect(result.html).not.toContain('data-element-path');
4230
+ });
4231
+
4232
+ test('emits data-element-path on every element when injectEditorAttrs is true', async () => {
4233
+ const node = {
4234
+ type: 'node',
4235
+ tag: 'div',
4236
+ children: [
4237
+ { type: 'node', tag: 'span', children: ['hi'] },
4238
+ { type: 'node', tag: 'a', attributes: { href: '/x' }, children: ['link'] },
4239
+ ],
4240
+ };
4241
+ const result = await buildComponentHTML(
4242
+ node as any,
4243
+ {}, {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4244
+ );
4245
+ expect(result.html).toContain('data-element-path="0"');
4246
+ expect(result.html).toContain('data-element-path="0,0"');
4247
+ expect(result.html).toContain('data-element-path="0,1"');
4248
+ });
4249
+
4250
+ test('emits data-cms-item-index and data-cms-context for elements inside a list', async () => {
4251
+ // List in prop mode under a component resolves items from componentResolvedProps.
4252
+ const Card: ComponentDefinition = {
4253
+ component: {
4254
+ interface: {
4255
+ items: { type: 'list', default: [{ label: 'A' }, { label: 'B' }] } as any,
4256
+ },
4257
+ structure: {
4258
+ type: 'node',
4259
+ tag: 'div',
4260
+ children: [{
4261
+ type: 'list',
4262
+ sourceType: 'prop',
4263
+ source: 'items',
4264
+ children: [{ type: 'node', tag: 'p', children: '{{item.label}}' }],
4265
+ }],
4266
+ },
4267
+ },
4268
+ };
4269
+ const result = await buildComponentHTML(
4270
+ { type: 'component', component: 'Card' } as any,
4271
+ { Card },
4272
+ {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4273
+ );
4274
+ expect(result.html).toContain('data-cms-item-index="0"');
4275
+ expect(result.html).toContain('data-cms-item-index="1"');
4276
+ expect(result.html).toContain('data-cms-context=');
4277
+ });
4278
+
4279
+ test('marks the component instance root with data-component-root', async () => {
4280
+ const Card: ComponentDefinition = {
4281
+ component: {
4282
+ interface: {},
4283
+ structure: { type: 'node', tag: 'div', children: ['inner'] },
4284
+ },
4285
+ };
4286
+ const result = await buildComponentHTML(
4287
+ { type: 'component', component: 'Card' } as any,
4288
+ { Card },
4289
+ {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4290
+ );
4291
+ expect(result.html).toContain('data-component-root="true"');
4292
+ expect(result.html).toContain('data-component-context="Card"');
4293
+ });
4294
+
4295
+ test('emits page-absolute data-element-path for elements inside a component', async () => {
4296
+ // Component has a single wrapper <div> with a <span> child. The page hosts
4297
+ // the component as the only child of its root <div>, so:
4298
+ // page root <div> → [0]
4299
+ // Card root <div> → [0,0]
4300
+ // inner <span> → [0,0,0]
4301
+ // Before the fix SSR reset elementPath inside components, so the inner
4302
+ // span landed at [0] (component-relative) and XRay couldn't find it.
4303
+ const Card: ComponentDefinition = {
4304
+ component: {
4305
+ interface: {},
4306
+ structure: {
4307
+ type: 'node',
4308
+ tag: 'div',
4309
+ children: [{ type: 'node', tag: 'span', children: 'inner' }],
4310
+ },
4311
+ },
4312
+ };
4313
+ const page = {
4314
+ type: 'node',
4315
+ tag: 'div',
4316
+ children: [{ type: 'component', component: 'Card' }],
4317
+ };
4318
+ const result = await buildComponentHTML(
4319
+ page as any,
4320
+ { Card },
4321
+ {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4322
+ );
4323
+ expect(result.html).toContain('data-element-path="0"');
4324
+ expect(result.html).toContain('data-element-path="0,0"');
4325
+ expect(result.html).toContain('data-element-path="0,0,0"');
4326
+ });
4327
+ });
4022
4328
  });