@appsemble/lang-sdk 0.37.5 → 0.38.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/iterApp.js +7 -2
- package/package.json +2 -2
- package/schemas/AppLayoutDefinition.js +14 -0
- package/schemas/GridBreakpointsDefinition.d.ts +2 -0
- package/schemas/GridBreakpointsDefinition.js +22 -0
- package/schemas/GridSpacingDefinition.js +2 -2
- package/schemas/LoopPageDefinition.js +8 -0
- package/schemas/NavbarDeviceGridLayoutDefinition.d.ts +1 -0
- package/schemas/NavbarDeviceGridLayoutDefinition.js +9 -0
- package/schemas/NavbarLayoutDefinition.d.ts +1 -0
- package/schemas/NavbarLayoutDefinition.js +24 -0
- package/schemas/PageLayoutDefinition.d.ts +1 -2
- package/schemas/PageLayoutDefinition.js +5 -14
- package/schemas/ResourceDefinition.js +1 -1
- package/schemas/ResponsiveGridLayoutDefinition.d.ts +2 -0
- package/schemas/ResponsiveGridLayoutDefinition.js +18 -0
- package/schemas/index.d.ts +4 -0
- package/schemas/index.js +4 -0
- package/types/core.d.ts +27 -1
- package/types/gridLayout.d.ts +20 -6
- package/validation.js +75 -5
package/iterApp.js
CHANGED
|
@@ -121,8 +121,13 @@ export function iterPage(page, callbacks, prefix = []) {
|
|
|
121
121
|
if (result) {
|
|
122
122
|
return true;
|
|
123
123
|
}
|
|
124
|
-
const { foreach } = page;
|
|
125
|
-
|
|
124
|
+
const { end, foreach, start } = page;
|
|
125
|
+
const subPages = [
|
|
126
|
+
['steps.first', start ?? foreach],
|
|
127
|
+
['steps', foreach],
|
|
128
|
+
['steps.last', end ?? foreach],
|
|
129
|
+
];
|
|
130
|
+
return subPages.some(([suffix, subPage]) => iterBlockList(subPage?.blocks ?? [], callbacks, [...prefix, suffix, 'blocks']));
|
|
126
131
|
}
|
|
127
132
|
if (page.type === 'container') {
|
|
128
133
|
let result = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/lang-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "Language SDK for Appsemble",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/language-tags": "1.0.4",
|
|
60
60
|
"@types/lcm": "0.0.2",
|
|
61
|
-
"vitest": "
|
|
61
|
+
"vitest": "4.1.11"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=24"
|
|
@@ -104,6 +104,20 @@ Only applies when \`navigation\` is set to \`top\`.
|
|
|
104
104
|
type: 'boolean',
|
|
105
105
|
default: false,
|
|
106
106
|
},
|
|
107
|
+
navbar: {
|
|
108
|
+
$ref: '#/components/schemas/NavbarLayoutDefinition',
|
|
109
|
+
description: `Responsive grid layout for the top navigation header.
|
|
110
|
+
|
|
111
|
+
Only applies when \`navigation\` is set to \`top\` and cannot be combined with \`stackedHeader\`.
|
|
112
|
+
`,
|
|
113
|
+
},
|
|
114
|
+
breakpoints: {
|
|
115
|
+
$ref: '#/components/schemas/GridBreakpointsDefinition',
|
|
116
|
+
description: `Minimum viewport widths in pixels at which the tablet and desktop grid layouts apply.
|
|
117
|
+
|
|
118
|
+
Applies to every responsive grid layout in the app, including pages and the navbar.
|
|
119
|
+
`,
|
|
120
|
+
},
|
|
107
121
|
hideGroupDropdown: {
|
|
108
122
|
description: `Whether to hide the group dropdown.
|
|
109
123
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const GridBreakpointsDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: `Minimum viewport widths in pixels at which the tablet and desktop grid layouts apply.
|
|
4
|
+
|
|
5
|
+
The mobile layout always applies from a width of 0. Each breakpoint must be smaller than the next.
|
|
6
|
+
`,
|
|
7
|
+
additionalProperties: false,
|
|
8
|
+
minProperties: 1,
|
|
9
|
+
properties: {
|
|
10
|
+
tablet: {
|
|
11
|
+
type: 'integer',
|
|
12
|
+
minimum: 0,
|
|
13
|
+
description: 'Minimum viewport width in pixels at which the tablet grid layout applies.',
|
|
14
|
+
},
|
|
15
|
+
desktop: {
|
|
16
|
+
type: 'integer',
|
|
17
|
+
minimum: 0,
|
|
18
|
+
description: 'Minimum viewport width in pixels at which the desktop grid layout applies.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=GridBreakpointsDefinition.js.map
|
|
@@ -5,9 +5,9 @@ export const GridSpacingDefinition = {
|
|
|
5
5
|
properties: {
|
|
6
6
|
unit: {
|
|
7
7
|
type: 'string',
|
|
8
|
-
description: 'CSS unit for spacing calculations (e.g., "1rem", "8px")',
|
|
8
|
+
description: 'CSS unit or custom property for spacing calculations (e.g., "1rem", "8px", "var(--grid-unit)")',
|
|
9
9
|
default: '1rem',
|
|
10
|
-
pattern: '
|
|
10
|
+
pattern: '^(?:-?(\\d+\\.?\\d*|\\.\\d+)(rem|em|px|%|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc)|var\\(--[A-Za-z_][A-Za-z0-9_-]*\\))$',
|
|
11
11
|
},
|
|
12
12
|
gap: {
|
|
13
13
|
type: 'number',
|
|
@@ -27,6 +27,14 @@ By default the flow page retains it's data after navigating once. Set to false t
|
|
|
27
27
|
`,
|
|
28
28
|
default: true,
|
|
29
29
|
},
|
|
30
|
+
start: {
|
|
31
|
+
$ref: '#/components/schemas/SubPage',
|
|
32
|
+
description: 'A page to display before the generated loop pages.',
|
|
33
|
+
},
|
|
34
|
+
end: {
|
|
35
|
+
$ref: '#/components/schemas/SubPage',
|
|
36
|
+
description: 'A page to display after the generated loop pages.',
|
|
37
|
+
},
|
|
30
38
|
},
|
|
31
39
|
});
|
|
32
40
|
//# sourceMappingURL=LoopPageDefinition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const NavbarDeviceGridLayoutDefinition: import("openapi-types").OpenAPIV3.SchemaObject;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DeviceGridLayoutDefinition } from './DeviceGridLayoutDefinition.js';
|
|
2
|
+
import { extendJSONSchema } from './utils/extendJSONSchema.js';
|
|
3
|
+
export const NavbarDeviceGridLayoutDefinition = extendJSONSchema(DeviceGridLayoutDefinition, {
|
|
4
|
+
type: 'object',
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
required: ['layout'],
|
|
7
|
+
description: 'Grid layout definition for a navbar breakpoint',
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=NavbarDeviceGridLayoutDefinition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const NavbarLayoutDefinition: import("openapi-types").OpenAPIV3.SchemaObject;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ResponsiveGridLayoutDefinition } from './ResponsiveGridLayoutDefinition.js';
|
|
2
|
+
import { extendJSONSchema } from './utils/extendJSONSchema.js';
|
|
3
|
+
export const NavbarLayoutDefinition = extendJSONSchema(ResponsiveGridLayoutDefinition, {
|
|
4
|
+
type: 'object',
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
minProperties: 1,
|
|
7
|
+
description: `Responsive grid layout for the top navigation header.
|
|
8
|
+
|
|
9
|
+
The available template areas are logo, name, navigation, and controls.
|
|
10
|
+
Each rendered area must be included and their first visual occurrence must follow that order.
|
|
11
|
+
`,
|
|
12
|
+
properties: {
|
|
13
|
+
desktop: {
|
|
14
|
+
$ref: '#/components/schemas/NavbarDeviceGridLayoutDefinition',
|
|
15
|
+
},
|
|
16
|
+
mobile: {
|
|
17
|
+
$ref: '#/components/schemas/NavbarDeviceGridLayoutDefinition',
|
|
18
|
+
},
|
|
19
|
+
tablet: {
|
|
20
|
+
$ref: '#/components/schemas/NavbarDeviceGridLayoutDefinition',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=NavbarLayoutDefinition.js.map
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const PageLayoutDefinition: OpenAPIV3.NonArraySchemaObject;
|
|
1
|
+
export declare const PageLayoutDefinition: import("openapi-types").OpenAPIV3.SchemaObject;
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { ResponsiveGridLayoutDefinition } from './ResponsiveGridLayoutDefinition.js';
|
|
2
|
+
import { extendJSONSchema } from './utils/extendJSONSchema.js';
|
|
3
|
+
export const PageLayoutDefinition = extendJSONSchema(ResponsiveGridLayoutDefinition, {
|
|
2
4
|
type: 'object',
|
|
3
|
-
description: 'Grid layout of the page, define as an anchor to re-use across multiple pages',
|
|
4
5
|
additionalProperties: false,
|
|
5
6
|
minProperties: 1,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
9
|
-
},
|
|
10
|
-
tablet: {
|
|
11
|
-
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
12
|
-
},
|
|
13
|
-
desktop: {
|
|
14
|
-
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
7
|
+
description: 'Responsive grid layout for a page',
|
|
8
|
+
});
|
|
18
9
|
//# sourceMappingURL=PageLayoutDefinition.js.map
|
|
@@ -100,10 +100,10 @@ name of the resource and how it should behave.
|
|
|
100
100
|
},
|
|
101
101
|
enforceOrderingGroupByFields: {
|
|
102
102
|
type: 'array',
|
|
103
|
-
pattern: '^[a-zA-Z0-9]*$',
|
|
104
103
|
description: 'Group resources by fields when handling resource positions',
|
|
105
104
|
items: {
|
|
106
105
|
type: 'string',
|
|
106
|
+
pattern: '^[a-zA-Z0-9]*$',
|
|
107
107
|
},
|
|
108
108
|
minItems: 1,
|
|
109
109
|
},
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const ResponsiveGridLayoutDefinition = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description: 'Responsive grid layout',
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
minProperties: 1,
|
|
6
|
+
properties: {
|
|
7
|
+
mobile: {
|
|
8
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
9
|
+
},
|
|
10
|
+
tablet: {
|
|
11
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
12
|
+
},
|
|
13
|
+
desktop: {
|
|
14
|
+
$ref: '#/components/schemas/DeviceGridLayoutDefinition',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=ResponsiveGridLayoutDefinition.js.map
|
package/schemas/index.d.ts
CHANGED
|
@@ -124,6 +124,10 @@ export * from './TabsPageDefinition.js';
|
|
|
124
124
|
export * from './Theme.js';
|
|
125
125
|
export * from './WebhookDefinition.js';
|
|
126
126
|
export * from './DeviceGridLayoutDefinition.js';
|
|
127
|
+
export * from './GridBreakpointsDefinition.js';
|
|
127
128
|
export * from './GridLayoutDefinition.js';
|
|
128
129
|
export * from './GridSpacingDefinition.js';
|
|
130
|
+
export * from './ResponsiveGridLayoutDefinition.js';
|
|
131
|
+
export * from './NavbarDeviceGridLayoutDefinition.js';
|
|
132
|
+
export * from './NavbarLayoutDefinition.js';
|
|
129
133
|
export * from './PageLayoutDefinition.js';
|
package/schemas/index.js
CHANGED
|
@@ -124,7 +124,11 @@ export * from './TabsPageDefinition.js';
|
|
|
124
124
|
export * from './Theme.js';
|
|
125
125
|
export * from './WebhookDefinition.js';
|
|
126
126
|
export * from './DeviceGridLayoutDefinition.js';
|
|
127
|
+
export * from './GridBreakpointsDefinition.js';
|
|
127
128
|
export * from './GridLayoutDefinition.js';
|
|
128
129
|
export * from './GridSpacingDefinition.js';
|
|
130
|
+
export * from './ResponsiveGridLayoutDefinition.js';
|
|
131
|
+
export * from './NavbarDeviceGridLayoutDefinition.js';
|
|
132
|
+
export * from './NavbarLayoutDefinition.js';
|
|
129
133
|
export * from './PageLayoutDefinition.js';
|
|
130
134
|
//# sourceMappingURL=index.js.map
|
package/types/core.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type ResourceDefinition } from './resource.js';
|
|
|
9
9
|
import { type AppRole, type ViewRole } from './roles.js';
|
|
10
10
|
import { type Security } from './security.js';
|
|
11
11
|
import { type Theme } from './theme.js';
|
|
12
|
-
import { type PageLayoutDefinition } from './gridLayout.js';
|
|
12
|
+
import { type GridBreakpointsDefinition, type NavbarLayoutDefinition, type PageLayoutDefinition } from './gridLayout.js';
|
|
13
13
|
export type SettingName = 'email' | 'languages' | 'name' | 'password' | 'phoneNumber' | 'picture';
|
|
14
14
|
export type AppContentSecurityPolicy = Record<string, string[]>;
|
|
15
15
|
export interface AppDefinition {
|
|
@@ -118,6 +118,24 @@ export interface AppDefinition {
|
|
|
118
118
|
* Only applies when `navigation` is set to `top`.
|
|
119
119
|
*/
|
|
120
120
|
stackedHeader?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Responsive grid layout for the top navigation header.
|
|
123
|
+
*
|
|
124
|
+
* Only applies when `navigation` is set to `top`. This cannot be combined with
|
|
125
|
+
* `stackedHeader`.
|
|
126
|
+
*
|
|
127
|
+
* The available template areas are logo, name, navigation, and controls.
|
|
128
|
+
* Each rendered area must be included and their first visual occurrence must follow that order.
|
|
129
|
+
*/
|
|
130
|
+
navbar?: NavbarLayoutDefinition;
|
|
131
|
+
/**
|
|
132
|
+
* Minimum viewport widths in pixels at which the tablet and desktop grid layouts apply.
|
|
133
|
+
*
|
|
134
|
+
* Applies to every responsive grid layout in the app, including pages and the navbar. The
|
|
135
|
+
* mobile layout always applies from a width of 0. Each breakpoint must be smaller than the
|
|
136
|
+
* next.
|
|
137
|
+
*/
|
|
138
|
+
breakpoints?: GridBreakpointsDefinition;
|
|
121
139
|
/**
|
|
122
140
|
* Whether to hide the group dropdown.
|
|
123
141
|
*
|
|
@@ -316,6 +334,14 @@ export interface LoopPageDefinition extends BasePageDefinition {
|
|
|
316
334
|
* @default true
|
|
317
335
|
*/
|
|
318
336
|
retainFlowData?: boolean;
|
|
337
|
+
/**
|
|
338
|
+
* The page to display before the generated loop pages.
|
|
339
|
+
*/
|
|
340
|
+
start?: SubPageDefinition;
|
|
341
|
+
/**
|
|
342
|
+
* The page to display after the generated loop pages.
|
|
343
|
+
*/
|
|
344
|
+
end?: SubPageDefinition;
|
|
319
345
|
}
|
|
320
346
|
export interface AlternateTabsDefinition {
|
|
321
347
|
foreach: SubPageDefinition;
|
package/types/gridLayout.d.ts
CHANGED
|
@@ -8,12 +8,26 @@ export interface GridLayoutSpacingDefinition {
|
|
|
8
8
|
padding: number;
|
|
9
9
|
gap: number;
|
|
10
10
|
}
|
|
11
|
+
export interface GridBreakpointsDefinition {
|
|
12
|
+
tablet?: number;
|
|
13
|
+
desktop?: number;
|
|
14
|
+
}
|
|
11
15
|
export interface DeviceGridLayoutDefinition {
|
|
16
|
+
layout?: GridLayoutDefinition;
|
|
17
|
+
spacing?: GridLayoutSpacingDefinition;
|
|
18
|
+
}
|
|
19
|
+
export interface ResponsiveGridLayoutDefinition {
|
|
20
|
+
desktop?: DeviceGridLayoutDefinition;
|
|
21
|
+
mobile?: DeviceGridLayoutDefinition;
|
|
22
|
+
tablet?: DeviceGridLayoutDefinition;
|
|
23
|
+
}
|
|
24
|
+
export interface NavbarDeviceGridLayoutDefinition extends DeviceGridLayoutDefinition {
|
|
12
25
|
layout: GridLayoutDefinition;
|
|
13
|
-
spacing: GridLayoutSpacingDefinition;
|
|
14
26
|
}
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
27
|
+
export interface ResponsiveNavbarGridLayoutDefinition {
|
|
28
|
+
desktop?: NavbarDeviceGridLayoutDefinition;
|
|
29
|
+
mobile?: NavbarDeviceGridLayoutDefinition;
|
|
30
|
+
tablet?: NavbarDeviceGridLayoutDefinition;
|
|
31
|
+
}
|
|
32
|
+
export type NavbarLayoutDefinition = RequireAtLeastOne<ResponsiveNavbarGridLayoutDefinition>;
|
|
33
|
+
export type PageLayoutDefinition = RequireAtLeastOne<ResponsiveGridLayoutDefinition>;
|
package/validation.js
CHANGED
|
@@ -437,7 +437,7 @@ function validateTemplateAreasAreRectangular(template, report, path) {
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
|
-
function
|
|
440
|
+
function validateResponsiveGridLayoutDefinition(layoutDefinition, report, path) {
|
|
441
441
|
if (!layoutDefinition) {
|
|
442
442
|
return;
|
|
443
443
|
}
|
|
@@ -445,10 +445,16 @@ function validatePageLayoutDefinition(layoutDefinition, report, path) {
|
|
|
445
445
|
if (!deviceDefinition?.layout?.template || !deviceDefinition?.layout?.columns) {
|
|
446
446
|
continue;
|
|
447
447
|
}
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
const { layout } = deviceDefinition;
|
|
449
|
+
if (layout.template.some((row) => row.split(' ').length !== layout.columns)) {
|
|
450
|
+
report(layout.template, 'template needs to be the same length as number of columns', [
|
|
451
|
+
...path,
|
|
452
|
+
deviceName,
|
|
453
|
+
'layout',
|
|
454
|
+
'template',
|
|
455
|
+
]);
|
|
450
456
|
}
|
|
451
|
-
validateTemplateAreasAreRectangular(
|
|
457
|
+
validateTemplateAreasAreRectangular(layout.template, report, [
|
|
452
458
|
...path,
|
|
453
459
|
deviceName,
|
|
454
460
|
'layout',
|
|
@@ -465,12 +471,67 @@ function validateBlockGridAreas(blocks, layoutDefinition, report, path) {
|
|
|
465
471
|
}
|
|
466
472
|
}
|
|
467
473
|
function validateSubPageLayout(layout, blocks, report, path) {
|
|
468
|
-
|
|
474
|
+
validateResponsiveGridLayoutDefinition(layout, report, path);
|
|
469
475
|
if (layout && blocks) {
|
|
470
476
|
validateBlockGridAreas(blocks, layout, report, [...path, 'blocks']);
|
|
471
477
|
}
|
|
472
478
|
}
|
|
473
479
|
function validateGridLayout(definition, report) {
|
|
480
|
+
const breakpoints = definition.layout?.breakpoints;
|
|
481
|
+
if (breakpoints?.tablet != null &&
|
|
482
|
+
breakpoints?.desktop != null &&
|
|
483
|
+
breakpoints.tablet >= breakpoints.desktop) {
|
|
484
|
+
report(breakpoints, 'tablet breakpoint must be smaller than the desktop breakpoint', [
|
|
485
|
+
'layout',
|
|
486
|
+
'breakpoints',
|
|
487
|
+
]);
|
|
488
|
+
}
|
|
489
|
+
const navbarLayout = definition.layout?.navbar;
|
|
490
|
+
validateResponsiveGridLayoutDefinition(navbarLayout, report, ['layout', 'navbar']);
|
|
491
|
+
if (navbarLayout) {
|
|
492
|
+
if (definition.layout?.navigation !== 'top') {
|
|
493
|
+
report(navbarLayout, 'only applies when navigation is set to top', ['layout', 'navbar']);
|
|
494
|
+
}
|
|
495
|
+
if (definition.layout?.stackedHeader) {
|
|
496
|
+
report(navbarLayout, 'cannot be combined with stackedHeader', ['layout', 'navbar']);
|
|
497
|
+
}
|
|
498
|
+
const navbarAreaOrder = ['logo', 'name', 'navigation', 'controls'];
|
|
499
|
+
const allowedAreas = new Set(['.', ...navbarAreaOrder]);
|
|
500
|
+
const requiredAreas = definition.layout?.logo?.position === 'navbar' ? navbarAreaOrder : navbarAreaOrder.slice(1);
|
|
501
|
+
for (const [deviceName, deviceDefinition] of Object.entries(navbarLayout)) {
|
|
502
|
+
if (!deviceDefinition?.layout?.template) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const { template } = deviceDefinition.layout;
|
|
506
|
+
const templatePath = ['layout', 'navbar', deviceName, 'layout', 'template'];
|
|
507
|
+
const visualAreaOrder = [];
|
|
508
|
+
const visualAreas = new Set();
|
|
509
|
+
for (const [rowIndex, row] of deviceDefinition.layout.template.entries()) {
|
|
510
|
+
for (const area of row.split(' ')) {
|
|
511
|
+
if (!allowedAreas.has(area)) {
|
|
512
|
+
report(row, `unknown navbar grid area '${area}'`, [...templatePath, rowIndex]);
|
|
513
|
+
}
|
|
514
|
+
else if (area !== '.' && !visualAreas.has(area)) {
|
|
515
|
+
visualAreas.add(area);
|
|
516
|
+
visualAreaOrder.push(area);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
let hasMissingArea = false;
|
|
521
|
+
for (const area of requiredAreas) {
|
|
522
|
+
if (!visualAreas.has(area)) {
|
|
523
|
+
hasMissingArea = true;
|
|
524
|
+
report(template, `navbar grid is missing required area '${area}'`, templatePath);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!hasMissingArea) {
|
|
528
|
+
const expectedAreaOrder = navbarAreaOrder.filter((area) => visualAreas.has(area));
|
|
529
|
+
if (visualAreaOrder.some((area, index) => area !== expectedAreaOrder[index])) {
|
|
530
|
+
report(template, `navbar grid areas must follow the accessible order: ${expectedAreaOrder.join(', ')}`, templatePath);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
474
535
|
iterApp(definition, {
|
|
475
536
|
onPage(page, path) {
|
|
476
537
|
// Basic page
|
|
@@ -503,10 +564,19 @@ function validateGridLayout(definition, report) {
|
|
|
503
564
|
// Loop page
|
|
504
565
|
if (page.type === 'loop') {
|
|
505
566
|
const loopPage = page;
|
|
567
|
+
if (loopPage.start) {
|
|
568
|
+
validateSubPageLayout(loopPage.start.layout, loopPage.start.blocks, report, [
|
|
569
|
+
...path,
|
|
570
|
+
'start',
|
|
571
|
+
]);
|
|
572
|
+
}
|
|
506
573
|
validateSubPageLayout(loopPage.foreach.layout, loopPage.foreach.blocks, report, [
|
|
507
574
|
...path,
|
|
508
575
|
'foreach',
|
|
509
576
|
]);
|
|
577
|
+
if (loopPage.end) {
|
|
578
|
+
validateSubPageLayout(loopPage.end.layout, loopPage.end.blocks, report, [...path, 'end']);
|
|
579
|
+
}
|
|
510
580
|
}
|
|
511
581
|
},
|
|
512
582
|
});
|