From f7d0c7818b1fae5d753bb014dc6af1e942166c5c Mon Sep 17 00:00:00 2001 From: Vladislav Shabanov Date: Sat, 13 Feb 2021 22:38:52 +0700 Subject: [PATCH 1/3] Proof of concept --- src/styleSheet.ts | 15 ----- .../EnhancedCSSProperties/CSSProperties.ts | 3 + .../EnhancedCSSObject.ts | 31 ++++++++++ .../EnhancedCSSProperties.ts | 16 +++++ .../EnhancedCSSProperties/enhancers/area.ts | 20 ++++++ .../EnhancedCSSProperties/enhancers/border.ts | 15 +++++ .../enhancers/boxShadow.ts | 61 +++++++++++++++++++ .../enhancers/enhancers.ts | 12 ++++ .../EnhancedCSSProperties/enhancers/index.ts | 1 + .../EnhancedCSSProperties/index.ts | 1 + src/styleSheet/EnhancedCSSProperties/index.ts | 1 + src/styleSheet/index.ts | 1 + src/styleSheet/styleSheet.ts | 22 +++++++ 13 files changed, 184 insertions(+), 15 deletions(-) delete mode 100644 src/styleSheet.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/CSSProperties.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSObject.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/EnhancedCSSProperties.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/border.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/index.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/index.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/index.ts create mode 100644 src/styleSheet/index.ts create mode 100644 src/styleSheet/styleSheet.ts diff --git a/src/styleSheet.ts b/src/styleSheet.ts deleted file mode 100644 index b012ed3..0000000 --- a/src/styleSheet.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SerializedStyles, css } from '@emotion/react'; -import type { CSSInterpolation } from '@emotion/serialize'; - -type Styles = Record; - -export function styleSheet(styles: S): { [K in keyof S]: SerializedStyles } { - const stylesNames = Object.getOwnPropertyNames(styles) as (keyof S)[]; - const serializedStyles = {} as { [K in keyof S]: SerializedStyles }; - - for (const styleName of stylesNames) { - serializedStyles[styleName] = css(styles[styleName]); - } - - return serializedStyles; -} diff --git a/src/styleSheet/EnhancedCSSProperties/CSSProperties.ts b/src/styleSheet/EnhancedCSSProperties/CSSProperties.ts new file mode 100644 index 0000000..7741249 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/CSSProperties.ts @@ -0,0 +1,3 @@ +import * as CSS from 'csstype'; + +export type CSSProperties = CSS.PropertiesFallback; diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSObject.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSObject.ts new file mode 100644 index 0000000..04ac0ae --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSObject.ts @@ -0,0 +1,31 @@ +import * as CSS from 'csstype'; +import { CSSProperties } from './CSSProperties'; +import { toCSSProperty, EnhancedCSSProperties } from './EnhancedCSSProperties'; + +type CSSPseudos

= { [K in CSS.Pseudos]?: CSSObject

}; + +interface CSSOthersObject

{ + [propertiesName: string]: CSSObject

; +} + +export type CSSObject

= (P & CSSPseudos

) | CSSOthersObject

; + +export type EnhancedCSSObject = CSSObject; + +export function toCSSObject( + cssObject: EnhancedCSSObject, +): CSSObject { + const result: any = {}; + + Object.entries(cssObject).forEach(([key, value]) => { + let cleanValue = toCSSProperty(key, value); + + if (typeof cleanValue === 'object') { + cleanValue = toCSSObject(cleanValue); + } + + result[key] = cleanValue; + }); + + return result; +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/EnhancedCSSProperties.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/EnhancedCSSProperties.ts new file mode 100644 index 0000000..a9124ea --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/EnhancedCSSProperties.ts @@ -0,0 +1,16 @@ +import { CSSProperties } from '../CSSProperties'; +import { enhancers, Enhancers } from './enhancers'; + +export type EnhancedCSSProperties = { + [K in keyof CSSProperties]: K extends keyof Enhancers + ? Parameters[0] + : CSSProperties[K]; +}; + +export function toCSSProperty(property: string, value: any) { + if (property in enhancers) { + return enhancers[property as keyof Enhancers](value); + } + + return value; +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts new file mode 100644 index 0000000..326b426 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts @@ -0,0 +1,20 @@ +type AreaOptions = + | { vertical: number; horizontal: number } + | { top: number; horizontal: number; bottom: number } + | { top: number; right: number; bottom: number; left: number }; + +export function area(value: number | AreaOptions): string { + if (typeof value === 'number') { + return `${value}px`; + } + + if ('left' in value) { + return `${value.top}px ${value.right}px ${value.bottom}px ${value.left}px`; + } + + if ('bottom' in value) { + return `${value.top}px ${value.horizontal}px ${value.bottom}px`; + } + + return `${value.vertical}px ${value.horizontal}px`; +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/border.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/border.ts new file mode 100644 index 0000000..6744346 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/border.ts @@ -0,0 +1,15 @@ +type BorderOptions = { + width: number; + type?: string; + color: string; +}; + +export function border(value: 'none' | BorderOptions): string { + if (value === 'none') { + return value; + } + + const { width, type = 'solid', color } = value; + + return `${width}px ${type} ${color}`; +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts new file mode 100644 index 0000000..1a052ee --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts @@ -0,0 +1,61 @@ +interface BoxShadow { + inset?: boolean; + x: number; + y: number; + color: string; + blur?: never; + spread?: never; +} +interface BoxShadowWithBlur { + inset?: boolean; + x: number; + y: number; + color: string; + blur: number; + spread?: never; +} +interface BoxShadowWithSpread { + inset?: boolean; + x: number; + y: number; + color: string; + blur: number; + spread: number; +} + +type BoxShadowOptions = BoxShadow | BoxShadowWithBlur | BoxShadowWithSpread; + +function isBoxShadowWithBlur(shadowOption: BoxShadowOptions): shadowOption is BoxShadowWithSpread { + return 'blur' in shadowOption; +} + +function isBoxShadowWithSpread( + shadowOption: BoxShadowOptions, +): shadowOption is BoxShadowWithSpread { + return 'blur' in shadowOption && 'spread' in shadowOption; +} + +function boxShadowOptionsToString(options: BoxShadowOptions): string { + const { inset, x, y, color } = options; + + let blur: number | undefined; + let spread: number | undefined; + + if (isBoxShadowWithSpread(options)) { + ({ blur, spread } = options); + } else if (isBoxShadowWithBlur(options)) { + ({ blur } = options); + } + + return [inset && 'inset', `${x}px`, `${y}px`, blur && `${blur}px`, spread && `${spread}px`, color] + .filter(Boolean) + .join(' '); +} + +export function boxShadow(value: BoxShadowOptions | BoxShadowOptions[]): string { + if (Array.isArray(value)) { + return value.map(boxShadowOptionsToString).join(', '); + } + + return boxShadowOptionsToString(value); +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts new file mode 100644 index 0000000..4f149dc --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts @@ -0,0 +1,12 @@ +import { area } from './area'; +import { boxShadow } from './boxShadow'; +import { border } from './border'; + +export const enhancers = { + border, + boxShadow, + margin: area, + padding: area, +}; + +export type Enhancers = typeof enhancers; diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/index.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/index.ts new file mode 100644 index 0000000..ae1107b --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/index.ts @@ -0,0 +1 @@ +export * from './enhancers'; diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/index.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/index.ts new file mode 100644 index 0000000..330b325 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/index.ts @@ -0,0 +1 @@ +export * from './EnhancedCSSProperties'; diff --git a/src/styleSheet/EnhancedCSSProperties/index.ts b/src/styleSheet/EnhancedCSSProperties/index.ts new file mode 100644 index 0000000..1582e36 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/index.ts @@ -0,0 +1 @@ +export * from './EnhancedCSSObject'; diff --git a/src/styleSheet/index.ts b/src/styleSheet/index.ts new file mode 100644 index 0000000..e861208 --- /dev/null +++ b/src/styleSheet/index.ts @@ -0,0 +1 @@ +export * from './styleSheet'; diff --git a/src/styleSheet/styleSheet.ts b/src/styleSheet/styleSheet.ts new file mode 100644 index 0000000..4a5b6a1 --- /dev/null +++ b/src/styleSheet/styleSheet.ts @@ -0,0 +1,22 @@ +import { SerializedStyles, css } from '@emotion/react'; +import { + CSSObject, + toCSSObject, + EnhancedCSSObject, +} from './EnhancedCSSProperties'; + +type Styles = Record>; + +type Result = { [K in keyof S]: SerializedStyles }; + +export function styleSheet(styles: S): Result { + const result = {} as Result; + + Object.entries(styles).forEach(([key, value]) => { + const cssObject: any = toCSSObject(value); + + result[key as keyof Result] = css(cssObject); + }); + + return result; +} From 1950394cc95b56689e22a671f0fcbd1b2ae7bb8a Mon Sep 17 00:00:00 2001 From: Maksim Milyutin Date: Mon, 15 Feb 2021 19:12:20 +0300 Subject: [PATCH 2/3] Background and transition --- .../enhancers/background.ts | 93 +++++++++++++++++++ .../enhancers/boxShadow.ts | 7 +- .../enhancers/enhancers.ts | 6 +- .../enhancers/transition.ts | 26 ++++++ 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/background.ts create mode 100644 src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/transition.ts diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/background.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/background.ts new file mode 100644 index 0000000..b9bbe90 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/background.ts @@ -0,0 +1,93 @@ +import { GlobalValues } from '../../../../types'; + +type BackgroundRepeatValues = + | 'repeat-x' + | 'repeat-y' + | 'repeat' + | 'space' + | 'round' + | 'no-repeat'; + +type BackgroundAttachment = 'scroll' | 'fixed' | 'local' | GlobalValues; +type BackgroundClip = + | 'border-box' + | 'padding-box' + | 'content-box' + | 'text' + | GlobalValues; +type BackgroundColor = string | GlobalValues; +type BackgroundImage = string; +type BackgroundOrigin = + | 'border-box' + | 'padding-box' + | 'content-box' + | GlobalValues; +type BackgroundPosition = string | GlobalValues; +type BackgroundRepeat = + | BackgroundRepeatValues + | [BackgroundRepeatValues, BackgroundRepeatValues] + | GlobalValues; +type BackgroundSize = string | GlobalValues; + +interface BackgroundWithoutSize { + attachment?: BackgroundAttachment; + clip?: BackgroundClip; + color?: BackgroundColor; + image?: BackgroundImage; + origin?: BackgroundOrigin; + position?: BackgroundSize; + repeat?: BackgroundRepeat; +} + +type BackgroundWithSize = Omit & { + position: BackgroundPosition; + size?: BackgroundSize; +}; + +type BackgroundOptions = BackgroundWithoutSize | BackgroundWithSize; + +function isBackgroundWithSize( + background: BackgroundOptions, +): background is BackgroundWithSize { + return 'size' in background; +} + +function backgroundOptionsToString(options: BackgroundOptions): string { + let position; + if (isBackgroundWithSize(options) && options.size) { + position = `${options.position}/${options.size}`; + } else { + position = options.position; + } + + return [ + options.image, + position, + options.repeat, + options.origin, + options.clip, + options.attachment, + options.color, + ] + .filter(Boolean) + .join(' '); +} + +/** + * Note: Color can only be defined on the last background, + * https://developer.mozilla.org/en-US/docs/Web/CSS/background + * TODO: Implement this check by types in Typescript 4.2 (by [...options, lastOptions]) + */ +export function background( + value: 'none' | BackgroundOptions | BackgroundOptions[], +): string { + if (value === 'none') { + return value; + } + + if (Array.isArray(value)) { + return value.map(backgroundOptionsToString).join(', '); + } + + return backgroundOptionsToString(value); +} diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts index 1a052ee..89154ee 100644 --- a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/boxShadow.ts @@ -52,7 +52,12 @@ function boxShadowOptionsToString(options: BoxShadowOptions): string { .join(' '); } -export function boxShadow(value: BoxShadowOptions | BoxShadowOptions[]): string { +export function boxShadow(value: 'none' | BoxShadowOptions | BoxShadowOptions[]): string { + if (value === 'none') { + return value; + } + + if (Array.isArray(value)) { return value.map(boxShadowOptionsToString).join(', '); } diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts index 4f149dc..6194e64 100644 --- a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/enhancers.ts @@ -1,12 +1,16 @@ import { area } from './area'; -import { boxShadow } from './boxShadow'; +import { background } from './background'; import { border } from './border'; +import { boxShadow } from './boxShadow'; +import { transition } from './transition'; export const enhancers = { + background, border, boxShadow, margin: area, padding: area, + transition, }; export type Enhancers = typeof enhancers; diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/transition.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/transition.ts new file mode 100644 index 0000000..b3e09c0 --- /dev/null +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/transition.ts @@ -0,0 +1,26 @@ +interface TransitionOptions { + property: string; + duration: string; + timingFunction?: string; + delay?: string; +} + +function transitionOptionsToString(options: TransitionOptions) { + const { property, duration, timingFunction, delay } = options; + + return [property, duration, timingFunction, delay].filter(Boolean).join(' '); +} + +export function transition( + value: 'none' | TransitionOptions | TransitionOptions[], +): string { + if (value === 'none') { + return value; + } + + if (Array.isArray(value)) { + return value.map(options => transitionOptionsToString(options)).join(', '); + } + + return transitionOptionsToString(value); +} From 5fbb8d9793f58701b25af9c7413f1ec3dd91ea04 Mon Sep 17 00:00:00 2001 From: Maksim Milyutin Date: Mon, 15 Feb 2021 20:30:03 +0300 Subject: [PATCH 3/3] Area improvments --- .../EnhancedCSSProperties/enhancers/area.ts | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts index 326b426..17df858 100644 --- a/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts +++ b/src/styleSheet/EnhancedCSSProperties/EnhancedCSSProperties/enhancers/area.ts @@ -1,20 +1,42 @@ +type AreaValue = number | string | 'auto'; + type AreaOptions = - | { vertical: number; horizontal: number } - | { top: number; horizontal: number; bottom: number } - | { top: number; right: number; bottom: number; left: number }; + | { vertical: AreaValue; horizontal: AreaValue } + | { top: AreaValue; horizontal: AreaValue; bottom: AreaValue } + | { top: AreaValue; right: AreaValue; bottom: AreaValue; left: AreaValue } + | [top: AreaValue, right: AreaValue, bottom: AreaValue, left: AreaValue] + | [top: AreaValue, horizontal: AreaValue, bottom: AreaValue] + | [vertical: AreaValue, horizontal: AreaValue] + | AreaValue; + +function getAreaFromValue(value: AreaOptions): AreaValue[] { + if (Array.isArray(value)) { + return value; + } -export function area(value: number | AreaOptions): string { - if (typeof value === 'number') { - return `${value}px`; + if (typeof value === 'number' || typeof value === 'string') { + return [value]; } if ('left' in value) { - return `${value.top}px ${value.right}px ${value.bottom}px ${value.left}px`; + return [value.top, value.right, value.bottom, value.left]; } if ('bottom' in value) { - return `${value.top}px ${value.horizontal}px ${value.bottom}px`; + return [value.top, value.horizontal, value.bottom]; } - return `${value.vertical}px ${value.horizontal}px`; + return [value.vertical, value.horizontal]; +} + +export function area(value: AreaOptions): string { + return getAreaFromValue(value) + .map((area) => { + if (typeof area === 'number') { + return `${area}px`; + } + + return area; + }) + .join(' '); }