From 2f44f530219db68b79db50c275f2948a731a0162 Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Mon, 6 Sep 2021 10:53:36 +0200 Subject: [PATCH] Started implementing shothand prop types --- theatre/core/src/propTypes/index.ts | 65 ++++++++++++++++++++----- theatre/core/src/propTypes/internals.ts | 39 +++++++++++++++ theatre/shared/src/testUtils.ts | 4 +- 3 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 theatre/core/src/propTypes/internals.ts diff --git a/theatre/core/src/propTypes/index.ts b/theatre/core/src/propTypes/index.ts index baf4927..6bba38e 100644 --- a/theatre/core/src/propTypes/index.ts +++ b/theatre/core/src/propTypes/index.ts @@ -13,9 +13,10 @@ * ``` * @module types */ +import {InvalidArgumentError} from '@theatre/shared/utils/errors' import type {$IntentionalAny} from '@theatre/shared/utils/types' - -const s = Symbol('TheatrePropType_Basic') +import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' +import {isLonghandPropType, propTypeSymbol, toLonghandProp} from './internals' /** * Creates a compound prop type (basically a JS object). @@ -43,11 +44,53 @@ export const compound = ( props: Props, extras?: PropTypeConfigExtras, ): PropTypeConfig_Compound => { + let sanitizedProps: Props + if (process.env.NODE_ENV !== 'production') { + sanitizedProps = {} as $IntentionalAny + if (typeof props !== 'object' || !props) { + throw new InvalidArgumentError( + `t.compound() expects an object, like: {x: 10}. ${userReadableTypeOfValue( + props, + )} given.`, + ) + } + for (const key of Object.keys(props) as Array) { + if (typeof key !== 'string') { + throw new InvalidArgumentError( + `t.compound()'s keys must be all strings. ${userReadableTypeOfValue( + key, + )} given.`, + ) + } else if (key.length === 0 || !key.match(/^\w+$/)) { + throw new InvalidArgumentError( + `compound key ${userReadableTypeOfValue( + key, + )} is invalid. The keys must be alphanumeric and start with a letter.`, + ) + } else if (key.length > 64) { + throw new InvalidArgumentError( + `compound key ${userReadableTypeOfValue(key)} is too long.`, + ) + } + + const val = props[key] + if (isLonghandPropType(val)) { + sanitizedProps[key as keyof Props] = val as $IntentionalAny + } else { + sanitizedProps[key as keyof Props] = toLonghandProp( + val, + ) as $IntentionalAny + } + } + } else { + sanitizedProps = {...props} + } + return { type: 'compound', - props, + props: sanitizedProps, valueType: null as $IntentionalAny, - [s]: 'TheatrePropType', + [propTypeSymbol]: 'TheatrePropType', label: extras?.label, } } @@ -72,7 +115,7 @@ export const number = ( type: 'number', valueType: 0, default: defaultValue, - [s]: 'TheatrePropType', + [propTypeSymbol]: 'TheatrePropType', ...(opts ? opts : {}), label: opts?.label, nudgeFn: opts?.nudgeFn ?? defaultNumberNudgeFn, @@ -97,7 +140,7 @@ export const boolean = ( type: 'boolean', default: defaultValue, valueType: null as $IntentionalAny, - [s]: 'TheatrePropType', + [propTypeSymbol]: 'TheatrePropType', label: extras?.label, } } @@ -112,14 +155,14 @@ export const boolean = ( */ export const string = ( defaultValue: string, - extras: PropTypeConfigExtras, + extras?: PropTypeConfigExtras, ): PropTypeConfig_String => { return { type: 'string', default: defaultValue, valueType: null as $IntentionalAny, - [s]: 'TheatrePropType', - label: extras.label, + [propTypeSymbol]: 'TheatrePropType', + label: extras?.label, } } @@ -141,7 +184,7 @@ export function stringLiteral( type: 'stringLiteral', default: defaultValue, options: {...options}, - [s]: 'TheatrePropType', + [propTypeSymbol]: 'TheatrePropType', valueType: null as $IntentionalAny, as: extras?.as ?? 'menu', label: extras?.label, @@ -163,7 +206,7 @@ export function stringLiteral( interface IBasePropType { valueType: ValueType - [s]: 'TheatrePropType' + [propTypeSymbol]: 'TheatrePropType' label: string | undefined } diff --git a/theatre/core/src/propTypes/internals.ts b/theatre/core/src/propTypes/internals.ts new file mode 100644 index 0000000..b1daf21 --- /dev/null +++ b/theatre/core/src/propTypes/internals.ts @@ -0,0 +1,39 @@ +import {InvalidArgumentError} from '@theatre/shared/utils/errors' +import type {$IntentionalAny} from '@theatre/shared/utils/types' +import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' +import {isPlainObject} from 'lodash-es' +import type {PropTypeConfig} from './index' +import * as t from './index' + +export const propTypeSymbol = Symbol('TheatrePropType_Basic') + +export function isLonghandPropType(t: unknown): t is PropTypeConfig { + return ( + typeof t === 'object' && + !!t && + (t as $IntentionalAny)[propTypeSymbol] === 'TheatrePropType' + ) +} + +export function toLonghandProp(p: unknown): PropTypeConfig { + if (typeof p === 'number') { + return t.number(p) + } else if (typeof p === 'boolean') { + return t.boolean(p) + } else if (typeof p === 'string') { + return t.string(p) + } else if (typeof p === 'object' && !!p) { + if (isLonghandPropType(p)) return p + if (isPlainObject(p)) { + return t.compound(p as $IntentionalAny) + } else { + throw new InvalidArgumentError( + `This value is not a valid prop type: ${userReadableTypeOfValue(p)}`, + ) + } + } else { + throw new InvalidArgumentError( + `This value is not a valid prop type: ${userReadableTypeOfValue(p)}`, + ) + } +} diff --git a/theatre/shared/src/testUtils.ts b/theatre/shared/src/testUtils.ts index e6ce59e..f32a8cf 100644 --- a/theatre/shared/src/testUtils.ts +++ b/theatre/shared/src/testUtils.ts @@ -34,11 +34,11 @@ export async function setupTestSheet(sheetState: SheetState_Historic) { const objPublicAPI = sheetPublicAPI.object( 'obj', t.compound({ - position: t.compound({ + position: { x: t.number(0), y: t.number(1), z: t.number(2), - }), + }, }), )