@appsemble/lang-sdk 0.36.2-test.0 → 0.36.2-test.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/lang-sdk",
3
- "version": "0.36.2-test.0",
3
+ "version": "0.36.2-test.2",
4
4
  "description": "Language SDK for Appsemble",
5
5
  "keywords": [
6
6
  "app",
@@ -89,6 +89,10 @@ The exact meaning of the parameters depends on the block type.
89
89
  },
90
90
  },
91
91
  events: { $ref: '#/components/schemas/EventsDefinition' },
92
+ gridArea: {
93
+ type: 'string',
94
+ description: 'Grid area defined in the page layout definition',
95
+ },
92
96
  },
93
97
  };
94
98
  //# sourceMappingURL=BlockDefinition.js.map
@@ -0,0 +1,2 @@
1
+ import { type OpenAPIV3 } from 'openapi-types';
2
+ export declare const DeviceGridLayoutDefinition: OpenAPIV3.NonArraySchemaObject;
@@ -0,0 +1,18 @@
1
+ export const DeviceGridLayoutDefinition = {
2
+ type: 'object',
3
+ description: 'Grid layout definition for each device breakpoint',
4
+ additionalProperties: false,
5
+ properties: {
6
+ spacing: {
7
+ $ref: '#/components/schemas/GridSpacingDefinition',
8
+ description: 'Spacing configuration for the grid',
9
+ default: { unit: '1rem', gap: 1, padding: 1 },
10
+ },
11
+ layout: {
12
+ $ref: '#/components/schemas/GridLayoutDefinition',
13
+ description: 'Layout configuration for the grid',
14
+ default: { columns: 1, template: ['main'] },
15
+ },
16
+ },
17
+ };
18
+ //# sourceMappingURL=DeviceGridLayoutDefinition.js.map
@@ -0,0 +1,2 @@
1
+ import { type OpenAPIV3 } from 'openapi-types';
2
+ export declare const GridLayoutDefinition: OpenAPIV3.NonArraySchemaObject;
@@ -0,0 +1,22 @@
1
+ export const GridLayoutDefinition = {
2
+ type: 'object',
3
+ description: 'Grid layout configuration for columns and template areas',
4
+ additionalProperties: false,
5
+ properties: {
6
+ columns: {
7
+ type: 'number',
8
+ description: 'Number of columns in the grid',
9
+ default: 1,
10
+ minimum: 1,
11
+ },
12
+ template: {
13
+ type: 'array',
14
+ description: 'Grid template areas - each string represents a row',
15
+ items: {
16
+ type: 'string',
17
+ },
18
+ default: ['main'],
19
+ },
20
+ },
21
+ };
22
+ //# sourceMappingURL=GridLayoutDefinition.js.map
@@ -0,0 +1,2 @@
1
+ import { type OpenAPIV3 } from 'openapi-types';
2
+ export declare const GridSpacingDefinition: OpenAPIV3.NonArraySchemaObject;
@@ -0,0 +1,26 @@
1
+ export const GridSpacingDefinition = {
2
+ type: 'object',
3
+ description: 'Define the spacing for grid layout of the page',
4
+ additionalProperties: false,
5
+ properties: {
6
+ unit: {
7
+ type: 'string',
8
+ description: 'CSS unit for spacing calculations (e.g., "1rem", "8px")',
9
+ default: '1rem',
10
+ pattern: '^-?(\\d+\\.?\\d*|\\.\\d+)(rem|em|px|%|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$',
11
+ },
12
+ gap: {
13
+ type: 'number',
14
+ description: 'Gap multiplier between grid items',
15
+ default: 1,
16
+ minimum: 0,
17
+ },
18
+ padding: {
19
+ type: 'number',
20
+ description: 'Padding multiplier around the grid',
21
+ default: 1,
22
+ minimum: 0,
23
+ },
24
+ },
25
+ };
26
+ //# sourceMappingURL=GridSpacingDefinition.js.map
@@ -21,6 +21,9 @@ the list.
21
21
  actions: {
22
22
  $ref: '#/components/schemas/PageActionsDefinition',
23
23
  },
24
+ layout: {
25
+ $ref: '#/components/schemas/PageLayoutDefinition',
26
+ },
24
27
  },
25
28
  });
26
29
  //# sourceMappingURL=PageDefinition.js.map
@@ -0,0 +1,2 @@
1
+ import { type OpenAPIV3 } from 'openapi-types';
2
+ export declare const PageLayoutDefinition: OpenAPIV3.NonArraySchemaObject;
@@ -0,0 +1,18 @@
1
+ export const PageLayoutDefinition = {
2
+ type: 'object',
3
+ description: 'Grid layout of the page, define as an anchor to re-use across multiple pages',
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=PageLayoutDefinition.js.map
@@ -8,6 +8,9 @@ export const SubPage = {
8
8
  $ref: '#/components/schemas/RemapperDefinition',
9
9
  description: 'The name of the sub page.',
10
10
  },
11
+ layout: {
12
+ $ref: '#/components/schemas/PageLayoutDefinition',
13
+ },
11
14
  roles: {
12
15
  type: 'array',
13
16
  description: `The list of roles that are allowed to view this page.
@@ -120,3 +120,7 @@ export * from './SecurityRoleDefinition.js';
120
120
  export * from './TabsPageDefinition.js';
121
121
  export * from './Theme.js';
122
122
  export * from './WebhookDefinition.js';
123
+ export * from './DeviceGridLayoutDefinition.js';
124
+ export * from './GridLayoutDefinition.js';
125
+ export * from './GridSpacingDefinition.js';
126
+ export * from './PageLayoutDefinition.js';
package/schemas/index.js CHANGED
@@ -120,4 +120,8 @@ export * from './SecurityRoleDefinition.js';
120
120
  export * from './TabsPageDefinition.js';
121
121
  export * from './Theme.js';
122
122
  export * from './WebhookDefinition.js';
123
+ export * from './DeviceGridLayoutDefinition.js';
124
+ export * from './GridLayoutDefinition.js';
125
+ export * from './GridSpacingDefinition.js';
126
+ export * from './PageLayoutDefinition.js';
123
127
  //# sourceMappingURL=index.js.map
package/types/block.d.ts CHANGED
@@ -78,4 +78,8 @@ export interface BlockDefinition extends ControllerDefinition {
78
78
  * The exact meaning of the parameters depends on the project type.
79
79
  */
80
80
  parameters?: JsonObject;
81
+ /**
82
+ * Grid area defined in the page layout definition
83
+ */
84
+ gridArea?: string;
81
85
  }
package/types/core.d.ts CHANGED
@@ -9,6 +9,7 @@ import { type ResourceDefinition } from './resource.js';
9
9
  import { 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
13
  export type SettingName = 'email' | 'languages' | 'name' | 'password' | 'phoneNumber' | 'picture';
13
14
  export interface AppDefinition {
14
15
  /**
@@ -225,10 +226,12 @@ export interface SubPageDefinition {
225
226
  name: Remapper;
226
227
  roles?: ViewRole[];
227
228
  blocks: BlockDefinition[];
229
+ layout?: PageLayoutDefinition;
228
230
  }
229
231
  export interface BasicPageDefinition extends BasePageDefinition {
230
232
  type?: 'page';
231
233
  blocks: BlockDefinition[];
234
+ layout?: PageLayoutDefinition;
232
235
  }
233
236
  export interface ContainerPageDefinition extends BasePageDefinition {
234
237
  type: 'container';
@@ -0,0 +1,19 @@
1
+ import { type RequireAtLeastOne } from 'type-fest';
2
+ export interface GridLayoutDefinition {
3
+ columns: number;
4
+ template: string[];
5
+ }
6
+ export interface GridLayoutSpacingDefinition {
7
+ unit: string;
8
+ padding: number;
9
+ gap: number;
10
+ }
11
+ export interface DeviceGridLayoutDefinition {
12
+ layout: GridLayoutDefinition;
13
+ spacing: GridLayoutSpacingDefinition;
14
+ }
15
+ export type PageLayoutDefinition = RequireAtLeastOne<{
16
+ mobile: DeviceGridLayoutDefinition;
17
+ tablet: DeviceGridLayoutDefinition;
18
+ desktop: DeviceGridLayoutDefinition;
19
+ }>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gridLayout.js.map
package/types/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * from './roles.js';
15
15
  export * from './security.js';
16
16
  export * from './theme.js';
17
17
  export * from './member.js';
18
+ export * from './gridLayout.js';
18
19
  export interface ActionType {
19
20
  /**
20
21
  * Whether or not app creators are required to define this action.
package/types/index.js CHANGED
@@ -14,4 +14,5 @@ export * from './roles.js';
14
14
  export * from './security.js';
15
15
  export * from './theme.js';
16
16
  export * from './member.js';
17
+ export * from './gridLayout.js';
17
18
  //# sourceMappingURL=index.js.map
package/validation.js CHANGED
@@ -299,6 +299,149 @@ function validateController(definition, controllerImplementations, report) {
299
299
  },
300
300
  });
301
301
  }
302
+ function getTemplateAreas(layoutDefinition) {
303
+ const areas = new Set();
304
+ if (!layoutDefinition) {
305
+ return areas;
306
+ }
307
+ for (const deviceDefinition of Object.values(layoutDefinition)) {
308
+ if (deviceDefinition?.layout?.template) {
309
+ for (const row of deviceDefinition.layout.template) {
310
+ for (const area of row.split(' ')) {
311
+ if (area && area !== '.') {
312
+ areas.add(area);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ return areas;
319
+ }
320
+ function validateTemplateAreasAreRectangular(template, report, path) {
321
+ const grid = template.map((row) => row.split(' '));
322
+ function isAreaRectangular(areaName) {
323
+ const cells = [];
324
+ for (const [row, rowCells] of grid.entries()) {
325
+ for (const [col, cell] of rowCells.entries()) {
326
+ if (cell === areaName) {
327
+ cells.push({ row, col });
328
+ }
329
+ }
330
+ }
331
+ if (cells.length === 0) {
332
+ return true;
333
+ }
334
+ let minRow = Infinity;
335
+ let maxRow = -Infinity;
336
+ let minCol = Infinity;
337
+ let maxCol = -Infinity;
338
+ for (const cell of cells) {
339
+ minRow = Math.min(minRow, cell.row);
340
+ maxRow = Math.max(maxRow, cell.row);
341
+ minCol = Math.min(minCol, cell.col);
342
+ maxCol = Math.max(maxCol, cell.col);
343
+ }
344
+ const expectedCount = (maxRow - minRow + 1) * (maxCol - minCol + 1);
345
+ if (cells.length !== expectedCount) {
346
+ return false;
347
+ }
348
+ for (let row = minRow; row <= maxRow; row += 1) {
349
+ for (let col = minCol; col <= maxCol; col += 1) {
350
+ if (grid[row][col] !== areaName) {
351
+ return false;
352
+ }
353
+ }
354
+ }
355
+ return true;
356
+ }
357
+ const areas = new Set();
358
+ for (const row of grid) {
359
+ for (const cell of row) {
360
+ if (cell && cell !== '.') {
361
+ areas.add(cell);
362
+ }
363
+ }
364
+ }
365
+ for (const area of areas) {
366
+ if (!isAreaRectangular(area)) {
367
+ report(template, `grid area '${area}' does not form a single contiguous rectangle`, path);
368
+ }
369
+ }
370
+ }
371
+ function validatePageLayoutDefinition(layoutDefinition, report, path) {
372
+ if (!layoutDefinition) {
373
+ return;
374
+ }
375
+ for (const [deviceName, deviceDefinition] of Object.entries(layoutDefinition)) {
376
+ if (!deviceDefinition?.layout?.template || !deviceDefinition?.layout?.columns) {
377
+ continue;
378
+ }
379
+ if (deviceDefinition.layout.template.some((row) => row.split(' ').length !== deviceDefinition.layout.columns)) {
380
+ report(deviceDefinition.layout.template, 'template needs to be the same length as number of columns', [...path, deviceName, 'layout', 'template']);
381
+ }
382
+ validateTemplateAreasAreRectangular(deviceDefinition.layout.template, report, [
383
+ ...path,
384
+ deviceName,
385
+ 'layout',
386
+ 'template',
387
+ ]);
388
+ }
389
+ }
390
+ function validateBlockGridAreas(blocks, layoutDefinition, report, path) {
391
+ const templateAreas = getTemplateAreas(layoutDefinition);
392
+ for (const [idx, block] of blocks.entries()) {
393
+ if (block.gridArea && !templateAreas.has(block.gridArea)) {
394
+ report(block.gridArea, `does not match any area defined in the layout template. Available areas: ${[...templateAreas].join(', ') || 'none'}`, [...path, idx, 'gridArea']);
395
+ }
396
+ }
397
+ }
398
+ function validateSubPageLayout(layout, blocks, report, path) {
399
+ validatePageLayoutDefinition(layout, report, path);
400
+ if (layout && blocks) {
401
+ validateBlockGridAreas(blocks, layout, report, [...path, 'blocks']);
402
+ }
403
+ }
404
+ function validateGridLayout(definition, report) {
405
+ iterApp(definition, {
406
+ onPage(page, path) {
407
+ // Basic page
408
+ if (page.type === 'page' || page.type === undefined) {
409
+ const basicPage = page;
410
+ validateSubPageLayout(basicPage.layout, basicPage.blocks, report, path);
411
+ return;
412
+ }
413
+ // Tabs page
414
+ if (page.type === 'tabs') {
415
+ const tabsPage = page;
416
+ if (tabsPage.tabs) {
417
+ for (const [tabIdx, tab] of tabsPage.tabs.entries()) {
418
+ validateSubPageLayout(tab.layout, tab.blocks, report, [...path, 'tabs', tabIdx]);
419
+ }
420
+ }
421
+ else if (tabsPage.definition?.foreach) {
422
+ validateSubPageLayout(tabsPage.definition.foreach.layout, tabsPage.definition.foreach.blocks, report, [...path, 'definition', 'foreach']);
423
+ }
424
+ return;
425
+ }
426
+ // Flow page
427
+ if (page.type === 'flow') {
428
+ const flowPage = page;
429
+ for (const [stepIdx, step] of flowPage.steps.entries()) {
430
+ validateSubPageLayout(step.layout, step.blocks, report, [...path, 'steps', stepIdx]);
431
+ }
432
+ return;
433
+ }
434
+ // Loop page
435
+ if (page.type === 'loop') {
436
+ const loopPage = page;
437
+ validateSubPageLayout(loopPage.foreach.layout, loopPage.foreach.blocks, report, [
438
+ ...path,
439
+ 'foreach',
440
+ ]);
441
+ }
442
+ },
443
+ });
444
+ }
302
445
  function validateBlocks(definition, blockVersions, report) {
303
446
  iterApp(definition, {
304
447
  onBlock(block, path) {
@@ -1241,6 +1384,7 @@ export async function validateAppDefinition(definition, getBlockVersions, contro
1241
1384
  validateActions(definition, report);
1242
1385
  validateEvents(definition, blockVersionMap, report);
1243
1386
  validateUniquePageNames(definition, report);
1387
+ validateGridLayout(definition, report);
1244
1388
  }
1245
1389
  catch (error) {
1246
1390
  report(null, `Unexpected error: ${error instanceof Error ? error.message : error}`, []);