typestyle
Version:
97 lines (85 loc) • 3.48 kB
text/typescript
import {CSSProperties, MediaQuery, NestedCSSProperties} from '../types';
/** Raf for node + browser */
export const raf: (cb: () => void) => void =
typeof requestAnimationFrame === 'undefined'
/**
* Make sure setTimeout is always invoked with
* `this` set to `window` or `global` automatically
**/
? (cb) => setTimeout(cb)
/**
* Make sure window.requestAnimationFrame is always invoked with `this` window
* We might have raf without window in case of `raf/polyfill` (recommended by React)
**/
: typeof window === 'undefined'
? requestAnimationFrame
: requestAnimationFrame.bind(window);
/**
* Utility to join classes conditionally
*/
export function classes(...classes: (string | false | undefined | null | { [className: string]: any })[]): string {
return classes
.map(c => c && typeof c === 'object' ? Object.keys(c).map(key => !!c[key] && key) : [c])
.reduce((flattened, c) => flattened.concat(c), [] as string[])
.filter(c => !!c)
.join(' ');
}
/**
* Merges various styles into a single style object.
* Note: if two objects have the same property the last one wins
*/
export function extend(...objects: (NestedCSSProperties | undefined | null | false)[]): NestedCSSProperties {
/** The final result we will return */
const result: CSSProperties & Record<string, any> = {};
for (const object of objects) {
if (object == null || object === false) {
continue;
}
for (const key in object) {
/** Falsy values except a explicit 0 is ignored */
const val: any = (object as any)[key];
if (!val && val !== 0) {
continue;
}
/** if nested media or pseudo selector */
if (key === '$nest' && val) {
result[key] = result['$nest'] ? extend(result['$nest'], val) : val;
}
/** if freestyle sub key that needs merging. We come here due to our recursive calls */
else if ((key.indexOf('&') !== -1 || key.indexOf('@media') === 0)) {
result[key] = result[key] ? extend(result[key], val) : val;
}
else {
result[key] = val;
}
}
}
return result;
}
/**
* Utility to help customize styles with media queries. e.g.
* ```
* style(
* media({maxWidth:500}, {color:'red'})
* )
* ```
*/
export const media = (mediaQuery: MediaQuery, ...objects: (NestedCSSProperties | undefined | null | false)[]): NestedCSSProperties => {
const mediaQuerySections: string[] = [];
if (mediaQuery.type) mediaQuerySections.push(mediaQuery.type);
if (mediaQuery.orientation) mediaQuerySections.push(`(orientation: ${mediaQuery.orientation})`);
if (mediaQuery.minWidth) mediaQuerySections.push(`(min-width: ${mediaLength(mediaQuery.minWidth)})`);
if (mediaQuery.maxWidth) mediaQuerySections.push(`(max-width: ${mediaLength(mediaQuery.maxWidth)})`);
if (mediaQuery.minHeight) mediaQuerySections.push(`(min-height: ${mediaLength(mediaQuery.minHeight)})`);
if (mediaQuery.maxHeight) mediaQuerySections.push(`(max-height: ${mediaLength(mediaQuery.maxHeight)})`);
if (mediaQuery.prefersColorScheme) mediaQuerySections.push(`(prefers-color-scheme: ${mediaQuery.prefersColorScheme})`);
const stringMediaQuery = `@media ${mediaQuerySections.join(' and ')}`;
const object: NestedCSSProperties = {
$nest: {
[stringMediaQuery]: extend(...objects)
}
};
return object;
}
const mediaLength = (value: number | string) =>
typeof value === 'string' ? value : `${value}px`;