UNPKG

typerscript

Version:

hyperscript and hyperscript helpers written in TypeScript, leveraging TypeStyle

231 lines (203 loc) 7.22 kB
import * as React from 'react' import { style as typeStyle } from 'typestyle' const __ = { IS_NATIVE__: false } // This avoids allocating new empty objects all the time const EmptyObject = Object.freeze({}) const objIsLocked = (obj: any) => !Object.isExtensible(obj) || Object.isFrozen(obj) || Object.isSealed(obj) // declare var process: any; // declare var __: {IS_NATIVE__: boolean} // try { if (!__) {} } // catch(e) { // let __ = {IS_NATIVE__: false} // } interface Selector { id?: string; className?: string; } export class Style<T extends object = any> { selector: string; rules: T; constructor({ selector, rules }: {selector: string, rules: T}) { const firstChar = selector[0] this.selector = firstChar === '.' || firstChar === '#' ? selector : '.' + selector this.rules = rules } } export type IClass = { [key: string]: object } export type IClasses<T extends IClass> = { [K in keyof T]: Style<T[K]> } export function createClasses<T extends IClass>(classes: T) { const styles: IClasses<T> = {} as IClasses<T> Object.entries(classes).forEach(([key, val]) => { const selector = __.IS_NATIVE__ ? key : typeStyle({ $debugName: key, ...val }) styles[key] = new Style({ rules: val, selector, }) }) return styles } //const isValidString = param => typeof param === 'string' && param.length > 0 const startsWith = (str: string, start: string) => str[0] === start //const isValidSelector = param => isValidString(param) && (startsWith(param, '.') || startsWith(param, '#')) const isStyleObject = (param: any) => param instanceof Style // isSelectorOrStyle = param => isValidString(param) || isStyleObject(param) const isSelector = (param: any) => typeof param === 'string' const isChildren = Array.isArray const classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/ function parseSelector(selector: string) { const parts = selector.split(classIdSplit) let classNames = [] let id for (let part of parts) { if (part.startsWith('#')) id = part.substring(1) else if (part.startsWith('.')) classNames.push(part.substring(1)) } const className = classNames.join(' ') if (id && className) { return { id, className } } if (id) { return { id } } if (className) { return { className } } return undefined } function createElement(nameOrType: any, properties: any={isRendered: true}, children: any[]=[]) { const { isRendered, ...props } = properties if (!isRendered && isRendered !== undefined) return null const args = [nameOrType, props] if (!Array.isArray(children)) { args.push(children) } else { args.push(...children) } return React.createElement.apply(React, args) } type filterObjectPredicate = (value: any, key: string) => boolean function filterObject(obj: {[key: string]: any}, predicate: filterObjectPredicate ) { return Object.assign({}, ...Object.keys(obj) .filter( key => predicate(obj[key], key) ) .map((key: string) => ({ [key]: obj[key] })) ) } const flexKeys = ['flex', 'flexGrow', 'flexShrink', 'flexBasis'] const getFlexObj = (rules: {[key: string]: any}) => filterObject( rules, (value: any, key: string) => flexKeys.includes(key), ) export function _hh<T, Props>(first: string|Style, second: Props, third?: any[]): any export function _hh<T, Props>(first: string|Style, second?: Props|any[]): any export function _hh<T, Props>(first: Props, second?: any[]): any export function _hh<T, Props>(first?: string|Style|Props|any[]): any export function _hh<T, Props>(..._args: any[]): any{} export type ComponentType<P> = (React.ReactElement<P>) export type ReactHTMLElement = any // export type SVGHTMLElement = any export type ReactElement = any import { Attributes, ClassicComponent, Component, ComponentClass, ComponentState, ClassicComponentClass, ClassType, DOMAttributes, HTMLAttributes, InputHTMLAttributes, ReactHTML, // ReactSVG, SFC, StatelessComponent, // SVGAttributes, ClassAttributes, } from 'react' export type options<P> = ( | ComponentClass<P> | string ) export type NameOrType<P,T> = ( 'input' // | ReactSVG | ReactHTML | string | Function | StatelessComponent<P> | SFC<P> | ClassType<P, ClassicComponent<P, ComponentState>, ClassicComponentClass<P>> | options<P> ) export type BaseProps<T> = ( // T extends SVGElement ? SVGAttributes<T> : T extends ReactHTMLElement ? HTMLAttributes<T> : T extends Element ? DOMAttributes<T> : {} ) export type AttrProps<P> = Attributes & P export type ComponentWState<P> = (Component<P, ComponentState>) export function hh<P, T extends NameOrType<P,T>>(nameOrType: T): typeof _hh export function hh<P, T extends ComponentWState<P>, C extends ComponentClass<P>>(nameOrType: ClassType<P, T, C>): typeof _hh { type Props = ( T extends ReactHTMLElement ? HTMLAttributes<T> : T extends Element ? DOMAttributes<T> : T extends 'input' ? (InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement> | null) : T extends StatelessComponent<P> ? Attributes & P : T extends Function ? Attributes & P : // T extends SFC<P> ? Attributes & P : T extends ComponentClass<P> ? Attributes & P : T extends ComponentWState<P> ? ClassAttributes<ClassicComponent<P, ComponentState>> & P | null : ClassAttributes<T> & P | null ) return ((...args: any[]) => h<T, Props>(nameOrType, ...args)) as typeof _hh } function h<T, Props>(nameOrType: any, first: string|Style, second: Props, third?: any[]): any function h<T, Props>(nameOrType: any, first: string|Style, second?: Props|any[]): any function h<T, Props>(nameOrType: any, first: Props, second?: any[]): any function h<T, Props>(nameOrType: any, first?: string|Style|Props|any[]): any function h<T, Props>(nameOrType: any, ..._args: any[]) { // const name = nameOrType.displayName || nameOrType.name || nameOrType let selector: Selector | undefined let rules: { [index: string]: any } let props: { [index: string]: any } let children: any[] let isLocked = false const args = _args.filter((arg: any) => arg !== undefined) let arg = 0 if (isSelector(args[arg])) { selector = parseSelector(args[arg]) arg++ } else if (isStyleObject(args[arg])) { selector = parseSelector(args[arg].selector) // if (__.IS_NATIVE__) { rules = args[arg].rules // } arg++ } if (typeof args[arg] === 'object' && !isChildren(args[arg])) { props = args[arg] isLocked = true arg++ } else props = {} if (isChildren(args[arg])) { children = args[arg] } if (__.IS_NATIVE__) { if (isLocked) { props = { ...props } } props.style = props.style ? { ...rules, ...props.style } : rules } else if (rules) { if (isLocked) { props = { ...props } } // react-native-web needs 'flex' props as an actual style prop in order // to work correctly const flexObj = getFlexObj(rules) if (Object.keys(flexObj).length > 0) { props.style = props.style ? { ...flexObj, ...props.style } : flexObj } } if (selector) { props = {...selector, ...props} } // In some cases, e.g. react-native's TouchableWithoutFeedback we will get // 'Error: React.Children.only expected to receive a single React element child.' // when passing an array, so if there is only one child, just put that return createElement(nameOrType, props, children) } export { h }