Skip to content

Instantly share code, notes, and snippets.

@lsdsjy
Created February 6, 2023 08:43
Show Gist options
  • Save lsdsjy/26e9243838cc894b6d69cd8edd4e6d55 to your computer and use it in GitHub Desktop.
Save lsdsjy/26e9243838cc894b6d69cd8edd4e6d55 to your computer and use it in GitHub Desktop.
import type { CSSProperties } from '@vue/runtime-dom';
import { rpx2vws } from './utils/rpx2vw';
const STANDALONE = [
'items-end',
'overflow-hidden',
'flex',
'flex-col',
'number',
'bold',
'inline-block',
'items-center',
'content-center',
'justify-between',
'justify-center',
'ellipsis',
] as const;
const PREFIX = [
'color',
'opacity',
'mt',
'mb',
'ml',
'mr',
'mx',
'text',
'leading',
'w',
'h',
'rounded',
'bg',
'max-w',
] as const;
type AllStandalone = typeof STANDALONE[number];
type AllPrefix = typeof PREFIX[number];
type AllPrefixed = `${AllPrefix}${string}`;
const MAP: Record<AllStandalone, CSSProperties> & Record<AllPrefix, keyof CSSProperties | (keyof CSSProperties)[]> = {
ellipsis: {
'word-break': 'break-all',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
},
'items-end': { alignItems: 'flex-end' },
'overflow-hidden': { overflow: 'hidden' },
'justify-center': { justifyContent: 'center' },
'flex-col': { flexDirection: 'column' },
'justify-between': { justifyContent: 'space-between' },
'items-center': { alignItems: 'center' },
'content-center': { justifyContent: 'center' },
'inline-block': { display: 'inline-block' },
flex: { display: 'flex' },
bold: { fontWeight: 'bold' },
number: { fontFamily: 'number' },
ml: 'marginLeft',
mr: 'marginRight',
mt: 'marginTop',
mb: 'marginBottom',
mx: ['marginLeft', 'marginRight'],
text: 'fontSize',
leading: 'lineHeight',
w: 'width',
h: 'height',
'max-w': 'max-width',
rounded: 'borderRadius',
bg: 'background',
color: 'color',
opacity: 'opacity',
};
function processParam(prefix: AllPrefix, param: string) {
if (prefix === 'opacity') {
return String(Number(param) / 100);
}
return param;
}
type Check<T extends [...unknown[]], Standalone = AllStandalone, Prefix extends string = AllPrefix> = T extends [
infer A,
...infer Rest,
]
? A extends Standalone
? [A, ...Check<Rest, Exclude<Standalone, A>, Prefix>]
: A extends `${Prefix}-${infer S}`
? `` extends S
? never
: A extends `${infer P}-${S}`
? [A, ...Check<Rest, Standalone, Exclude<Prefix, P>>]
: never
: never
: T;
function isPrefixed(x: string): x is AllPrefixed {
return PREFIX.some((p) => x.startsWith(p + '-'));
}
function isStandalone(x: string): x is AllStandalone {
return STANDALONE.includes(x as any);
}
type SplitBySpace<S extends string> = S extends `${infer H} ${infer T}` ? [H, ...SplitBySpace<T>] : [S];
const cache = new Map<string, CSSProperties>();
export function style<S extends string, T extends unknown[] = SplitBySpace<S>>(
s: S & (Check<T> extends never ? never : S),
): CSSProperties {
if (cache.has(s)) {
return cache.get(s)!;
}
const items = s.split(' ') as (AllStandalone | AllPrefixed)[];
const result: Record<string, string> = {};
for (const item of items) {
if (isPrefixed(item)) {
const seg = item.lastIndexOf('-');
const prefix = item.slice(0, seg) as AllPrefix;
const rawValue = item.slice(seg + 1);
let value = processParam(prefix, rawValue);
const number = Number(value);
if (Number.isInteger(number)) {
value = rpx2vws(number);
}
let properties = MAP[prefix as AllPrefix];
if (!Array.isArray(properties)) {
properties = [properties];
}
for (const property of properties) {
result[property] = value;
}
} else if (isStandalone(item)) {
Object.assign(result, MAP[item]);
} else {
throw new Error('invalid style params');
}
}
cache.set(s, result);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment