@superblocksteam/util 0.0.22 → 1.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.
@@ -15,7 +15,8 @@ const compileTypeScript = (inputFilePath, outputDir) => {
15
15
  try {
16
16
  const tscPath = require.resolve("typescript/bin/tsc");
17
17
  if (tscPath) {
18
- (0, child_process_1.execFile)(tscPath, [
18
+ (0, child_process_1.execFile)("node", [
19
+ tscPath,
19
20
  node_path_1.default.resolve(inputFilePath),
20
21
  "--outDir",
21
22
  outputDir,
@@ -59,9 +60,13 @@ async function getFolderPaths() {
59
60
  withFileTypes: true,
60
61
  });
61
62
  // filter out any non-directory items
62
- const directories = folderPaths.filter((dirent) => dirent.isDirectory());
63
+ const directories = folderPaths.filter((dirent) => dirent.isDirectory() ||
64
+ (dirent.isSymbolicLink() &&
65
+ fs
66
+ .statSync(node_path_1.default.join(constants_1.CUSTOM_COMPONENTS_PATH, dirent.name))
67
+ .isDirectory()));
63
68
  // map each directory to its path
64
- const folderPathsArray = directories.map((dirent) => node_path_1.default.resolve(`${constants_1.CUSTOM_COMPONENTS_PATH}/${dirent.name}`));
69
+ const folderPathsArray = directories.map((dirent) => node_path_1.default.join(constants_1.CUSTOM_COMPONENTS_PATH, dirent.name));
65
70
  return folderPathsArray;
66
71
  }
67
72
  catch {
@@ -72,9 +77,10 @@ async function getComponentConfigs(generateTypesFiles) {
72
77
  const folderPaths = await getFolderPaths();
73
78
  let hasError = false;
74
79
  const promiseConfigFiles = folderPaths.map(async (ccpath) => {
80
+ const absPath = node_path_1.default.resolve(ccpath);
75
81
  try {
76
- const config = await fs.readJSON(`${ccpath}/config.json`);
77
- console.log(`Found component in ${ccpath}`);
82
+ const config = await fs.readJSON(`${absPath}/config.json`);
83
+ console.log(`Found component in ${absPath}`);
78
84
  return { ccpath, config };
79
85
  }
80
86
  catch (e) {
@@ -91,6 +97,7 @@ async function getComponentConfigs(generateTypesFiles) {
91
97
  const outputDirectory = node_path_1.default.resolve(`${node_os_1.default.tmpdir()}/superblocks/${ccpath}/config.timestamp-${Date.now()}-${Math.random()
92
98
  .toString(16)
93
99
  .slice(2)}`);
100
+ await fs.ensureDir(outputDirectory);
94
101
  await compileTypeScript(fileName, outputDirectory);
95
102
  console.log(`Typescript compiled to ${ccpath}`);
96
103
  const config = (await Promise.resolve(`${outputDirectory + "/config.js"}`).then(s => tslib_1.__importStar(require(s)))).default;
@@ -7,3 +7,7 @@ export declare const ERROR_FILE_ACCESS = "FileAccessError";
7
7
  export declare class FileAccessError extends Error {
8
8
  constructor(message: string);
9
9
  }
10
+ export declare const ERROR_FILE_NOT_FOUND = "FileNotFoundError";
11
+ export declare class NotFoundError extends Error {
12
+ constructor(message: string);
13
+ }
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FileAccessError = exports.ERROR_FILE_ACCESS = exports.CUSTOM_COMPONENTS_PATH = exports.ROOT_CONFIG_RELATIVE_PATH = exports.RESOURCE_CONFIG_PATH = exports.TOKEN_CONFIG_PATH = exports.SUPERBLOCKS_HOME_FOLDER_NAME = void 0;
3
+ exports.NotFoundError = exports.ERROR_FILE_NOT_FOUND = exports.FileAccessError = exports.ERROR_FILE_ACCESS = exports.CUSTOM_COMPONENTS_PATH = exports.ROOT_CONFIG_RELATIVE_PATH = exports.RESOURCE_CONFIG_PATH = exports.TOKEN_CONFIG_PATH = exports.SUPERBLOCKS_HOME_FOLDER_NAME = void 0;
4
4
  exports.SUPERBLOCKS_HOME_FOLDER_NAME = ".superblocks";
5
5
  exports.TOKEN_CONFIG_PATH = ".superblocks/auth.json";
6
6
  exports.RESOURCE_CONFIG_PATH = ".superblocks/superblocks.json";
@@ -27,3 +27,11 @@ class FileAccessError extends Error {
27
27
  }
28
28
  }
29
29
  exports.FileAccessError = FileAccessError;
30
+ exports.ERROR_FILE_NOT_FOUND = "FileNotFoundError";
31
+ class NotFoundError extends Error {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = exports.ERROR_FILE_NOT_FOUND;
35
+ }
36
+ }
37
+ exports.NotFoundError = NotFoundError;
@@ -10,31 +10,37 @@ const indent = (str, spaces) => {
10
10
  .join("\n");
11
11
  };
12
12
  function renderInputTypeAsTS(name, type, trailingChars, indentSpaces) {
13
- return indent(`${name}: ${data_types_1.dataTypeDefinions[type].tsType}${trailingChars}`, indentSpaces);
13
+ let { tsType } = data_types_1.dataTypeDefinions[type];
14
+ if (["number", "string"].includes(type)) {
15
+ tsType += "| null";
16
+ }
17
+ return indent(`${name}: ${tsType}${trailingChars}`, indentSpaces);
14
18
  }
15
19
  function generateComponentTypesFile(config) {
16
20
  var _a, _b;
17
21
  const properties = (_a = config.properties) !== null && _a !== void 0 ? _a : [];
18
22
  const eventHandlers = (_b = config.events) !== null && _b !== void 0 ? _b : [];
19
- return `// WARNING: This file is automatically generated and managed by Superblocks CLI and should not be edited manually.
23
+ return `// GENERATED CODE -- DO NOT EDIT!
24
+ //
25
+ // This file is automatically generated by the Superblocks CLI
20
26
  // Use the \`superblocks components watch\` command to regenerate this file from its source in config.ts
21
27
 
22
28
  // All properties of your component are defined here.
23
29
  // These are the properties which are surfaced in the Superblocks properties panel
24
30
  // and can be referenced throughout your Superblocks Application
25
- export interface Props {
31
+ export type Props = {
26
32
  ${properties
27
33
  .map((v) => renderInputTypeAsTS(v.path, v.dataType, ";", 2) + "\n")
28
- .join("")}}
34
+ .join("")}};
29
35
 
30
- export interface EventTriggers {
36
+ export type EventTriggers = {
31
37
  // Call the subsequent function(s) to trigger event(s) in Superblocks from your component.
32
38
  // These events can be wired up to event handlers in your Superblocks App
33
39
  ${eventHandlers.length === 0
34
40
  ? " // Note: no event handlers defined\n"
35
41
  : eventHandlers
36
42
  .map((v) => indent(v.path, 2) + ": () => void;" + "\n")
37
- .join("")}}
43
+ .join("")}};
38
44
  `;
39
45
  }
40
46
  exports.generateComponentTypesFile = generateComponentTypesFile;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSuperblocksResourceConfigIfExists = exports.getSuperblocksBackendConfigJson = exports.getSuperblocksApplicationConfigJson = exports.getSuperblocksMonorepoConfigJson = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
5
6
  const fs = tslib_1.__importStar(require("fs-extra"));
6
7
  const constants_1 = require("./constants");
7
8
  /**
@@ -9,27 +10,32 @@ const constants_1 = require("./constants");
9
10
  * @param checkParentDir Whether to recursively check the parent directory for a Superblocks config file
10
11
  */
11
12
  async function getSuperblocksMonorepoConfigJson(checkParentDir = false, pathPrefix = "") {
13
+ const absolutePath = path_1.default.resolve(pathPrefix);
14
+ let hasReachedFilesystemRoot = false;
15
+ try {
16
+ hasReachedFilesystemRoot = path_1.default.parse(absolutePath).root == absolutePath;
17
+ }
18
+ catch {
19
+ throw new Error("Could not parse path " + absolutePath);
20
+ }
12
21
  let superblocksConfig;
13
- if (pathPrefix && !fs.existsSync(pathPrefix)) {
22
+ if ((pathPrefix && !fs.existsSync(absolutePath)) ||
23
+ hasReachedFilesystemRoot) {
14
24
  throw new Error("No Superblocks config file found in current directory hierarchy " +
15
25
  pathPrefix);
16
26
  }
27
+ const attemptedPath = path_1.default.join(absolutePath, constants_1.RESOURCE_CONFIG_PATH);
17
28
  try {
18
- superblocksConfig = await fs.readJSON(pathPrefix + constants_1.RESOURCE_CONFIG_PATH);
29
+ superblocksConfig = await fs.readJSON(attemptedPath);
19
30
  if (superblocksConfig.configType !== "ROOT") {
20
31
  throw new Error("Not the root Superblocks config file");
21
32
  }
22
- return [
23
- superblocksConfig,
24
- pathPrefix + constants_1.RESOURCE_CONFIG_PATH,
25
- ];
33
+ return [superblocksConfig, attemptedPath];
26
34
  }
27
35
  catch {
28
36
  if (!checkParentDir) {
29
37
  // no superblocks config file found
30
- throw new Error("No Superblocks config file found in current directory " +
31
- pathPrefix +
32
- constants_1.RESOURCE_CONFIG_PATH);
38
+ throw new Error("No Superblocks config file found in current directory " + attemptedPath);
33
39
  }
34
40
  return getSuperblocksMonorepoConfigJson(true, "../" + pathPrefix);
35
41
  }
package/dist/types.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  /// <reference types="node" />
2
2
  import { type UUID } from "node:crypto";
3
3
  import { type DataType } from "./data-types";
4
- interface PropertiesPanelDisplay {
4
+ interface PropertiesPanelDisplay<T extends DataType> {
5
5
  /**
6
6
  * Determines what form item type is shown in the Superblocks properties panel.
7
+ *
8
+ * Only booleans are allowed to use the "switch" control
7
9
  */
8
- controlType: "text" | "js-expr" | "switch";
10
+ controlType: T extends "boolean" ? "text" | "js-expr" | "switch" : "text" | "js-expr";
9
11
  /**
10
12
  * Determines form item label in the Superblocks properties panel.
11
13
  */
@@ -18,18 +20,39 @@ interface PropertiesPanelDisplay {
18
20
  /**
19
21
  * This shows up in the popover as a user types into the properties panel.
20
22
  * This is a documentation field that helps users understand what type of data is accepted
21
- * by this property.
23
+ * by this property. You should give a specific example, not a generic type
24
+ *
25
+ * @example {
26
+ * label: "My Label",
27
+ * value: "myValue"
28
+ * }
22
29
  */
23
30
  exampleData?: string;
31
+ /**
32
+ * This shows up in the popover as a user types into the properties panel.
33
+ * This is a documentation field that helps users understand what type of data is accepted
34
+ * by this property. Typescript notation is recommended.
35
+ *
36
+ * @example "Array<{ label: string; value: string; }>"
37
+ */
38
+ expectedType?: string;
39
+ /**
40
+ * The default value should match the dataType of the property.
41
+ *
42
+ * When dataType is "any", you must provide a string default value.
43
+ * Your string will be interpreted based on the controlType, so if you have controlType "js-expr",
44
+ * you should provide a string that is a valid javascript expression including quotations marks.
45
+ */
46
+ defaultValue?: T extends "number" ? number : T extends "boolean" ? boolean : string;
24
47
  }
25
- export interface Property {
48
+ export interface PropertyForData<T extends DataType> {
26
49
  path: string;
27
- dataType: DataType;
50
+ dataType: T;
28
51
  /**
29
52
  * The presence of this object determines whether or not this property
30
53
  * shows up in the Superblocks properties panel.
31
54
  **/
32
- propertiesPanelDisplay?: PropertiesPanelDisplay;
55
+ propertiesPanelDisplay?: PropertiesPanelDisplay<T>;
33
56
  /**
34
57
  * A description of this property. This is used in the Superblocks properties panel
35
58
  * as well as in the custom component's autocomplete.
@@ -48,6 +71,7 @@ export interface Property {
48
71
  **/
49
72
  isExternallySettable?: boolean;
50
73
  }
74
+ export type Property = PropertyForData<"string"> | PropertyForData<"number"> | PropertyForData<"boolean"> | PropertyForData<"any">;
51
75
  export interface ComponentConfig {
52
76
  id: UUID;
53
77
  /**
@@ -62,7 +86,7 @@ export interface ComponentConfig {
62
86
  * @example "components/myComponent/component.tsx"
63
87
  */
64
88
  componentPath: string;
65
- properties: Property[];
89
+ properties: Array<PropertyForData<"string"> | PropertyForData<"number"> | PropertyForData<"boolean"> | PropertyForData<"any">>;
66
90
  /**
67
91
  * @example [{
68
92
  * label: "On Click",
@@ -73,5 +97,9 @@ export interface ComponentConfig {
73
97
  label: string;
74
98
  path: string;
75
99
  }>;
100
+ gridDimensions?: {
101
+ initialColumns: number;
102
+ initialRows: number;
103
+ };
76
104
  }
77
105
  export {};
@@ -24,6 +24,15 @@ const componentSchema = {
24
24
  $ref: "#/definitions/EventHandler",
25
25
  },
26
26
  },
27
+ gridDimensions: {
28
+ type: "object",
29
+ properties: {
30
+ initialColumns: { type: "number" },
31
+ initialRows: { type: "number" },
32
+ },
33
+ additionalProperties: false,
34
+ required: ["initialColumns", "initialRows"],
35
+ },
27
36
  },
28
37
  required: [
29
38
  "id",
@@ -44,9 +53,22 @@ const componentSchema = {
44
53
  properties: {
45
54
  label: { type: "string" },
46
55
  controlType: { enum: ["text", "js-expr", "switch"] },
47
- defaultValue: { type: "string" },
56
+ defaultValue: {
57
+ oneOf: [
58
+ {
59
+ type: "string",
60
+ },
61
+ {
62
+ type: "boolean",
63
+ },
64
+ {
65
+ type: "number",
66
+ },
67
+ ],
68
+ },
48
69
  placeholder: { type: "string" },
49
70
  exampleData: { type: "string" },
71
+ expectedType: { type: "string" },
50
72
  },
51
73
  required: ["controlType", "label"],
52
74
  additionalProperties: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/util",
3
- "version": "0.0.22",
3
+ "version": "1.0.0",
4
4
  "main": "dist/index.js",
5
5
  "homepage": "https://www.superblocks.com",
6
6
  "scripts": {
@@ -9,7 +9,8 @@
9
9
  "clean": "npm run clean:build && rm -rf node_modules",
10
10
  "clean:build": "tsc --build --clean && rm -rf dist tsconfig.tsbuildinfo",
11
11
  "lint": "eslint . --ext .ts --config .eslintrc.json",
12
- "lint:fix": "eslint . --ext .ts --config .eslintrc.json --fix"
12
+ "lint:fix": "eslint . --ext .ts --config .eslintrc.json --fix",
13
+ "typecheck": "tsc --noEmit"
13
14
  },
14
15
  "license": "Superblocks Community Software License",
15
16
  "description": "",
@@ -13,8 +13,9 @@ const compileTypeScript = (inputFilePath: string, outputDir: string) => {
13
13
  const tscPath = require.resolve("typescript/bin/tsc");
14
14
  if (tscPath) {
15
15
  execFile(
16
- tscPath,
16
+ "node",
17
17
  [
18
+ tscPath,
18
19
  path.resolve(inputFilePath),
19
20
  "--outDir",
20
21
  outputDir,
@@ -64,10 +65,17 @@ async function getFolderPaths() {
64
65
  withFileTypes: true,
65
66
  });
66
67
  // filter out any non-directory items
67
- const directories = folderPaths.filter((dirent) => dirent.isDirectory());
68
+ const directories = folderPaths.filter(
69
+ (dirent) =>
70
+ dirent.isDirectory() ||
71
+ (dirent.isSymbolicLink() &&
72
+ fs
73
+ .statSync(path.join(CUSTOM_COMPONENTS_PATH, dirent.name))
74
+ .isDirectory())
75
+ );
68
76
  // map each directory to its path
69
77
  const folderPathsArray = directories.map((dirent) =>
70
- path.resolve(`${CUSTOM_COMPONENTS_PATH}/${dirent.name}`)
78
+ path.join(CUSTOM_COMPONENTS_PATH, dirent.name)
71
79
  );
72
80
  return folderPathsArray;
73
81
  } catch {
@@ -86,9 +94,10 @@ export async function getComponentConfigs(
86
94
  const folderPaths = await getFolderPaths();
87
95
  let hasError = false;
88
96
  const promiseConfigFiles = folderPaths.map(async (ccpath) => {
97
+ const absPath = path.resolve(ccpath);
89
98
  try {
90
- const config = await fs.readJSON(`${ccpath}/config.json`);
91
- console.log(`Found component in ${ccpath}`);
99
+ const config = await fs.readJSON(`${absPath}/config.json`);
100
+ console.log(`Found component in ${absPath}`);
92
101
  return { ccpath, config };
93
102
  } catch (e: any) {
94
103
  // Noop because there is a fallback
@@ -107,6 +116,7 @@ export async function getComponentConfigs(
107
116
  .toString(16)
108
117
  .slice(2)}`
109
118
  );
119
+ await fs.ensureDir(outputDirectory);
110
120
  await compileTypeScript(fileName, outputDirectory);
111
121
  console.log(`Typescript compiled to ${ccpath}`);
112
122
  const config = (await import(outputDirectory + "/config.js")).default;
package/src/constants.ts CHANGED
@@ -26,3 +26,11 @@ export class FileAccessError extends Error {
26
26
  this.name = ERROR_FILE_ACCESS;
27
27
  }
28
28
  }
29
+
30
+ export const ERROR_FILE_NOT_FOUND = "FileNotFoundError";
31
+ export class NotFoundError extends Error {
32
+ constructor(message: string) {
33
+ super(message);
34
+ this.name = ERROR_FILE_NOT_FOUND;
35
+ }
36
+ }
@@ -16,27 +16,30 @@ function renderInputTypeAsTS(
16
16
  trailingChars: string,
17
17
  indentSpaces: number
18
18
  ) {
19
- return indent(
20
- `${name}: ${dataTypeDefinions[type].tsType}${trailingChars}`,
21
- indentSpaces
22
- );
19
+ let { tsType } = dataTypeDefinions[type];
20
+ if (["number", "string"].includes(type)) {
21
+ tsType += "| null";
22
+ }
23
+ return indent(`${name}: ${tsType}${trailingChars}`, indentSpaces);
23
24
  }
24
25
 
25
26
  export function generateComponentTypesFile(config: ComponentConfig) {
26
27
  const properties = config.properties ?? [];
27
28
  const eventHandlers = config.events ?? [];
28
- return `// WARNING: This file is automatically generated and managed by Superblocks CLI and should not be edited manually.
29
+ return `// GENERATED CODE -- DO NOT EDIT!
30
+ //
31
+ // This file is automatically generated by the Superblocks CLI
29
32
  // Use the \`superblocks components watch\` command to regenerate this file from its source in config.ts
30
33
 
31
34
  // All properties of your component are defined here.
32
35
  // These are the properties which are surfaced in the Superblocks properties panel
33
36
  // and can be referenced throughout your Superblocks Application
34
- export interface Props {
37
+ export type Props = {
35
38
  ${properties
36
39
  .map((v) => renderInputTypeAsTS(v.path, v.dataType, ";", 2) + "\n")
37
- .join("")}}
40
+ .join("")}};
38
41
 
39
- export interface EventTriggers {
42
+ export type EventTriggers = {
40
43
  // Call the subsequent function(s) to trigger event(s) in Superblocks from your component.
41
44
  // These events can be wired up to event handlers in your Superblocks App
42
45
  ${
@@ -45,6 +48,6 @@ ${
45
48
  : eventHandlers
46
49
  .map((v: any) => indent(v.path, 2) + ": () => void;" + "\n")
47
50
  .join("")
48
- }}
51
+ }};
49
52
  `;
50
53
  }
@@ -1,3 +1,4 @@
1
+ import path from "path";
1
2
  import * as fs from "fs-extra";
2
3
  import { RESOURCE_CONFIG_PATH } from "./constants";
3
4
 
@@ -45,30 +46,38 @@ export async function getSuperblocksMonorepoConfigJson(
45
46
  checkParentDir = false,
46
47
  pathPrefix = ""
47
48
  ): Promise<[SuperblocksMonorepoConfig, string]> {
49
+ const absolutePath = path.resolve(pathPrefix);
50
+
51
+ let hasReachedFilesystemRoot = false;
52
+ try {
53
+ hasReachedFilesystemRoot = path.parse(absolutePath).root == absolutePath;
54
+ } catch {
55
+ throw new Error("Could not parse path " + absolutePath);
56
+ }
57
+
48
58
  let superblocksConfig;
49
- if (pathPrefix && !fs.existsSync(pathPrefix)) {
59
+ if (
60
+ (pathPrefix && !fs.existsSync(absolutePath)) ||
61
+ hasReachedFilesystemRoot
62
+ ) {
50
63
  throw new Error(
51
64
  "No Superblocks config file found in current directory hierarchy " +
52
65
  pathPrefix
53
66
  );
54
67
  }
55
68
 
69
+ const attemptedPath = path.join(absolutePath, RESOURCE_CONFIG_PATH);
56
70
  try {
57
- superblocksConfig = await fs.readJSON(pathPrefix + RESOURCE_CONFIG_PATH);
71
+ superblocksConfig = await fs.readJSON(attemptedPath);
58
72
  if (superblocksConfig.configType !== "ROOT") {
59
73
  throw new Error("Not the root Superblocks config file");
60
74
  }
61
- return [
62
- superblocksConfig as SuperblocksMonorepoConfig,
63
- pathPrefix + RESOURCE_CONFIG_PATH,
64
- ];
75
+ return [superblocksConfig as SuperblocksMonorepoConfig, attemptedPath];
65
76
  } catch {
66
77
  if (!checkParentDir) {
67
78
  // no superblocks config file found
68
79
  throw new Error(
69
- "No Superblocks config file found in current directory " +
70
- pathPrefix +
71
- RESOURCE_CONFIG_PATH
80
+ "No Superblocks config file found in current directory " + attemptedPath
72
81
  );
73
82
  }
74
83
  return getSuperblocksMonorepoConfigJson(true, "../" + pathPrefix);
package/src/types.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import { type UUID } from "node:crypto";
2
2
  import { type DataType } from "./data-types";
3
3
 
4
- interface PropertiesPanelDisplay {
4
+ interface PropertiesPanelDisplay<T extends DataType> {
5
5
  /**
6
6
  * Determines what form item type is shown in the Superblocks properties panel.
7
+ *
8
+ * Only booleans are allowed to use the "switch" control
7
9
  */
8
- controlType: "text" | "js-expr" | "switch";
10
+ controlType: T extends "boolean"
11
+ ? "text" | "js-expr" | "switch"
12
+ : "text" | "js-expr";
9
13
 
10
14
  /**
11
15
  * Determines form item label in the Superblocks properties panel.
@@ -21,20 +25,47 @@ interface PropertiesPanelDisplay {
21
25
  /**
22
26
  * This shows up in the popover as a user types into the properties panel.
23
27
  * This is a documentation field that helps users understand what type of data is accepted
24
- * by this property.
28
+ * by this property. You should give a specific example, not a generic type
29
+ *
30
+ * @example {
31
+ * label: "My Label",
32
+ * value: "myValue"
33
+ * }
25
34
  */
26
35
  exampleData?: string;
36
+
37
+ /**
38
+ * This shows up in the popover as a user types into the properties panel.
39
+ * This is a documentation field that helps users understand what type of data is accepted
40
+ * by this property. Typescript notation is recommended.
41
+ *
42
+ * @example "Array<{ label: string; value: string; }>"
43
+ */
44
+ expectedType?: string;
45
+
46
+ /**
47
+ * The default value should match the dataType of the property.
48
+ *
49
+ * When dataType is "any", you must provide a string default value.
50
+ * Your string will be interpreted based on the controlType, so if you have controlType "js-expr",
51
+ * you should provide a string that is a valid javascript expression including quotations marks.
52
+ */
53
+ defaultValue?: T extends "number"
54
+ ? number
55
+ : T extends "boolean"
56
+ ? boolean
57
+ : string;
27
58
  }
28
59
 
29
- export interface Property {
60
+ export interface PropertyForData<T extends DataType> {
30
61
  path: string;
31
- dataType: DataType;
62
+ dataType: T;
32
63
 
33
64
  /**
34
65
  * The presence of this object determines whether or not this property
35
66
  * shows up in the Superblocks properties panel.
36
67
  **/
37
- propertiesPanelDisplay?: PropertiesPanelDisplay;
68
+ propertiesPanelDisplay?: PropertiesPanelDisplay<T>;
38
69
 
39
70
  /**
40
71
  * A description of this property. This is used in the Superblocks properties panel
@@ -57,6 +88,12 @@ export interface Property {
57
88
  isExternallySettable?: boolean;
58
89
  }
59
90
 
91
+ export type Property =
92
+ | PropertyForData<"string">
93
+ | PropertyForData<"number">
94
+ | PropertyForData<"boolean">
95
+ | PropertyForData<"any">;
96
+
60
97
  export interface ComponentConfig {
61
98
  id: UUID;
62
99
 
@@ -75,7 +112,15 @@ export interface ComponentConfig {
75
112
  */
76
113
  componentPath: string;
77
114
 
78
- properties: Property[];
115
+ properties: Array<
116
+ // Note to devs: The reason we are duplicating the Property type above is that it produces a better
117
+ // error message. The user will see 'is not assignable to type PropertyForData<"string"> | PropertyForData<"number">'
118
+ // instead of seeing "is not assignable to type Property"
119
+ | PropertyForData<"string">
120
+ | PropertyForData<"number">
121
+ | PropertyForData<"boolean">
122
+ | PropertyForData<"any">
123
+ >;
79
124
 
80
125
  /**
81
126
  * @example [{
@@ -87,4 +132,9 @@ export interface ComponentConfig {
87
132
  label: string;
88
133
  path: string;
89
134
  }>;
135
+
136
+ gridDimensions?: {
137
+ initialColumns: number;
138
+ initialRows: number;
139
+ };
90
140
  }
package/src/validation.ts CHANGED
@@ -22,6 +22,15 @@ const componentSchema: Schema = {
22
22
  $ref: "#/definitions/EventHandler",
23
23
  },
24
24
  },
25
+ gridDimensions: {
26
+ type: "object",
27
+ properties: {
28
+ initialColumns: { type: "number" },
29
+ initialRows: { type: "number" },
30
+ },
31
+ additionalProperties: false,
32
+ required: ["initialColumns", "initialRows"],
33
+ },
25
34
  },
26
35
  required: [
27
36
  "id",
@@ -42,9 +51,22 @@ const componentSchema: Schema = {
42
51
  properties: {
43
52
  label: { type: "string" },
44
53
  controlType: { enum: ["text", "js-expr", "switch"] },
45
- defaultValue: { type: "string" },
54
+ defaultValue: {
55
+ oneOf: [
56
+ {
57
+ type: "string",
58
+ },
59
+ {
60
+ type: "boolean",
61
+ },
62
+ {
63
+ type: "number",
64
+ },
65
+ ],
66
+ },
46
67
  placeholder: { type: "string" },
47
68
  exampleData: { type: "string" },
69
+ expectedType: { type: "string" },
48
70
  },
49
71
  required: ["controlType", "label"],
50
72
  additionalProperties: false,