More progress on shorthand props

This commit is contained in:
Aria Minaei 2021-09-06 11:05:35 +02:00
parent 2f44f53021
commit aefb769855
3 changed files with 97 additions and 74 deletions

View file

@ -1,22 +1,11 @@
/**
* Usage:
* ```ts
* import {types as t} from '@theatre/core'
*
* const props = t.compound({
* x: t.number(0),
* y: t.number(0)
* })
*
*
* const obj = sheet.obj("An object", props)
* ```
* @module types
*/
import {InvalidArgumentError} from '@theatre/shared/utils/errors'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import type {
import {isLonghandPropType, propTypeSymbol, toLonghandProp} from './internals' IShorthandCompoundProps,
IValidCompoundProps,
ShorthandCompoundPropsToLonghandCompoundProps,
} from './internals'
import {sanitizeCompoundProps} from './internals'
import {propTypeSymbol} from './internals'
/** /**
* Creates a compound prop type (basically a JS object). * Creates a compound prop type (basically a JS object).
@ -38,57 +27,16 @@ import {isLonghandPropType, propTypeSymbol, toLonghandProp} from './internals'
* @param extras * @param extras
* @returns * @returns
* *
* @category Prop type definitions
*/ */
export const compound = <Props extends IValidCompoundProps>( export const compound = <Props extends IShorthandCompoundProps>(
props: Props, props: Props,
extras?: PropTypeConfigExtras, extras?: PropTypeConfigExtras,
): PropTypeConfig_Compound<Props> => { ): PropTypeConfig_Compound<
let sanitizedProps: Props ShorthandCompoundPropsToLonghandCompoundProps<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<keyof Props>) {
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 { return {
type: 'compound', type: 'compound',
props: sanitizedProps, props: sanitizeCompoundProps(props),
valueType: null as $IntentionalAny, valueType: null as $IntentionalAny,
[propTypeSymbol]: 'TheatrePropType', [propTypeSymbol]: 'TheatrePropType',
label: extras?.label, label: extras?.label,
@ -101,7 +49,6 @@ export const compound = <Props extends IValidCompoundProps>(
* @param opts * @param opts
* @returns * @returns
* *
* @category Prop type definitions
*/ */
export const number = ( export const number = (
defaultValue: number, defaultValue: number,
@ -130,7 +77,6 @@ export const number = (
* @param extras * @param extras
* @returns * @returns
* *
* @category Prop type definitions
*/ */
export const boolean = ( export const boolean = (
defaultValue: boolean, defaultValue: boolean,
@ -151,7 +97,6 @@ export const boolean = (
* @param extras * @param extras
* @returns * @returns
* *
* @category Prop type definitions
*/ */
export const string = ( export const string = (
defaultValue: string, defaultValue: string,
@ -173,7 +118,6 @@ export const string = (
* @param extras * @param extras
* @returns * @returns
* *
* @category Prop type definitions
*/ */
export function stringLiteral<Opts extends {[key in string]: string}>( export function stringLiteral<Opts extends {[key in string]: string}>(
defaultValue: Extract<keyof Opts, string>, defaultValue: Extract<keyof Opts, string>,
@ -262,10 +206,6 @@ export interface PropTypeConfig_StringLiteral<T extends string>
as: 'menu' | 'switch' as: 'menu' | 'switch'
} }
type IValidCompoundProps = {
[K in string]: PropTypeConfig
}
/** /**
* @todo Determine if 'compound' is a clear term for what this is. * @todo Determine if 'compound' is a clear term for what this is.
* I didn't want to use 'object' as it could get confused with * I didn't want to use 'object' as it could get confused with

View file

@ -2,11 +2,50 @@ import {InvalidArgumentError} from '@theatre/shared/utils/errors'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
import {isPlainObject} from 'lodash-es' import {isPlainObject} from 'lodash-es'
import type {PropTypeConfig} from './index' import type {
PropTypeConfig,
PropTypeConfig_Boolean,
PropTypeConfig_Compound,
PropTypeConfig_Number,
PropTypeConfig_String,
} from './index'
import * as t from './index' import * as t from './index'
export const propTypeSymbol = Symbol('TheatrePropType_Basic') export const propTypeSymbol = Symbol('TheatrePropType_Basic')
export type IValidCompoundProps = {
[K in string]: PropTypeConfig
}
type IShorthandProp =
| string
| number
| boolean
| PropTypeConfig
| IShorthandCompoundProps
export type IShorthandCompoundProps = {
[K in string]: IShorthandProp
}
type ShorthandPropToLonghandProp<P extends IShorthandProp> = P extends string
? PropTypeConfig_String
: P extends number
? PropTypeConfig_Number
: P extends boolean
? PropTypeConfig_Boolean
: P extends PropTypeConfig
? P
: P extends IShorthandCompoundProps
? PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<P>>
: never
export type ShorthandCompoundPropsToLonghandCompoundProps<
P extends IShorthandCompoundProps,
> = {
[K in keyof P]: ShorthandPropToLonghandProp<P[K]>
}
export function isLonghandPropType(t: unknown): t is PropTypeConfig { export function isLonghandPropType(t: unknown): t is PropTypeConfig {
return ( return (
typeof t === 'object' && typeof t === 'object' &&
@ -37,3 +76,47 @@ export function toLonghandProp(p: unknown): PropTypeConfig {
) )
} }
} }
export function sanitizeCompoundProps(
props: IShorthandCompoundProps,
): IValidCompoundProps {
const sanitizedProps: IValidCompoundProps = {}
if (process.env.NODE_ENV !== 'production') {
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)) {
if (process.env.NODE_ENV !== 'production') {
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] = val as $IntentionalAny
} else {
sanitizedProps[key] = toLonghandProp(val) as $IntentionalAny
}
}
return sanitizedProps
}

View file

@ -35,7 +35,7 @@ export async function setupTestSheet(sheetState: SheetState_Historic) {
'obj', 'obj',
t.compound({ t.compound({
position: { position: {
x: t.number(0), x: 0,
y: t.number(1), y: t.number(1),
z: t.number(2), z: t.number(2),
}, },