@lowdefy/build 4.7.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/build/addDefaultPages/404.js +8 -2
  2. package/dist/build/buildApi/buildRoutine/validateStep.js +7 -5
  3. package/dist/build/buildApi/validateEndpoint.js +6 -5
  4. package/dist/build/buildConnections.js +6 -0
  5. package/dist/build/buildImports/buildIconImports.js +5 -1
  6. package/dist/build/buildImports/buildImportsDev.js +1 -6
  7. package/dist/build/buildImports/buildImportsProd.js +1 -6
  8. package/dist/build/buildImports/validateIconImports.js +65 -0
  9. package/dist/build/buildJs/jsMapParser.js +5 -2
  10. package/dist/build/buildPages/buildBlock/buildBlock.js +12 -4
  11. package/dist/build/buildPages/buildBlock/buildEvents.js +34 -1
  12. package/dist/build/buildPages/buildBlock/buildRequests.js +7 -5
  13. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +9 -9
  14. package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
  15. package/dist/build/buildPages/buildBlock/moveAreasToSlots.js +31 -0
  16. package/dist/build/buildPages/buildBlock/{moveSkeletonBlocksToArea.js → moveSkeletonBlocksToSlot.js} +8 -8
  17. package/dist/build/buildPages/buildBlock/{moveSubBlocksToArea.js → moveSubBlocksToSlot.js} +3 -3
  18. package/dist/build/buildPages/buildBlock/normalizeClassAndStyles.js +124 -0
  19. package/dist/build/buildPages/buildBlock/normalizeLayout.js +68 -0
  20. package/dist/build/buildPages/buildBlock/setBlockId.js +7 -1
  21. package/dist/build/buildPages/buildBlock/validateSlots.js +34 -0
  22. package/dist/build/buildPages/buildPage.js +23 -1
  23. package/dist/build/buildRefs/addLineNumbers.js +76 -0
  24. package/dist/build/{buildImports/buildStyleImports.js → buildRefs/getLineNumber.js} +4 -10
  25. package/dist/build/buildRefs/getRefContent.js +9 -1
  26. package/dist/build/buildRefs/parseRefContent.js +4 -66
  27. package/dist/build/buildTypes.js +4 -2
  28. package/dist/build/cleanBuildDirectory.js +3 -1
  29. package/dist/build/collectPageContent.js +57 -0
  30. package/dist/build/jit/buildPageJit.js +14 -3
  31. package/dist/build/jit/collectSkeletonSourceFiles.js +75 -0
  32. package/dist/build/jit/extractIconData.js +16 -1
  33. package/dist/build/jit/pageContentKeys.js +1 -0
  34. package/dist/build/jit/shallowBuild.js +34 -1
  35. package/dist/build/jit/stripPageContent.js +29 -0
  36. package/dist/build/jit/writePageJit.js +9 -1
  37. package/dist/build/testSchema.js +3 -0
  38. package/dist/build/writePluginImports/collectBlockSourceContent.js +65 -0
  39. package/dist/build/writePluginImports/writeActionSchemaMap.js +1 -1
  40. package/dist/build/writePluginImports/writeBlockSchemaMap.js +45 -7
  41. package/dist/build/writePluginImports/writeGlobalsCss.js +126 -0
  42. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +1 -1
  43. package/dist/build/writePluginImports/writePluginImports.js +7 -2
  44. package/dist/build/writeTheme.js +28 -0
  45. package/dist/createContext.js +2 -0
  46. package/dist/defaultTypesMap.js +1693 -837
  47. package/dist/index.js +16 -0
  48. package/dist/lowdefySchema.js +100 -0
  49. package/dist/scripts/generateDefaultTypes.js +5 -10
  50. package/dist/test-utils/runBuild.js +3 -0
  51. package/dist/test-utils/runBuildForSnapshots.js +5 -2
  52. package/dist/test-utils/testContext.js +2 -1
  53. package/dist/utils/createHandleWarning.js +3 -0
  54. package/dist/utils/createPluginTypesMap.js +5 -9
  55. package/dist/utils/validateId.js +24 -0
  56. package/package.json +47 -47
  57. package/dist/build/writePluginImports/writeStyleImports.js +0 -34
@@ -16,14 +16,20 @@
16
16
  id: '404',
17
17
  type: 'Result',
18
18
  style: {
19
- minHeight: '100vh'
19
+ minHeight: '100vh',
20
+ background: 'var(--ant-color-bg-layout)'
20
21
  },
21
22
  properties: {
22
23
  status: 'info',
24
+ icon: {
25
+ name: 'AiOutlineFileSearch',
26
+ size: 80,
27
+ color: 'var(--ant-color-primary)'
28
+ },
23
29
  title: '404',
24
30
  subTitle: 'Sorry, the page you are visiting does not exist.'
25
31
  },
26
- areas: {
32
+ slots: {
27
33
  extra: {
28
34
  blocks: [
29
35
  {
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import { ConfigError } from '@lowdefy/errors';
17
+ import validateId from '../../../utils/validateId.js';
17
18
  function validateStep(step, { endpointId }) {
18
19
  const configKey = step['~k'];
19
20
  if (Object.keys(step).length === 0) {
@@ -32,11 +33,12 @@ function validateStep(step, { endpointId }) {
32
33
  configKey
33
34
  });
34
35
  }
35
- if (step.id.includes('.')) {
36
- throw new ConfigError(`Step id "${step.id}" at endpoint "${endpointId}" should not include a period (".").`, {
37
- configKey
38
- });
39
- }
36
+ validateId({
37
+ id: step.id,
38
+ field: 'Step id',
39
+ location: `endpoint "${endpointId}"`,
40
+ configKey
41
+ });
40
42
  if (type.isNone(step.type)) {
41
43
  throw new ConfigError(`Step type is not defined at "${step.id}" on endpoint "${endpointId}".`, {
42
44
  configKey
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import { ConfigError } from '@lowdefy/errors';
17
+ import validateId from '../../utils/validateId.js';
17
18
  function validateEndpoint({ endpoint, index, checkDuplicateEndpointId }) {
18
19
  const configKey = endpoint['~k'];
19
20
  if (type.isUndefined(endpoint.id)) {
@@ -27,11 +28,11 @@ function validateEndpoint({ endpoint, index, checkDuplicateEndpointId }) {
27
28
  configKey
28
29
  });
29
30
  }
30
- if (endpoint.id.includes('.')) {
31
- throw new ConfigError(`Endpoint id "${endpoint.id}" should not include a period (".").`, {
32
- configKey
33
- });
34
- }
31
+ validateId({
32
+ id: endpoint.id,
33
+ field: 'Endpoint id',
34
+ configKey
35
+ });
35
36
  if (type.isUndefined(endpoint.type)) {
36
37
  throw new ConfigError(`Endpoint type is not defined at "${endpoint.id}".`, {
37
38
  configKey
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import countOperators from '../utils/countOperators.js';
16
16
  import createCheckDuplicateId from '../utils/createCheckDuplicateId.js';
17
+ import validateId from '../utils/validateId.js';
17
18
  function buildConnections({ components, context }) {
18
19
  // Store connection IDs for validation in buildRequests
19
20
  context.connectionIds = new Set();
@@ -29,6 +30,11 @@ function buildConnections({ components, context }) {
29
30
  id: connection.id,
30
31
  configKey
31
32
  });
33
+ validateId({
34
+ id: connection.id,
35
+ field: 'Connection id',
36
+ configKey
37
+ });
32
38
  // Track type usage for buildTypes validation
33
39
  context.typeCounters.connections.increment(connection.type, configKey);
34
40
  // Store connectionId for request validation and rename id
@@ -13,6 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import iconPackages from './iconPackages.js';
16
+ import validateIconImports from './validateIconImports.js';
16
17
  function getConfigIcons({ components, icons, regex }) {
17
18
  [
18
19
  ...JSON.stringify(components.global || {}).matchAll(regex)
@@ -55,6 +56,9 @@ function buildIconImports({ blocks, components, context, defaults = {} }) {
55
56
  package: iconPackage
56
57
  });
57
58
  });
58
- return iconImports;
59
+ return validateIconImports({
60
+ iconImports,
61
+ context
62
+ });
59
63
  }
60
64
  export default buildIconImports;
@@ -13,7 +13,6 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import buildIconImports from './buildIconImports.js';
16
- import buildStyleImports from './buildStyleImports.js';
17
16
  import defaultIconsDev from './defaultIconsDev.js';
18
17
  function getPluginPackages({ components }) {
19
18
  const pluginPackages = new Set();
@@ -96,11 +95,7 @@ function buildImportsDev({ components, context }) {
96
95
  pluginPackages,
97
96
  map: context.typesMap.operators.server
98
97
  })
99
- },
100
- styles: buildStyleImports({
101
- blocks,
102
- context
103
- })
98
+ }
104
99
  };
105
100
  }
106
101
  export default buildImportsDev;
@@ -13,7 +13,6 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import buildIconImports from './buildIconImports.js';
16
- import buildStyleImports from './buildStyleImports.js';
17
16
  import defaultIconsProd from './defaultIconsProd.js';
18
17
  function buildImportClassProd(types) {
19
18
  return Object.entries(types).map(([typeName, type])=>({
@@ -44,11 +43,7 @@ function buildImportsProd({ components, context }) {
44
43
  operators: {
45
44
  client: buildImportClassProd(components.types.operators.client),
46
45
  server: buildImportClassProd(components.types.operators.server)
47
- },
48
- styles: buildStyleImports({
49
- blocks,
50
- context
51
- })
46
+ }
52
47
  };
53
48
  }
54
49
  export default buildImportsProd;
@@ -0,0 +1,65 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { createRequire } from 'module';
16
+ import path from 'path';
17
+ import { ConfigWarning } from '@lowdefy/errors';
18
+ import findSimilarString from '../../utils/findSimilarString.js';
19
+ function validateIconImports({ iconImports, context }) {
20
+ const serverRequire = createRequire(path.join(context.directories.server, 'package.json'));
21
+ const moduleCache = {};
22
+ return iconImports.map(({ icons, package: pkg })=>{
23
+ if (icons.length === 0) {
24
+ return {
25
+ icons,
26
+ package: pkg
27
+ };
28
+ }
29
+ if (!moduleCache[pkg]) {
30
+ try {
31
+ moduleCache[pkg] = serverRequire(pkg);
32
+ } catch {
33
+ return {
34
+ icons,
35
+ package: pkg
36
+ };
37
+ }
38
+ }
39
+ const exports = Object.keys(moduleCache[pkg]);
40
+ const validIcons = [];
41
+ for (const icon of icons){
42
+ if (moduleCache[pkg][icon]) {
43
+ validIcons.push(icon);
44
+ } else {
45
+ let message = `Icon "${icon}" not found in "${pkg}".`;
46
+ const suggestion = findSimilarString({
47
+ input: icon,
48
+ candidates: exports,
49
+ maxDistance: Math.max(3, Math.ceil(icon.length * 0.4))
50
+ });
51
+ if (suggestion) {
52
+ message += ` Did you mean "${suggestion}"?`;
53
+ }
54
+ context.handleWarning(new ConfigWarning(message, {
55
+ checkSlug: 'icons'
56
+ }));
57
+ }
58
+ }
59
+ return {
60
+ icons: validIcons,
61
+ package: pkg
62
+ };
63
+ });
64
+ }
65
+ export default validateIconImports;
@@ -12,7 +12,8 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import { serializer, type } from '@lowdefy/helpers';
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
+ import { serializer, type } from '@lowdefy/helpers';
16
17
  import crypto from 'crypto';
17
18
  function makeHash({ jsMap, env, value }) {
18
19
  const hash = crypto.createHash('sha1').update(value).digest('base64');
@@ -31,7 +32,9 @@ function JsMapParser({ input, jsMap, env }) {
31
32
  const key = Object.keys(value)[0];
32
33
  if (key !== '_js') return value;
33
34
  if (!type.isString(value[key])) {
34
- throw new Error('_js operator expects the JavaScript definition as a string.');
35
+ throw new ConfigError(`_js operator expects the JavaScript definition as a string. Received ${JSON.stringify(value[key])}.`, {
36
+ configKey: value['~k']
37
+ });
35
38
  }
36
39
  return makeHash({
37
40
  jsMap,
@@ -17,18 +17,26 @@ import buildRequests from './buildRequests.js';
17
17
  import buildSubBlocks from './buildSubBlocks.js';
18
18
  import countBlockOperators from './countBlockOperators.js';
19
19
  import countBlockTypes from './countBlockTypes.js';
20
- import moveSubBlocksToArea from './moveSubBlocksToArea.js';
21
- import moveSkeletonBlocksToArea from './moveSkeletonBlocksToArea.js';
20
+ import moveAreasToSlots from './moveAreasToSlots.js';
21
+ import moveSubBlocksToSlot from './moveSubBlocksToSlot.js';
22
+ import moveSkeletonBlocksToSlot from './moveSkeletonBlocksToSlot.js';
23
+ import normalizeClassAndStyles from './normalizeClassAndStyles.js';
24
+ import normalizeLayout from './normalizeLayout.js';
22
25
  import setBlockId from './setBlockId.js';
23
26
  import validateBlock from './validateBlock.js';
27
+ import validateSlots from './validateSlots.js';
24
28
  function buildBlock(block, pageContext) {
25
29
  validateBlock(block, pageContext);
26
30
  setBlockId(block, pageContext);
31
+ normalizeLayout(block, pageContext);
32
+ moveAreasToSlots(block, pageContext);
27
33
  countBlockOperators(block, pageContext);
28
34
  buildEvents(block, pageContext);
29
35
  buildRequests(block, pageContext);
30
- moveSubBlocksToArea(block, pageContext);
31
- moveSkeletonBlocksToArea(block, pageContext);
36
+ normalizeClassAndStyles(block, pageContext);
37
+ moveSubBlocksToSlot(block, pageContext);
38
+ moveSkeletonBlocksToSlot(block, pageContext);
39
+ validateSlots(block, pageContext);
32
40
  countBlockTypes(block, pageContext);
33
41
  buildSubBlocks(block, pageContext);
34
42
  }
@@ -13,8 +13,16 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
- import { ConfigError } from '@lowdefy/errors';
16
+ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
17
17
  import createCheckDuplicateId from '../../../utils/createCheckDuplicateId.js';
18
+ const BROWSER_DEFAULT_SHORTCUTS = new Set([
19
+ 'mod+n',
20
+ 'mod+t',
21
+ 'mod+w',
22
+ 'mod+r',
23
+ 'mod+q',
24
+ 'mod+l'
25
+ ]);
18
26
  function checkAction(action, { blockId, checkDuplicateActionId, eventId, linkActionRefs, pageId, requestActionRefs, typeCounters }) {
19
27
  const configKey = action['~k'];
20
28
  if (type.isUndefined(action.id)) {
@@ -140,6 +148,31 @@ function buildEvents(block, pageContext) {
140
148
  requestActionRefs: pageContext.requestActionRefs,
141
149
  checkDuplicateActionId
142
150
  }));
151
+ // Validate shortcut strings and collect refs for duplicate detection
152
+ if (type.isObject(block.events[key]) && !type.isNone(block.events[key].shortcut)) {
153
+ const shortcuts = type.isArray(block.events[key].shortcut) ? block.events[key].shortcut : [
154
+ block.events[key].shortcut
155
+ ];
156
+ shortcuts.forEach((shortcut)=>{
157
+ if (!type.isString(shortcut) || shortcut === '') {
158
+ throw new ConfigError(`Event shortcut is not a valid string on event "${key}" on block "${block.blockId}" on page "${pageContext.pageId}".`, {
159
+ received: shortcut,
160
+ configKey: eventConfigKey
161
+ });
162
+ }
163
+ if (BROWSER_DEFAULT_SHORTCUTS.has(shortcut.toLowerCase())) {
164
+ pageContext.context.handleWarning(new ConfigWarning(`Shortcut "${shortcut}" on event "${key}" on block "${block.blockId}" on page "${pageContext.pageId}" conflicts with a browser default.`, {
165
+ configKey: eventConfigKey
166
+ }));
167
+ }
168
+ pageContext.shortcutRefs.push({
169
+ shortcut,
170
+ blockId: block.blockId,
171
+ eventId: key,
172
+ configKey: eventConfigKey
173
+ });
174
+ });
175
+ }
143
176
  });
144
177
  }
145
178
  }
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import { ConfigError } from '@lowdefy/errors';
17
+ import validateId from '../../../utils/validateId.js';
17
18
  function buildRequest(request, pageContext) {
18
19
  const { auth, checkDuplicateRequestId, context, pageId, typeCounters } = pageContext;
19
20
  const configKey = request['~k'];
@@ -33,11 +34,12 @@ function buildRequest(request, pageContext) {
33
34
  configKey,
34
35
  pageId
35
36
  });
36
- if (request.id.includes('.')) {
37
- throw new ConfigError(`Request id "${request.id}" at page "${pageId}" should not include a period (".").`, {
38
- configKey
39
- });
40
- }
37
+ validateId({
38
+ id: request.id,
39
+ field: 'Request id',
40
+ location: `page "${pageId}"`,
41
+ configKey
42
+ });
41
43
  if (!type.isString(request.type)) {
42
44
  throw new ConfigError(`Request type is not a string at request "${request.id}" at page "${pageId}".`, {
43
45
  received: request.type,
@@ -16,18 +16,18 @@
16
16
  import { ConfigError } from '@lowdefy/errors';
17
17
  import buildBlock from './buildBlock.js';
18
18
  function buildSubBlocks(block, pageContext) {
19
- if (type.isObject(block.areas)) {
20
- Object.keys(block.areas).forEach((key)=>{
21
- if (type.isNone(block.areas[key].blocks)) {
22
- block.areas[key].blocks = [];
19
+ if (type.isObject(block.slots)) {
20
+ Object.keys(block.slots).forEach((key)=>{
21
+ if (type.isNone(block.slots[key].blocks)) {
22
+ block.slots[key].blocks = [];
23
23
  }
24
- if (!type.isArray(block.areas[key].blocks)) {
25
- throw new ConfigError(`Expected blocks to be an array at ${block.blockId} in area ${key} on page ${pageContext.pageId}.`, {
26
- received: block.areas[key].blocks,
27
- configKey: block.areas[key]['~k'] ?? block['~k']
24
+ if (!type.isArray(block.slots[key].blocks)) {
25
+ throw new ConfigError(`Expected blocks to be an array at ${block.blockId} in slot ${key} on page ${pageContext.pageId}.`, {
26
+ received: block.slots[key].blocks,
27
+ configKey: block.slots[key]['~k'] ?? block['~k']
28
28
  });
29
29
  }
30
- block.areas[key].blocks.map((blk)=>buildBlock(blk, pageContext));
30
+ block.slots[key].blocks.map((blk)=>buildBlock(blk, pageContext));
31
31
  });
32
32
  }
33
33
  }
@@ -15,7 +15,7 @@
15
15
  */ import countOperators from '../../../utils/countOperators.js';
16
16
  function countBlockOperators(block, { typeCounters }) {
17
17
  // eslint-disable-next-line no-unused-vars
18
- const { requests, areas, blocks, ...webBlock } = block;
18
+ const { requests, slots, blocks, ...webBlock } = block;
19
19
  countOperators(webBlock, {
20
20
  counter: typeCounters.operators.client
21
21
  });
@@ -0,0 +1,31 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
17
+ function moveAreasToSlots(block, pageContext) {
18
+ if (!type.isNone(block.areas)) {
19
+ if (!type.isNone(block.slots)) {
20
+ throw new ConfigError(`Block "${block.blockId}" on page "${pageContext.pageId}" cannot have both "areas" and "slots". Use "slots".`, {
21
+ configKey: block['~k']
22
+ });
23
+ }
24
+ pageContext.context.handleWarning(new ConfigWarning(`Block "${block.blockId}" on page "${pageContext.pageId}": "areas" is deprecated, use "slots".`, {
25
+ configKey: block['~k']
26
+ }));
27
+ block.slots = block.areas;
28
+ delete block.areas;
29
+ }
30
+ }
31
+ export default moveAreasToSlots;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { set, type } from '@lowdefy/helpers';
16
16
  import { ConfigError } from '@lowdefy/errors';
17
- function recMoveSkeletonBlocksToArea(block, blockId, pageId) {
17
+ function recMoveSkeletonBlocksToSlot(block, blockId, pageId) {
18
18
  if (!type.isNone(block.blocks)) {
19
19
  if (!type.isArray(block.blocks)) {
20
20
  throw new ConfigError(`Skeleton blocks at ${blockId} on page ${pageId} is not an array.`, {
@@ -22,18 +22,18 @@ function recMoveSkeletonBlocksToArea(block, blockId, pageId) {
22
22
  configKey: block['~k']
23
23
  });
24
24
  }
25
- set(block, 'areas.content.blocks', block.blocks);
25
+ set(block, 'slots.content.blocks', block.blocks);
26
26
  delete block.blocks;
27
27
  }
28
- Object.keys(block.areas || {}).forEach((area)=>{
29
- block.areas[area].blocks.forEach((block, i)=>{
30
- recMoveSkeletonBlocksToArea(block, `${blockId}.areas.${area}.${i}.blocks`, pageId);
28
+ Object.keys(block.slots || {}).forEach((slot)=>{
29
+ block.slots[slot].blocks.forEach((block, i)=>{
30
+ recMoveSkeletonBlocksToSlot(block, `${blockId}.slots.${slot}.${i}.blocks`, pageId);
31
31
  });
32
32
  });
33
33
  }
34
- function moveSkeletonBlocksToArea(block, pageContext) {
34
+ function moveSkeletonBlocksToSlot(block, pageContext) {
35
35
  if (type.isObject(block.skeleton)) {
36
- recMoveSkeletonBlocksToArea(block.skeleton, `${block.blockId}.skeleton`, pageContext.pageId);
36
+ recMoveSkeletonBlocksToSlot(block.skeleton, `${block.blockId}.skeleton`, pageContext.pageId);
37
37
  }
38
38
  }
39
- export default moveSkeletonBlocksToArea;
39
+ export default moveSkeletonBlocksToSlot;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { set, type } from '@lowdefy/helpers';
16
16
  import { ConfigError } from '@lowdefy/errors';
17
- function moveSubBlocksToArea(block, pageContext) {
17
+ function moveSubBlocksToSlot(block, pageContext) {
18
18
  if (!type.isNone(block.blocks)) {
19
19
  if (!type.isArray(block.blocks)) {
20
20
  throw new ConfigError(`Blocks at ${block.blockId} on page ${pageContext.pageId} is not an array.`, {
@@ -22,8 +22,8 @@ function moveSubBlocksToArea(block, pageContext) {
22
22
  configKey: block['~k']
23
23
  });
24
24
  }
25
- set(block, 'areas.content.blocks', block.blocks);
25
+ set(block, 'slots.content.blocks', block.blocks);
26
26
  delete block.blocks;
27
27
  }
28
28
  }
29
- export default moveSubBlocksToArea;
29
+ export default moveSubBlocksToSlot;
@@ -0,0 +1,124 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ const breakpointKeys = new Set([
18
+ 'xs',
19
+ 'sm',
20
+ 'md',
21
+ 'lg',
22
+ 'xl',
23
+ '2xl'
24
+ ]);
25
+ // Keys that look like operators (single key starting with _) but are not.
26
+ const KNOWN_NON_OPERATORS = new Set([
27
+ '_id'
28
+ ]);
29
+ function isOperator(value) {
30
+ const nonTildeKeys = Object.keys(value).filter((k)=>!k.startsWith('~'));
31
+ if (nonTildeKeys.length !== 1) return false;
32
+ const key = nonTildeKeys[0];
33
+ const [op] = key.split('.');
34
+ const operator = op.replace(/^(_+)/gm, '_');
35
+ return operator.length > 1 && operator[0] === '_' && !KNOWN_NON_OPERATORS.has(operator);
36
+ }
37
+ function stripDotPrefix(key) {
38
+ return key.startsWith('.') ? key.slice(1) : key;
39
+ }
40
+ function getCssKeyNames(block, pageContext) {
41
+ const blockMeta = pageContext?.context?.blockMetas?.[block.type];
42
+ if (!blockMeta?.cssKeys) return new Set();
43
+ return new Set(Object.keys(blockMeta.cssKeys));
44
+ }
45
+ function normalizeStyle(block, pageContext) {
46
+ // properties.style → element slot (deprecation: component's own style maps to .element)
47
+ if (!type.isNone(block.properties?.style)) {
48
+ if (!block.style) block.style = {};
49
+ const existing = block.style['.element'];
50
+ block.style['.element'] = existing ? {
51
+ ...block.properties.style,
52
+ ...existing
53
+ } : block.properties.style;
54
+ delete block.properties.style;
55
+ }
56
+ // Partition plain CSS → block slot, . keys → strip prefix (single pass)
57
+ if (type.isObject(block.style)) {
58
+ const invalidKeys = Object.keys(block.style).filter((k)=>!k.startsWith('.') && breakpointKeys.has(k));
59
+ if (invalidKeys.length > 0) {
60
+ throw new ConfigError(`Block "${block.blockId}": Responsive breakpoint keys (${invalidKeys.join(', ')}) in "style" are no longer supported. Use CSS classes instead:\n class: "p-16 sm:p-8"`, {
61
+ configKey: block['~k']
62
+ });
63
+ }
64
+ const result = {};
65
+ const plainCSS = {};
66
+ for (const [key, value] of Object.entries(block.style)){
67
+ if (key.startsWith('.')) {
68
+ result[stripDotPrefix(key)] = value;
69
+ } else {
70
+ plainCSS[key] = value;
71
+ }
72
+ }
73
+ if (Object.keys(plainCSS).length > 0) {
74
+ result.block = result.block ? {
75
+ ...plainCSS,
76
+ ...result.block
77
+ } : plainCSS;
78
+ }
79
+ block.style = result;
80
+ // Validate no nested objects in style slot values (except operators)
81
+ const validCssKeys = getCssKeyNames(block, pageContext);
82
+ for (const [slotKey, slotStyle] of Object.entries(block.style)){
83
+ if (!type.isObject(slotStyle) || isOperator(slotStyle)) continue;
84
+ for (const [cssKey, cssValue] of Object.entries(slotStyle)){
85
+ if (cssKey.startsWith('~')) continue;
86
+ if (type.isObject(cssValue) && !isOperator(cssValue)) {
87
+ const hint = validCssKeys.has(cssKey) ? ` Did you mean ".${cssKey}"? Use a dot prefix to target CSS slot keys.` : '';
88
+ throw new ConfigError(`Block "${block.blockId}": Style property "${cssKey}" has a nested object value.${hint} CSS properties must be simple values (strings, numbers) or operators.`, {
89
+ configKey: block['~k']
90
+ });
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ function normalizeClass(block, pageContext) {
97
+ if (type.isString(block.class) || type.isArray(block.class)) {
98
+ block.class = {
99
+ block: block.class
100
+ };
101
+ return;
102
+ }
103
+ if (!type.isObject(block.class)) return;
104
+ // Validate: non-dot keys that match cssKeys are likely missing the dot prefix
105
+ const validCssKeys = getCssKeyNames(block, pageContext);
106
+ for (const key of Object.keys(block.class)){
107
+ if (!key.startsWith('.') && !key.startsWith('~') && validCssKeys.has(key)) {
108
+ throw new ConfigError(`Block "${block.blockId}": Class key "${key}" matches a CSS slot key but is missing the dot prefix. Did you mean ".${key}"?`, {
109
+ configKey: block['~k']
110
+ });
111
+ }
112
+ }
113
+ // Strip dot prefixes
114
+ const normalized = {};
115
+ for (const [key, value] of Object.entries(block.class)){
116
+ normalized[stripDotPrefix(key)] = value;
117
+ }
118
+ block.class = normalized;
119
+ }
120
+ function normalizeClassAndStyles(block, pageContext) {
121
+ normalizeStyle(block, pageContext);
122
+ normalizeClass(block, pageContext);
123
+ }
124
+ export default normalizeClassAndStyles;