From 3ec883afdca488b3af1af2ca1b566943959ad6ab Mon Sep 17 00:00:00 2001 From: Tom Picton Date: Wed, 16 Sep 2020 14:05:34 -0700 Subject: [PATCH 1/3] Set up generation --- .eslintrc.js | 1 + packages/tacky-css/build.ts | 98 ++ .../csstype/collections/data-types.ts | 117 +++ .../csstype/collections/properties.ts | 225 +++++ .../tacky-css/csstype/collections/syntaxes.ts | 136 +++ packages/tacky-css/csstype/data/patches.ts | 23 + packages/tacky-css/csstype/data/svg.ts | 228 +++++ packages/tacky-css/csstype/data/urls.json | 471 +++++++++ packages/tacky-css/csstype/declarator.ts | 467 +++++++++ packages/tacky-css/csstype/syntax/parser.ts | 319 ++++++ packages/tacky-css/csstype/syntax/typer.ts | 349 +++++++ packages/tacky-css/csstype/typescript.ts | 212 ++++ packages/tacky-css/csstype/utils/casing.ts | 27 + packages/tacky-css/csstype/utils/comment.ts | 255 +++++ packages/tacky-css/csstype/utils/compat.ts | 256 +++++ packages/tacky-css/csstype/utils/logger.ts | 11 + packages/tacky-css/csstype/utils/output.ts | 89 ++ packages/tacky-css/csstype/utils/urls.ts | 74 ++ packages/tacky-css/package.json | 16 + packages/tacky-css/src/animation.ts | 189 ---- packages/tacky-css/src/color.ts | 15 - packages/tacky-css/src/function.ts | 9 - packages/tacky-css/src/image.ts | 139 --- packages/tacky-css/src/index.ts | 42 +- packages/tacky-css/src/macro.ts | 170 ---- packages/tacky-css/src/media/index.ts | 51 - packages/tacky-css/src/media/mediaFeatures.ts | 70 -- packages/tacky-css/src/media/mediaTypes.ts | 43 - packages/tacky-css/src/media/operators.ts | 4 - packages/tacky-css/src/media/types.ts | 15 - packages/tacky-css/src/property/animations.ts | 24 - .../src/property/backgroundsAndBorders.ts | 407 -------- .../src/property/basicUserInterface.ts | 29 - .../tacky-css/src/property/boxAlignment.ts | 25 - packages/tacky-css/src/property/boxModel.ts | 95 -- packages/tacky-css/src/property/color.ts | 6 - packages/tacky-css/src/property/columns.ts | 7 - .../src/property/compositingAndBlending.ts | 19 - .../tacky-css/src/property/containment.ts | 21 - packages/tacky-css/src/property/display.ts | 4 - .../src/property/flexibleBoxLayout.ts | 45 - packages/tacky-css/src/property/fonts.ts | 36 - .../tacky-css/src/property/fragmentation.ts | 13 - .../src/property/generatedContent.ts | 12 - packages/tacky-css/src/property/images.ts | 5 - packages/tacky-css/src/property/index.ts | 30 - .../src/property/listsAndCounters.ts | 3 - packages/tacky-css/src/property/logical.ts | 14 - packages/tacky-css/src/property/masking.ts | 17 - .../tacky-css/src/property/miscellaneous.ts | 5 - packages/tacky-css/src/property/overflow.ts | 17 - .../tacky-css/src/property/pointerEvents.ts | 3 - .../tacky-css/src/property/positioning.ts | 24 - .../tacky-css/src/property/scrollAnchoring.ts | 3 - packages/tacky-css/src/property/table.ts | 16 - packages/tacky-css/src/property/text.ts | 37 - .../tacky-css/src/property/textDecoration.ts | 67 -- packages/tacky-css/src/property/transforms.ts | 3 - .../tacky-css/src/property/transitions.ts | 31 - packages/tacky-css/src/property/view.ts | 3 - .../tacky-css/src/property/writingModes.ts | 9 - packages/tacky-css/src/shape.ts | 51 - packages/tacky-css/src/types.ts | 275 +----- packages/tacky-css/src/unit.ts | 51 - packages/tacky-css/src/utils.ts | 46 - packages/tacky-css/tsconfig.json | 6 +- .../typings/mdn-browser-compat-data.d.ts | 139 +++ packages/tacky-css/typings/mdn-data.d.ts | 103 ++ packages/tacky-css/utils.ts | 74 ++ packages/tacky-css/yarn.lock | 922 ++++++++++++++++++ 70 files changed, 4656 insertions(+), 2162 deletions(-) create mode 100644 packages/tacky-css/build.ts create mode 100644 packages/tacky-css/csstype/collections/data-types.ts create mode 100644 packages/tacky-css/csstype/collections/properties.ts create mode 100644 packages/tacky-css/csstype/collections/syntaxes.ts create mode 100644 packages/tacky-css/csstype/data/patches.ts create mode 100644 packages/tacky-css/csstype/data/svg.ts create mode 100644 packages/tacky-css/csstype/data/urls.json create mode 100644 packages/tacky-css/csstype/declarator.ts create mode 100644 packages/tacky-css/csstype/syntax/parser.ts create mode 100644 packages/tacky-css/csstype/syntax/typer.ts create mode 100644 packages/tacky-css/csstype/typescript.ts create mode 100644 packages/tacky-css/csstype/utils/casing.ts create mode 100644 packages/tacky-css/csstype/utils/comment.ts create mode 100644 packages/tacky-css/csstype/utils/compat.ts create mode 100644 packages/tacky-css/csstype/utils/logger.ts create mode 100644 packages/tacky-css/csstype/utils/output.ts create mode 100644 packages/tacky-css/csstype/utils/urls.ts delete mode 100644 packages/tacky-css/src/animation.ts delete mode 100644 packages/tacky-css/src/color.ts delete mode 100644 packages/tacky-css/src/function.ts delete mode 100644 packages/tacky-css/src/image.ts delete mode 100644 packages/tacky-css/src/macro.ts delete mode 100644 packages/tacky-css/src/media/index.ts delete mode 100644 packages/tacky-css/src/media/mediaFeatures.ts delete mode 100644 packages/tacky-css/src/media/mediaTypes.ts delete mode 100644 packages/tacky-css/src/media/operators.ts delete mode 100644 packages/tacky-css/src/media/types.ts delete mode 100644 packages/tacky-css/src/property/animations.ts delete mode 100644 packages/tacky-css/src/property/backgroundsAndBorders.ts delete mode 100644 packages/tacky-css/src/property/basicUserInterface.ts delete mode 100644 packages/tacky-css/src/property/boxAlignment.ts delete mode 100644 packages/tacky-css/src/property/boxModel.ts delete mode 100644 packages/tacky-css/src/property/color.ts delete mode 100644 packages/tacky-css/src/property/columns.ts delete mode 100644 packages/tacky-css/src/property/compositingAndBlending.ts delete mode 100644 packages/tacky-css/src/property/containment.ts delete mode 100644 packages/tacky-css/src/property/display.ts delete mode 100644 packages/tacky-css/src/property/flexibleBoxLayout.ts delete mode 100644 packages/tacky-css/src/property/fonts.ts delete mode 100644 packages/tacky-css/src/property/fragmentation.ts delete mode 100644 packages/tacky-css/src/property/generatedContent.ts delete mode 100644 packages/tacky-css/src/property/images.ts delete mode 100644 packages/tacky-css/src/property/index.ts delete mode 100644 packages/tacky-css/src/property/listsAndCounters.ts delete mode 100644 packages/tacky-css/src/property/logical.ts delete mode 100644 packages/tacky-css/src/property/masking.ts delete mode 100644 packages/tacky-css/src/property/miscellaneous.ts delete mode 100644 packages/tacky-css/src/property/overflow.ts delete mode 100644 packages/tacky-css/src/property/pointerEvents.ts delete mode 100644 packages/tacky-css/src/property/positioning.ts delete mode 100644 packages/tacky-css/src/property/scrollAnchoring.ts delete mode 100644 packages/tacky-css/src/property/table.ts delete mode 100644 packages/tacky-css/src/property/text.ts delete mode 100644 packages/tacky-css/src/property/textDecoration.ts delete mode 100644 packages/tacky-css/src/property/transforms.ts delete mode 100644 packages/tacky-css/src/property/transitions.ts delete mode 100644 packages/tacky-css/src/property/view.ts delete mode 100644 packages/tacky-css/src/property/writingModes.ts delete mode 100644 packages/tacky-css/src/shape.ts delete mode 100644 packages/tacky-css/src/unit.ts delete mode 100644 packages/tacky-css/src/utils.ts create mode 100644 packages/tacky-css/typings/mdn-browser-compat-data.d.ts create mode 100644 packages/tacky-css/typings/mdn-data.d.ts create mode 100644 packages/tacky-css/utils.ts diff --git a/.eslintrc.js b/.eslintrc.js index 846eaeb..a973310 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,5 +49,6 @@ module.exports = { }, ], "arrow-parens": ["error", "as-needed"], + "@typescript-eslint/no-namespace": 0, }, }; diff --git a/packages/tacky-css/build.ts b/packages/tacky-css/build.ts new file mode 100644 index 0000000..23653e0 --- /dev/null +++ b/packages/tacky-css/build.ts @@ -0,0 +1,98 @@ +// import * as chokidar from 'chokidar'; +import * as path from "path"; +// import * as prettier from 'prettier'; +import { writeFileAsync } from "./utils"; + +// import { runCLI } from 'jest'; + +trigger().then(() => { + process.exit(0); +}); + +async function trigger(): Promise { + const generatePath = path.resolve("./csstype"); + for (const key in require.cache) { + if (key.indexOf(generatePath) !== -1) { + delete require.cache[key]; + } + } + const { default: generateTypescript } = await import("./csstype/typescript"); + const result = await generateTypescript(); + await writeFileAsync("./src/generated.ts", result); +} + +// if (process.argv.includes('--start')) { +// trigger() +// .then(() => { +// process.exit(0); +// }) +// .catch(e => { +// console.error(e); +// process.exit(1); +// }); +// } else if (process.argv.includes('--watch')) { +// trigger() +// .catch(e => { +// console.error(e); +// }) +// .then(() => { +// console.info('Done! Watching...'); +// let debounce: NodeJS.Timer; +// chokidar +// .watch(path.join(__dirname, 'src'), { ignored: '*.json', ignoreInitial: true }) +// .on('all', (event: string) => { +// clearTimeout(debounce); +// debounce = setTimeout( +// () => +// trigger() +// .catch(e => console.error(e)) +// .then(() => console.info('Done! Moving on...')), +// 300, +// ); +// }); +// }); +// } + +// export default async function trigger() { +// console.info('Generating...'); +// const { unformattedFlow, unformattedTypescript } = await create(); +// console.info('Formatting...'); +// const [flow, typescript] = await Promise.all([ +// format(unformattedFlow, 'flow'), +// format(unformattedTypescript, 'typescript'), +// ]); +// console.info(`Writing files...`); +// await Promise.all([writeFileAsync(FLOW_FILENAME, flow), writeFileAsync(TYPESCRIPT_FILENAME, typescript)]); +// console.info('Testing...'); +// await testing(); +// } + +// async function create() { +// const generatePath = path.resolve('./src'); +// for (const key in require.cache) { +// if (key.indexOf(generatePath) !== -1) { +// delete require.cache[key]; +// } +// } +// const { default: generateFlow } = await import('./src/flow'); +// const { default: generateTypescript } = await import('./src/typescript'); +// return { unformattedFlow: await generateFlow(), unformattedTypescript: await generateTypescript() }; +// } + +// async function format(output: string, parser: prettier.BuiltInParserName) { +// const options = await prettier.resolveConfig(path.join(__dirname, '.prettierrc')); +// try { +// return prettier.format(output, { +// ...options, +// printWidth: 180, +// singleQuote: false, +// parser, +// }); +// } catch (e) { +// throw new Error(e); +// } +// } + +// function testing() { +// return runCLI({ testMatch: ['**/__tests__/dist.*.ts'] } as any, [__dirname]); +// } diff --git a/packages/tacky-css/csstype/collections/data-types.ts b/packages/tacky-css/csstype/collections/data-types.ts new file mode 100644 index 0000000..d198190 --- /dev/null +++ b/packages/tacky-css/csstype/collections/data-types.ts @@ -0,0 +1,117 @@ +import parse from '../syntax/parser'; +import typing, { addType, DataType, hasType, IDataType, ResolvedType, Type, TypeType } from '../syntax/typer'; +import { compatSyntax } from '../utils/compat'; +import { getPropertySyntax, getSyntax } from './syntaxes'; + +export interface IDataTypeDictionary { + [key: string]: ResolvedType[]; +} + +export async function getDataTypesOf(dataTypeOf: (dataTypes: IDataTypeDictionary) => Promise) { + const dictionary: IDataTypeDictionary = {}; + const result = await dataTypeOf(dictionary); + return [dictionary, result] as const; +} + +export async function resolveDataTypes( + dictionary: IDataTypeDictionary, + types: TypeType[], + minTypesInDataTypes: number, + resolver: ( + dataTypes: IDataTypeDictionary, + dataType: IDataType, + min: number, + ) => Promise = simpleDataTypeResolver, +): Promise { + let resolvedDataTypes: ResolvedType[] = []; + + for (const type of types) { + switch (type.type) { + case Type.DataType: { + const resolvedDataType = await resolver(dictionary, type, minTypesInDataTypes); + + if (resolvedDataType.length >= minTypesInDataTypes) { + // Dissolve data type if it's too small + resolvedDataTypes = addType(resolvedDataTypes, addDataType(dictionary, type.name, resolvedDataType)); + } else { + for (const resolvedType of resolvedDataType) { + resolvedDataTypes = addType(resolvedDataTypes, resolvedType); + } + } + break; + } + case Type.PropertyReference: { + const resolvedProperty = await resolver(dictionary, type, minTypesInDataTypes); + + // Dissolve property references completely + for (const resolvedType of resolvedProperty) { + resolvedDataTypes = addType(resolvedDataTypes, resolvedType); + } + break; + } + default: + resolvedDataTypes = addType(resolvedDataTypes, type); + } + } + + return resolvedDataTypes; +} + +async function simpleDataTypeResolver( + dictionary: IDataTypeDictionary, + dataType: IDataType, + minTypesInDataTypes: number, +): Promise { + const syntax = await getSyntax(dataType.name); + return syntax + ? resolveDataTypes(dictionary, typing(parse(syntax)), minTypesInDataTypes, simpleDataTypeResolver) + : [{ type: Type.String }]; +} + +export function createPropertyDataTypeResolver(data: MDN.CompatData | undefined) { + const resolver: ( + dictionary: IDataTypeDictionary, + dataType: IDataType, + min: number, + ) => Promise = async (dictionary, dataType, minTypesInDataTypes) => { + const syntax = + dataType.type === Type.DataType ? await getSyntax(dataType.name) : await getPropertySyntax(dataType.name); + return syntax + ? resolveDataTypes( + dictionary, + data ? typing(compatSyntax(data, parse(syntax))) : typing(parse(syntax)), + minTypesInDataTypes, + resolver, + ) + : [{ type: Type.String }]; + }; + + return resolver; +} + +function addDataType(dictionary: IDataTypeDictionary, name: string, types: ResolvedType[], index = 0): DataType { + const realName = name + (index > 0 ? index + 1 : ''); + + // Rename in case of conflict + if (realName in dictionary) { + const existingDataType = dictionary[realName]; + + for (const type of types) { + if (!hasType(existingDataType, type)) { + return addDataType(dictionary, name, types, index + 1); + } + } + for (const type of existingDataType) { + if (!hasType(types, type)) { + return addDataType(dictionary, name, types, index + 1); + } + } + } + + dictionary[realName] = types; + + return { + type: Type.DataType, + name: realName, + }; +} diff --git a/packages/tacky-css/csstype/collections/properties.ts b/packages/tacky-css/csstype/collections/properties.ts new file mode 100644 index 0000000..45d828f --- /dev/null +++ b/packages/tacky-css/csstype/collections/properties.ts @@ -0,0 +1,225 @@ +import { properties as rawSvgProperties } from '../data/svg'; +import parse from '../syntax/parser'; +import typing, { ResolvedType } from '../syntax/typer'; +import { composeCommentBlock } from '../utils/comment'; +import { + compatNames, + compatSyntax, + getCompats, + getPropertyData, + getTypesData, + isAddedBySome, + isDeprecated, +} from '../utils/compat'; +import { warn } from '../utils/logger'; +import { createPropertyDataTypeResolver, IDataTypeDictionary, resolveDataTypes } from './data-types'; +import { getProperties, getPropertySyntax } from './syntaxes'; + +const ALL = 'all'; + +const IGNORES = [ + // Custom properties + '--*', + // We define this manually + ALL, +]; + +const REGEX_VENDOR_PROPERTY = /^-/; + +interface IProperty { + name: string; + vendor: boolean; + shorthand?: boolean; + obsolete: boolean; + comment: () => Promise; + types: ResolvedType[]; +} + +const getGlobalCompatibilityData = async () => { + const data = await getTypesData('global_keywords'); + + if (!data) { + throw new Error('Compatibility data for CSS-wide keywords is missing or may have been moved'); + } + + return data; +}; + +// The CSS-wide keywords are identical to the `all` property +// https://www.w3.org/TR/2016/CR-css-cascade-4-20160114/#all-shorthand +export async function getGlobals( + dataTypeDictionary: IDataTypeDictionary, + minTypesInDataTypes: number, +): Promise { + const dataTypes = await resolveDataTypes( + dataTypeDictionary, + typing(compatSyntax(await getGlobalCompatibilityData(), parse(await getPropertySyntax(ALL)))), + minTypesInDataTypes, + ); + + return dataTypes; +} + +const htmlPropertiesMap: { [name: string]: IProperty } = {}; + +const svgPropertiesMap: { [name: string]: IProperty } = {}; + +export async function getHtmlProperties(dataTypeDictionary: IDataTypeDictionary, minTypesInDataTypes: number) { + const propertiesMap = await getProperties(); + const allPropertyData = await getPropertyData(ALL); + + let getAllComment = async () => { + const comment = await composeCommentBlock(allPropertyData, propertiesMap[ALL]); + getAllComment = () => Promise.resolve(comment); + return comment; + }; + + htmlPropertiesMap[ALL] = { + name: 'all', + vendor: false, + shorthand: true, + obsolete: false, + comment: () => getAllComment(), + types: [], + }; + + function filterMissingProperties(names: string[]) { + // Filter only those which isn't defined in MDN data + return names.filter(name => !(name in propertiesMap)); + } + + for (const originalName in propertiesMap) { + if (IGNORES.includes(originalName)) { + continue; + } + + const data = propertiesMap[originalName]; + + // Default values + let entities = parse(await getPropertySyntax(originalName)); + let currentNames: string[] = [originalName]; + let obsoleteNames: string[] = []; + let deprecated = isDeprecated(data); + + const compatibilityData = await getPropertyData(originalName); + + if (compatibilityData) { + const compats = getCompats(compatibilityData); + + if (compats.every(compat => !isAddedBySome(compat))) { + // The property needs to be added by some browsers + continue; + } + + entities = compatSyntax(compatibilityData, entities); + currentNames = currentNames.concat( + ...compats.map(compat => filterMissingProperties(compatNames(compat, originalName))), + ); + obsoleteNames = obsoleteNames.concat( + ...compats.map(compat => filterMissingProperties(compatNames(compat, originalName, true))), + ); + deprecated = compats.every(compat => isDeprecated(data, compat)); + } + + if (deprecated) { + // Move all property names to obsolete + obsoleteNames = obsoleteNames.concat(currentNames); + currentNames = []; + } + + const types = await resolveDataTypes( + dataTypeDictionary, + typing(entities), + minTypesInDataTypes, + createPropertyDataTypeResolver(compatibilityData), + ); + + // Remove duplicates + for (const name of new Set(currentNames)) { + const vendor = isVendorProperty(name); + + let getComment = () => { + const comment = composeCommentBlock(compatibilityData, data, vendor); + getComment = () => comment; + return comment; + }; + + htmlPropertiesMap[name] = mergeRecurrent(name, { + name: originalName, + vendor, + shorthand: data.shorthand, + obsolete: false, + comment: () => getComment(), + types, + }); + } + + // Remove duplicates + for (const name of new Set(obsoleteNames)) { + const vendor = isVendorProperty(name); + + let getComment = () => { + const comment = composeCommentBlock(compatibilityData, data, vendor, true); + getComment = () => comment; + return comment; + }; + + htmlPropertiesMap[name] = mergeRecurrent(name, { + name: originalName, + vendor, + shorthand: data.shorthand, + obsolete: true, + comment: () => getComment(), + types, + }); + } + } + + return htmlPropertiesMap; +} + +export async function getSvgProperties(dataTypeDictionary: IDataTypeDictionary, minTypesInDataTypes: number) { + for (const name in rawSvgProperties) { + const compatibilityData = await getPropertyData(name); + const syntax = rawSvgProperties[name].syntax; + if (syntax) { + svgPropertiesMap[name] = { + name, + vendor: false, + shorthand: false, + obsolete: false, + comment: () => Promise.resolve(undefined), + types: await resolveDataTypes( + dataTypeDictionary, + typing(parse(syntax)), + minTypesInDataTypes, + createPropertyDataTypeResolver(compatibilityData), + ), + }; + } + } + + return svgPropertiesMap; +} + +export function isVendorProperty(name: string) { + return REGEX_VENDOR_PROPERTY.test(name); +} + +function mergeRecurrent(name: string, property: IProperty) { + if (name in htmlPropertiesMap) { + const current = htmlPropertiesMap[name]; + + if (current.name !== property.name) { + warn('Property `%s` resolved by `%s` was duplicated by `%s`', name, property.name, current.name); + } + + return { + ...current, + // Only mark as obsolete if it's mutual + obsolete: current.obsolete && property.obsolete, + }; + } + + return property; +} diff --git a/packages/tacky-css/csstype/collections/syntaxes.ts b/packages/tacky-css/csstype/collections/syntaxes.ts new file mode 100644 index 0000000..b2fd3b0 --- /dev/null +++ b/packages/tacky-css/csstype/collections/syntaxes.ts @@ -0,0 +1,136 @@ +import * as rawProperties from 'mdn-data/css/properties.json'; +import * as rawSyntaxes from 'mdn-data/css/syntaxes.json'; +import { IExtendedProperty, properties as patchedProperties, syntaxes as patchedSyntaxes } from '../data/patches'; +import { properties as rawSvgProperties, syntaxes as rawSvgSyntaxes } from '../data/svg'; +import parse from '../syntax/parser'; +import typing, { hasType } from '../syntax/typer'; +import { getPropertyData, getTypesData } from '../utils/compat'; +import { error, warn } from '../utils/logger'; +import { createPropertyDataTypeResolver, resolveDataTypes } from './data-types'; + +export async function getProperties() { + const properties: { [property: string]: IExtendedProperty } = {}; + + for (const name in rawProperties) { + properties[name] = { + ...rawProperties[name], + syntax: await getPropertySyntax(name), + shorthand: + name in patchedProperties && typeof patchedProperties[name].shorthand === 'boolean' + ? patchedProperties[name].shorthand + : Array.isArray(rawProperties[name].computed), + }; + } + + for (const name in patchedProperties) { + if (!(name in rawProperties)) { + properties[name] = patchedProperties[name]; + } + } + + return properties; +} + +export function isProperty(name: string) { + return ( + name in rawProperties || name in rawSvgProperties || (name in patchedProperties && !!patchedProperties[name].syntax) + ); +} + +export function isSyntax(name: string) { + return name in rawSyntaxes || name in rawSvgSyntaxes || (name in patchedSyntaxes && !!patchedSyntaxes[name].syntax); +} + +const validatedPropertySyntaxes: string[] = []; + +export async function getPropertySyntax(name: string) { + const patch = patchedProperties[name]; + const rawSyntax = rawProperties[name] && rawProperties[name].syntax; + + if (patch && patch.syntax) { + if (rawSyntax && !validatedPropertySyntaxes.includes(name)) { + const compatibilityData = await getPropertyData(name); + + if (compatibilityData && !validatePatch(compatibilityData, rawProperties[name].syntax, patch.syntax)) { + error( + 'The patched property `%s` did not patch the source with anything or was incomplete compared to source', + name, + ); + } + + validatedPropertySyntaxes.push(name); + } + + return patch.syntax; + } + + if (!rawSyntax) { + warn('Syntax for property `%s` is missing', name); + } + + return rawSyntax; +} + +const validatedSyntaxes: string[] = []; + +export async function getSyntax(name: string) { + const patch = patchedSyntaxes[name]; + + const rawSyntax = + name in rawSyntaxes ? rawSyntaxes[name].syntax : rawSvgSyntaxes[name] && rawSvgSyntaxes[name].syntax; + + if (patch && patch.syntax) { + if (rawSyntax && !validatedSyntaxes.includes(name)) { + const compatibilityData = await getTypesData(name); + + if (compatibilityData && !validatePatch(compatibilityData, rawSyntaxes[name].syntax, patch.syntax)) { + error( + 'The patched syntax `%s` did not patch the source with anything or was incomplete compared to source', + name, + ); + } + + validatedSyntaxes.push(name); + } + + return patch.syntax; + } + + if (!rawSyntax) { + warn('Syntax for `%s` is missing', name); + } + + return rawSyntax; +} + +async function validatePatch(compat: MDN.CompatData, sourceSyntax: string, patchSyntax: string): Promise { + // Dissolve all data types to check whether it already exists or not + const dissolvedSourceTypes = await resolveDataTypes( + {}, + typing(parse(sourceSyntax)), + Infinity, + createPropertyDataTypeResolver(compat), + ); + const dissolvedPatchTypes = await resolveDataTypes( + {}, + typing(parse(patchSyntax)), + Infinity, + createPropertyDataTypeResolver(compat), + ); + + for (const type of dissolvedSourceTypes) { + if (!hasType(dissolvedPatchTypes, type)) { + return false; + } + } + + let complements = false; + for (const type of dissolvedPatchTypes) { + if (!hasType(dissolvedSourceTypes, type)) { + complements = true; + break; + } + } + + return complements; +} diff --git a/packages/tacky-css/csstype/data/patches.ts b/packages/tacky-css/csstype/data/patches.ts new file mode 100644 index 0000000..7fff752 --- /dev/null +++ b/packages/tacky-css/csstype/data/patches.ts @@ -0,0 +1,23 @@ +export interface IExtendedProperty extends Partial { + shorthand?: boolean; +} + +export const properties: { [property: string]: IExtendedProperty } = { + /** + * https://drafts.csswg.org/css-overflow-3/#line-clamp + */ + 'line-clamp': { + shorthand: true, + }, +}; + +export const syntaxes: { [property: string]: MDN.Syntax } = { + // Undefined syntax + 'hex-color': { + syntax: '', + }, + // Undefined syntax + 'unicode-range': { + syntax: '', + }, +}; diff --git a/packages/tacky-css/csstype/data/svg.ts b/packages/tacky-css/csstype/data/svg.ts new file mode 100644 index 0000000..bc5f374 --- /dev/null +++ b/packages/tacky-css/csstype/data/svg.ts @@ -0,0 +1,228 @@ +// This is originated from https://svgwg.org/svg2-draft/propidx.html to add SVG specific properties +// and is a temporarily solution until https://github.com/mdn/data/issues/59 is solved + +import * as styleProperties from 'mdn-data/css/properties.json'; + +export const properties: { [property: string]: Pick } = { + 'alignment-baseline': { + syntax: + 'auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical', + initial: 'see property description', + inherited: false, + media: 'visual', + }, + 'baseline-shift': { + syntax: 'baseline | sub | super | | ', + initial: 'baseline', + inherited: false, + media: 'visual', + }, + clip: styleProperties.clip, + 'clip-path': styleProperties['clip-path'], + 'clip-rule': { + syntax: 'nonzero | evenodd', + initial: 'nonzero', + inherited: true, + media: 'visual', + }, + color: styleProperties.color, + 'color-interpolation': { + syntax: 'auto | sRGB | linearRGB', + initial: 'sRGB', + inherited: true, + media: 'visual', + }, + 'color-rendering': { + syntax: 'auto | optimizeSpeed | optimizeQuality', + initial: 'auto', + inherited: true, + media: 'visual', + }, + cursor: styleProperties.cursor, + direction: styleProperties.direction, + display: styleProperties.display, + 'dominant-baseline': { + syntax: + 'auto | use-script | no-change | reset-size | ideographic | alphabetic | hanging | mathematical | central | middle | text-after-edge | text-before-edge', + initial: 'auto', + inherited: false, + media: 'visual', + }, + fill: { + syntax: '', + initial: 'black', + inherited: true, + media: 'visual', + }, + 'fill-opacity': { + syntax: '', + initial: '1', + inherited: true, + media: 'visual', + }, + 'fill-rule': { + syntax: 'nonzero | evenodd', + initial: 'nonzero', + inherited: true, + media: 'visual', + }, + filter: styleProperties.filter, + 'flood-color': { + syntax: 'currentColor | ', + initial: 'black', + inherited: false, + media: 'visual', + }, + 'flood-opacity': { + syntax: '', + initial: '1', + inherited: false, + media: 'visual', + }, + font: styleProperties.font, + 'font-family': styleProperties['font-family'], + 'font-size': styleProperties['font-size'], + 'font-size-adjust': styleProperties['font-size-adjust'], + 'font-stretch': styleProperties['font-stretch'], + 'font-style': styleProperties['font-style'], + 'font-variant': styleProperties['font-variant'], + 'font-weight': styleProperties['font-weight'], + 'glyph-orientation-vertical': { + syntax: 'auto | | ', + initial: 'auto', + inherited: true, + media: 'visual', + }, + 'image-rendering': styleProperties['image-rendering'], + 'letter-spacing': styleProperties['letter-spacing'], + 'lighting-color': { + syntax: 'currentColor | ', + initial: 'white', + inherited: false, + media: 'visual', + }, + 'line-height': styleProperties['line-height'], + marker: { + syntax: 'none | ', + initial: 'none | ', + inherited: true, + media: 'visual', + }, + 'marker-end': { + syntax: 'none | ', + initial: 'none', + inherited: true, + media: 'visual', + }, + 'marker-mid': { + syntax: 'none | ', + initial: 'none', + inherited: true, + media: 'visual', + }, + 'marker-start': { + syntax: 'none | ', + initial: 'none', + inherited: true, + media: 'visual', + }, + mask: styleProperties.mask, + opacity: styleProperties.opacity, + overflow: styleProperties.overflow, + 'paint-order': styleProperties['paint-order'], + 'pointer-events': styleProperties['pointer-events'], + 'shape-rendering': { + syntax: 'auto | optimizeSpeed | crispEdges | geometricPrecision', + initial: 'auto', + inherited: true, + media: 'visual', + }, + 'stop-color': { + syntax: 'currentColor | ', + initial: 'black', + inherited: false, + media: 'visual', + }, + 'stop-opacity': { + syntax: '', + initial: '1', + inherited: false, + media: 'visual', + }, + stroke: { + syntax: '', + initial: 'none', + inherited: true, + media: 'visual', + }, + 'stroke-dasharray': { + syntax: 'none | ', + initial: 'none', + inherited: true, + media: 'visual', + }, + 'stroke-dashoffset': { + syntax: ' | ', + initial: '0', + inherited: true, + media: 'visual', + }, + 'stroke-linecap': { + syntax: 'butt | round | square', + initial: 'butt', + inherited: true, + media: 'visual', + }, + 'stroke-linejoin': { + syntax: 'miter | round | bevel', + initial: 'miter', + inherited: true, + media: 'visual', + }, + 'stroke-miterlimit': { + syntax: '', + initial: '4', + inherited: true, + media: 'visual', + }, + 'stroke-opacity': { + syntax: '', + initial: '1', + inherited: true, + media: 'visual', + }, + 'stroke-width': { + syntax: ' | ', + initial: '1', + inherited: true, + media: 'visual', + }, + 'text-anchor': { + syntax: 'start | middle | end', + initial: 'start', + inherited: true, + media: 'visual', + }, + 'text-decoration': styleProperties['text-decoration'], + 'text-rendering': styleProperties['text-rendering'], + 'unicode-bidi': styleProperties['unicode-bidi'], + 'vector-effect': { + syntax: 'non-scaling-stroke | none', + initial: 'none', + inherited: false, + media: 'visual', + }, + visibility: styleProperties.visibility, + 'word-spacing': styleProperties['word-spacing'], + 'white-space': styleProperties['white-space'], + 'writing-mode': styleProperties['writing-mode'], +}; + +export const syntaxes: MDN.Syntaxes = { + paint: { + syntax: 'none | child | child() | | [ none | ]? | context-fill | context-stroke', + }, + dasharray: { + syntax: '[ | | ]#', + }, +}; diff --git a/packages/tacky-css/csstype/data/urls.json b/packages/tacky-css/csstype/data/urls.json new file mode 100644 index 0000000..35039a6 --- /dev/null +++ b/packages/tacky-css/csstype/data/urls.json @@ -0,0 +1,471 @@ +{ + "https://developer.mozilla.org/docs/Mozilla/CSS/overflow-clip-box": "The **`overflow-clip-box`** CSS property specifies relative to which box the clipping happens when there is an overflow. It is short hand for the `overflow-clip-box-inline` and `overflow-clip-box-block` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-binding": "The **`-moz-binding`** CSS property is used by Mozilla-based applications to attach an XBL binding to a DOM element.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-border-bottom-colors": "In Mozilla applications like Firefox, the **`-moz-border-bottom-colors`** CSS property sets a list of colors for the bottom border.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-border-left-colors": "In Mozilla applications like Firefox, the **`-moz-border-left-colors`** CSS property sets a list of colors for the left border.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-border-right-colors": "In Mozilla applications like Firefox, the **`-moz-border-right-colors`** CSS property sets a list of colors for the right border.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-border-top-colors": "In Mozilla applications like Firefox, the **`-moz-border-top-colors`** CSS property sets a list of colors for the top border.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-context-properties": "The `**-moz-context-properties**` property can be used within privileged contexts in Firefox to share the values of specified properties of the element with a child SVG image.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-float-edge": "The non-standard **`-moz-float-edge`** CSS property specifies whether the height and width properties of the element include the margin, border, or padding thickness.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-force-broken-image-icon": "The **`-moz-force-broken-image-icon`** extended CSS property can be used to force the broken image icon to be shown even when a broken image has an `alt` attribute.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-image-region": "For certain XUL elements and pseudo-elements that use an image from the `list-style-image` property, this property specifies a region of the image that is used in place of the whole image. This allows elements to use different pieces of the same image to improve performance.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-orient": "The **`-moz-orient`** CSS property specifies the orientation of the element to which it's applied.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-outline-radius": "In Mozilla applications like Firefox, the **`-moz-outline-radius`** CSS shorthand property can be used to give an element's `outline` rounded corners.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-outline-radius-bottomleft": "In Mozilla applications, the **`-moz-outline-radius-bottomleft`** CSS property can be used to round the bottom-left corner of an element's `outline`.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-outline-radius-bottomright": "In Mozilla applications, the **`-moz-outline-radius-bottomright`** CSS property can be used to round the bottom-right corner of an element's `outline`.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-outline-radius-topleft": "In Mozilla applications, the **`-moz-outline-radius-topleft`** CSS property can be used to round the top-left corner of an element's `outline`.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-outline-radius-topright": "In Mozilla applications, the **`-moz-outline-radius-topright`** CSS property can be used to round the top-right corner of an element's `outline`.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-stack-sizing": "**`-moz-stack-sizing`** is an extended CSS property. Normally, a `` will change its size so that all of its child elements are completely visible. For example, moving a child of the stack far to the right will widen the stack so the child remains visible.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-text-blink": "The **`-moz-text-blink`** non-standard Mozilla CSS extension specifies the blink mode.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-user-focus": "The **`-moz-user-focus`** CSS property is used to indicate whether an element can have the focus.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-user-input": "In Mozilla applications, **`-moz-user-input`** determines if an element will accept user input.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-user-modify": "The **`user-modify`** property has no effect in Firefox. It was originally planned to determine whether or not the content of an element can be edited by a user.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-window-dragging": "The **`-moz-window-dragging`** CSS property specifies whether a window is draggable or not. It only works in Chrome code, and only on Mac OS X.", + "https://developer.mozilla.org/docs/Web/CSS/-moz-window-shadow": "The **`-moz-window-shadow`** CSS property specifies whether a window will have a shadow. It only works on Mac OS X.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-accelerator": "The **`-ms-accelerator`** CSS property is a Microsoft extension that sets or retrieves a string indicating whether the object represents a keyboard shortcut.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-block-progression": "The **`-ms-block-progression`** CSS property is a Microsoft extension that specifies the block progression and layout orientation.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-chaining": "The **`-ms-content-zoom-chaining`** CSS property is a Microsoft extension specifying the zoom behavior that occurs when a user hits the zoom limit during page manipulation.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-limit": "The **`-ms-content-zoom-limit`** CSS shorthand property is a Microsoft extension that specifies values for the `-ms-content-zoom-limit-min` and `-ms-content-zoom-limit-max` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-limit-max": "The **`-ms-content-zoom-limit-max`** CSS property is a Microsoft extension that specifies the selected elements' maximum zoom factor.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-limit-min": "The **`-ms-content-zoom-limit-min`** CSS property is a Microsoft extension that specifies the minimum zoom factor.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-snap": "The **`-ms-content-zoom-snap`** CSS shorthand property is a Microsoft extension that specifies values for the `-ms-content-zoom-snap-type` and `-ms-content-zoom-snap-points` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-snap-points": "The **`-ms-content-zoom-snap-points`** CSS property is a Microsoft extension that specifies where zoom snap-points are located.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zoom-snap-type": "The **`-ms-content-zoom-snap-type`** CSS property is a Microsoft extension that specifies how zooming is affected by defined snap-points.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-content-zooming": "The **`-ms-content-zooming`** CSS property is a Microsoft extension that specifies whether zooming is enabled.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-filter": "The `-ms-filter` CSS property is a Microsoft extension that sets or retrieves the filter or collection of filters applied to an object.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-flow-from": "The **`-ms-flow-from`** CSS property is a Microsoft extension that gets or sets a value identifying a region container in the document that accepts the content flow from the data source.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-flow-into": "The **`-ms-flow-into`** CSS property is a Microsoft extension that gets or sets a value identifying an iframe container in the document that serves as the region's data source.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-high-contrast-adjust": "The **`-ms-high-contrast-adjust`** CSS property is a Microsoft extension that gets or sets a value indicating whether to override any CSS properties that would have been set in high contrast mode.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-hyphenate-limit-chars": "The **`-ms-hyphenate-limit-chars`** CSS property is a Microsoft extension that specifies one to three values indicating the minimum number of characters in a hyphenated word. If the word does not meet the required minimum number of characters in the word, before the hyphen, or after the hyphen, then the word is not hyphenated.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-hyphenate-limit-lines": "The **`-ms-hyphenate-limit-lines`** CSS property is a Microsoft extension specifying the maximum number of consecutive lines in an element that may be ended with a hyphenated word.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-hyphenate-limit-zone": "The `**-ms-hyphenate-limit-zone**` CSS property is a Microsoft extension specifying the width of the hyphenation zone.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-ime-align": "The **`-ms-ime-align`** CSS property is a Microsoft extension aligning the Input Method Editor (IME) candidate window box relative to the element on which the IME composition is active. The extension is implemented in Microsoft Edge and Internet Explorer 11.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-overflow-style": "The **`-ms-overflow-style`** CSS property is a Microsoft extension controlling the behavior of scrollbars when the content of an element overflows.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-chaining": "The `**-ms-scroll-chaining**` CSS property is a Microsoft extension that specifies the scrolling behavior that occurs when a user hits the scroll limit during a manipulation.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit": "The **\\-ms-scroll-limit** CSS property is a Microsoft extension that specifies values for the `-ms-scroll-limit-x-min`, `-ms-scroll-limit-y-min`, `-ms-scroll-limit-x-max`, and `-ms-scroll-limit-y-max` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit-x-max": "The `**-ms-scroll-limit-x-max**` CSS property is a Microsoft extension that specifies the maximum value for the `Element.scrollLeft` property.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit-x-min": "The **`-ms-scroll-limit-x-min`** CSS property is a Microsoft extension that specifies the minimum value for the `Element.scrollLeft` property.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit-y-max": "The **`-ms-scroll-limit-y-max`** CSS property is a Microsoft extension that specifies the maximum value for the `Element.scrollTop` property.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit-y-min": "The **`-ms-scroll-limit-y-min`** CSS property is a Microsoft extension that specifies the minimum value for the `Element.scrollTop` property.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-rails": "The **`-ms-scroll-rails`** CSS property is a Microsoft extension that specifies whether scrolling locks to the primary axis of motion.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-snap-points-x": "The **`-ms-scroll-snap-points-x`** CSS property is a Microsoft extension that specifies where snap-points will be located along the x-axis.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-snap-points-y": "The **`-ms-scroll-snap-points-y`** CSS property is a Microsoft extension that specifies where snap-points will be located along the y-axis.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-snap-type": "The **`scroll-snap-type`** CSS property sets how strictly snap points are enforced on the scroll container in case there is one.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-snap-x": "The **`-ms-scroll-snap-x`** CSS shorthand property is a Microsoft extension that specifies values for the `-ms-scroll-snap-type` and `-ms-scroll-snap-points-x` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-snap-y": "The **`-ms-scroll-snap-x`** CSS shorthand property is a Microsoft extension that specifies values for the `-ms-scroll-snap-type` and `-ms-scroll-snap-points-y` properties.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-translation": "The **`-ms-scroll-translation`** CSS property is a Microsoft extension that specifies whether vertical-to-horizontal scroll wheel translation occurs on the specified element.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-3dlight-color": "The **`-ms-scrollbar-3dlight-color`** CSS property is a Microsoft extension specifying the color of the top and left edges of the scroll box and scroll arrows of a scroll bar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-arrow-color": "The **`-ms-scrollbar-arrow-color`** CSS property is a Microsoft extension that specifies the color of the arrow elements of a scroll arrow.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-base-color": "The `**-ms-scrollbar-base-color**` CSS property is a Microsoft extension that specifies the base color of the main elements of a scroll bar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-darkshadow-color": "The **`-ms-scrollbar-darkshadow-color`** CSS property is a Microsoft extension that specifies the color of a scroll bar's gutter.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-face-color": "The `**-ms-scrollbar-face-color**` CSS property is a Microsoft extension that specifies the color of the scroll box and scroll arrows of a scroll bar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-highlight-color": "The `**-ms-scrollbar-highlight-color**` CSS property is a Microsoft extension that specifies the color of the slider tray, the top and left edges of the scroll box, and the scroll arrows of a scroll bar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-shadow-color": "The **`-ms-scrollbar-shadow-color`** CSS property is a Microsoft extension that specifies the color of the bottom and right edges of the scroll box and scroll arrows of a scroll bar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-scrollbar-track-color": "The **`-ms-scrollbar-track-color`** CSS property is a Microsoft extension that specifies the color of the track element of a scrollbar.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-text-autospace": "The **`-ms-text-autospace`** CSS property is a Microsoft extension that specifies the autospacing and narrow space width adjustment of text.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-touch-select": "The **`-ms-touch-select`** CSS property is a Microsoft extension that toggles the gripper visual elements that enable touch text selection.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-user-select": "The `**user-select**` CSS property controls whether the user can select text. This doesn't have any effect on content loaded as chrome, except in textboxes.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-wrap-flow": "The **`-ms-wrap-flow`** CSS property is a Microsoft extension that specifies how exclusions impact inline content within block-level elements.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-wrap-margin": "The **`-ms-wrap-margin`** CSS property is a Microsoft extension that specifies a margin that offsets the inner wrap shape from other shapes.", + "https://developer.mozilla.org/docs/Web/CSS/-ms-wrap-through": "The **`-ms-wrap-through`** CSS property is a Microsoft extension that specifies how content should wrap around an exclusion element.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-border-before": "The **`-webkit-border-before`** CSS property is a shorthand property for setting the individual logical block start border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-box-reflect": "The **`-webkit-box-reflect`** CSS property lets you reflect the content of an element in one specific direction.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-line-clamp": "The **`-webkit-line-clamp`** CSS property allows limiting of the contents of a block container to the specified number of lines.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-attachment": "If a `-webkit-mask-image` is specified, `-webkit-mask-attachment` determines whether the mask image's position is fixed within the viewport, or scrolls along with its containing block.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-composite": "The **`-webkit-mask-composite`** property specifies the manner in which multiple mask images applied to the same element are composited with one another. Mask images are composited in the opposite order that they are declared with the `-webkit-mask-image` property.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-position-x": "The `-webkit-mask-position-x` CSS property sets the initial horizontal position of a mask image.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-position-y": "The `-webkit-mask-position-y` CSS property sets the initial vertical position of a mask image.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-repeat-x": "The `-webkit-mask-repeat-x` property specifies whether and how a mask image is repeated (tiled) horizontally.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-mask-repeat-y": "The `-webkit-mask-repeat-y` property sets whether and how a mask image is repeated (tiled) vertically.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-overflow-scrolling": "The `-webkit-overflow-scrolling` CSS property controls whether or not touch devices use momentum-based scrolling for a given element.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-tap-highlight-color": "**`-webkit-tap-highlight-color`** is a non-standard CSS property that sets the color of the highlight that appears over a link while it's being tapped. The highlighting indicates to the user that their tap is being successfully recognized, and indicates which element they're tapping on.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-text-fill-color": "The **`-webkit-text-fill-color`** CSS property specifies the fill color of characters of text. If this property is not set, the value of the `color` property is used.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-text-stroke": "The **`-webkit-text-stroke`** CSS property specifies the width and color of strokes for text characters. This is a shorthand property for the longhand properties `-webkit-text-stroke-width` and `-webkit-text-stroke-color`.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-text-stroke-color": "The **`-webkit-text-stroke-color`** CSS property specifies the stroke color of characters of text. If this property is not set, the value of the `color` property is used.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-text-stroke-width": "The **`-webkit-text-stroke-width`** CSS property specifies the width of the stroke for text.", + "https://developer.mozilla.org/docs/Web/CSS/-webkit-touch-callout": "The `-webkit-touch-callout` CSS property controls the display of the default callout shown when you touch and hold a touch target.", + "https://developer.mozilla.org/docs/Web/CSS/align-content": "The CSS **`align-content`** property sets the distribution of space between and around content items along a flexbox's cross-axis or a grid's block axis.", + "https://developer.mozilla.org/docs/Web/CSS/align-items": "The CSS **`align-items`** property sets the `align-self` value on all direct children as a group. In Flexbox, it controls the alignment of items on the Cross Axis. In Grid Layout, it controls the alignment of items on the Block Axis within their grid area.", + "https://developer.mozilla.org/docs/Web/CSS/align-self": "The **`align-self`** CSS property overrides a grid or flex item's `align-items` value. In Grid, it aligns the item inside the grid area. In Flexbox, it aligns the item on the cross axis.", + "https://developer.mozilla.org/docs/Web/CSS/all": "The `**all**` shorthand CSS property resets all of an element's properties except `unicode-bidi`, `direction`, and CSS Custom Properties. It can set properties to their initial or inherited values, or to the values specified in another stylesheet origin.", + "https://developer.mozilla.org/docs/Web/CSS/animation": "The **`animation`** shorthand CSS property applies an animation between styles. It is a shorthand for `animation-name`, `animation-duration`, `animation-timing-function`, `animation-delay`, `animation-iteration-count`, `animation-direction`, `animation-fill-mode`, and `animation-play-state`.", + "https://developer.mozilla.org/docs/Web/CSS/animation-delay": "The **`animation-delay`** CSS property specifies the amount of time to wait from applying the animation to an element before beginning to perform the animation. The animation can start later, immediately from its beginning, or immediately and partway through the animation.", + "https://developer.mozilla.org/docs/Web/CSS/animation-direction": "The **`animation-direction`** CSS property sets whether an animation should play forward, backward, or alternate back and forth between playing the sequence forward and backward.", + "https://developer.mozilla.org/docs/Web/CSS/animation-duration": "The **`animation-duration`** CSS property sets the length of time that an animation takes to complete one cycle.", + "https://developer.mozilla.org/docs/Web/CSS/animation-fill-mode": "The **`animation-fill-mode`** CSS property sets how a CSS animation applies styles to its target before and after its execution.", + "https://developer.mozilla.org/docs/Web/CSS/animation-iteration-count": "The **`animation-iteration-count`** CSS property sets the number of times an animation sequence should be played before stopping.", + "https://developer.mozilla.org/docs/Web/CSS/animation-name": "The **`animation-name`** CSS property specifies the names of one or more `@keyframes` at-rules describing the animation or animations to apply to the element.", + "https://developer.mozilla.org/docs/Web/CSS/animation-play-state": "The **`animation-play-state`** CSS property sets whether an animation is running or paused.", + "https://developer.mozilla.org/docs/Web/CSS/animation-timing-function": "The **`animation-timing-function`** CSS property sets how an animation progresses through the duration of each cycle.", + "https://developer.mozilla.org/docs/Web/CSS/appearance": "The `**appearance**` CSS property is used to display an element using platform-native styling, based on the operating system's theme. The **`-moz-appearance`** and **`-webkit-appearance`** properties are non-standard versions of this propery, used (respectively) by Gecko (Firefox) and by WebKit-based (e.g., Safari) and Blink-based (e.g., Chrome, Opera) browsers to achieve the same thing. Note that Firefox and Edge also support `-webkit-appearance`, for compatibility reasons.", + "https://developer.mozilla.org/docs/Web/CSS/aspect-ratio": "The **`aspect-ratio`**   CSS property sets a **preferred aspect ratio** for the box, which will be used in the calculation of auto sizes and some other layout functions.", + "https://developer.mozilla.org/docs/Web/CSS/backdrop-filter": "The **`backdrop-filter`** CSS property lets you apply graphical effects such as blurring or color shifting to the area behind an element. Because it applies to everything _behind_ the element, to see the effect you must make the element or its background at least partially transparent.", + "https://developer.mozilla.org/docs/Web/CSS/backface-visibility": "The **`backface-visibility`** CSS property sets whether the back face of an element is visible when turned towards the user.", + "https://developer.mozilla.org/docs/Web/CSS/background": "The **`background`** shorthand CSS property sets all background style properties at once, such as color, image, origin and size, or repeat method.", + "https://developer.mozilla.org/docs/Web/CSS/background-attachment": "The **`background-attachment`** CSS property sets whether a background image's position is fixed within the viewport, or scrolls with its containing block.", + "https://developer.mozilla.org/docs/Web/CSS/background-blend-mode": "The **`background-blend-mode`** CSS property sets how an element's background images should blend with each other and with the element's background color.", + "https://developer.mozilla.org/docs/Web/CSS/background-clip": "The **`background-clip`** CSS property sets whether an element's background extends underneath its border box, padding box, or content box.", + "https://developer.mozilla.org/docs/Web/CSS/background-color": "The **`background-color`** CSS property sets the background color of an element.", + "https://developer.mozilla.org/docs/Web/CSS/background-image": "The **`background-image`** CSS property sets one or more background images on an element.", + "https://developer.mozilla.org/docs/Web/CSS/background-origin": "The **`background-origin`** CSS property sets the background's origin: from the border start, inside the border, or inside the padding.", + "https://developer.mozilla.org/docs/Web/CSS/background-position": "The **`background-position`** CSS property sets the initial position for each background image. The position is relative to the position layer set by `background-origin`.", + "https://developer.mozilla.org/docs/Web/CSS/background-position-x": "The **`background-position-x`** CSS property sets the initial horizontal position for each background image. The position is relative to the position layer set by `background-origin`.", + "https://developer.mozilla.org/docs/Web/CSS/background-position-y": "The **`background-position-y`** CSS property sets the initial vertical position for each background image. The position is relative to the position layer set by `background-origin`.", + "https://developer.mozilla.org/docs/Web/CSS/background-repeat": "The **`background-repeat`** CSS property sets how background images are repeated. A background image can be repeated along the horizontal and vertical axes, or not repeated at all.", + "https://developer.mozilla.org/docs/Web/CSS/background-size": "The **`background-size`** CSS property sets the size of the element's background image. The image can be left to its natural size, stretched, or constrained to fit the available space.", + "https://developer.mozilla.org/docs/Web/CSS/block-size": "The **`block-size`** CSS property defines the horizontal or vertical size of an element's block, depending on its writing mode. It corresponds to either the `width` or the `height` property, depending on the value of `writing-mode`.", + "https://developer.mozilla.org/docs/Web/CSS/border": "The **`border`** shorthand CSS property sets an element's border. It sets the values of `border-width`, `border-style`, and `border-color`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block": "The **`border-block`** CSS property is a shorthand property for setting the individual logical block border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-color": "The **`border-block-color`** CSS property defines the color of the logical block borders of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color` and `border-bottom-color`, or `border-right-color` and `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-end": "The **`border-block-end`** CSS property is a shorthand property for setting the individual logical block-end border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-end-color": "The **`border-block-end-color`** CSS property defines the color of the logical block-end border of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color`, `border-right-color`, `border-bottom-color`, or `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-end-style": "The **`border-block-end-style`** CSS property defines the style of the logical block-end border of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style`, `border-right-style`, `border-bottom-style`, or `border-left-style` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-end-width": "The **`border-block-end-width`** CSS property defines the width of the logical block-end border of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width`, `border-right-width`, `border-bottom-width`, or `border-left-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-start": "The **`border-block-start`** CSS property is a shorthand property for setting the individual logical block-start border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-start-color": "The **`border-block-start-color`** CSS property defines the color of the logical block-start border of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color`, `border-right-color`, `border-bottom-color`, or `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-start-style": "The **`border-block-start-style`** CSS property defines the style of the logical block start border of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style`, `border-right-style`, `border-bottom-style`, or `border-left-style` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-start-width": "The **`border-block-start-width`** CSS property defines the width of the logical block-start border of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width`, `border-right-width`, `border-bottom-width`, or `border-left-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-style": "The **`border-block-style`** CSS property defines the style of the logical block borders of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style` and `border-bottom-style`, or `border-left-style` and `border-right-style` properties depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-block-width": "The **`border-block-width`** CSS property defines the width of the logical block borders of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width` and `border-bottom-width`, or `border-left-width`, and `border-right-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom": "The **`border-bottom`** shorthand CSS property sets an element's bottom border. It sets the values of `border-bottom-width`, `border-bottom-style` and `border-bottom-color`.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom-color": "The **`border-bottom-color`** CSS property sets the color of an element's bottom border. It can also be set with the shorthand CSS properties `border-color` or `border-bottom`.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom-left-radius": "The **`border-bottom-left-radius`** CSS property rounds the bottom-left corner of an element by specifying the radius (or the radius of the semi-major and semi-minor axes) of the ellipse defining the curvature of the corner.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom-right-radius": "The **`border-bottom-right-radius`** CSS property rounds the top-left corner of an element by specifying the radius (or the radius of the semi-major and semi-minor axes) of the ellipse defining the curvature of the corner.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom-style": "The **`border-bottom-style`** CSS property sets the line style of an element's bottom `border`.", + "https://developer.mozilla.org/docs/Web/CSS/border-bottom-width": "The **`border-bottom-width`** CSS property sets the width of the bottom border of an element.", + "https://developer.mozilla.org/docs/Web/CSS/border-collapse": "The **`border-collapse`** CSS property sets whether cells inside a `` have shared or separate borders.", + "https://developer.mozilla.org/docs/Web/CSS/border-color": "The **`border-color`** shorthand CSS property sets the color of an element's border.", + "https://developer.mozilla.org/docs/Web/CSS/border-end-end-radius": "The **`border-end-end-radius`** CSS property defines a logical border radius on an element, which maps to a physical border radius that depends on on the element's `writing-mode`, `direction`, and `text-orientation`. This is useful when building styles to work regardless of the text orientation and writing mode.", + "https://developer.mozilla.org/docs/Web/CSS/border-end-start-radius": "The **`border-end-start-radius`** CSS property defines a logical border radius on an element, which maps to a physical border radius depending on the element's `writing-mode`, `direction`, and `text-orientation`. This is useful when building styles to work regardless of the text orientation and writing mode.", + "https://developer.mozilla.org/docs/Web/CSS/border-image": "The **`border-image`** CSS property draws an image around a given element. It replaces the element's regular border.", + "https://developer.mozilla.org/docs/Web/CSS/border-image-outset": "The **`border-image-outset`** CSS property sets the distance by which an element's border image is set out from its border box.", + "https://developer.mozilla.org/docs/Web/CSS/border-image-repeat": "The **`border-image-repeat`** CSS property defines how the edge regions of a source image are adjusted to fit the dimensions of an element's border image.", + "https://developer.mozilla.org/docs/Web/CSS/border-image-slice": "The **`border-image-slice`** CSS property divides the image specified by `border-image-source` into regions. These regions form the components of an element's border image.", + "https://developer.mozilla.org/docs/Web/CSS/border-image-source": "The **`border-image-source`** CSS property sets the source image used to create an element's border image.", + "https://developer.mozilla.org/docs/Web/CSS/border-image-width": "The **`border-image-width`** CSS property sets the width of an element's border image.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline": "The **`border-inline`** CSS property is a shorthand property for setting the individual logical inline border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-color": "The **`border-inline-color`** CSS property defines the color of the logical inline borders of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color` and `border-bottom-color`, or `border-right-color` and `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-end": "The **`border-inline-end`** CSS property is a shorthand property for setting the individual logical inline-end border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-end-color": "The **`border-inline-end-color`** CSS property defines the color of the logical inline-end border of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color`, `border-right-color`, `border-bottom-color`, or `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-end-style": "The **`border-inline-end-style`** CSS property defines the style of the logical inline end border of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style`, `border-right-style`, `border-bottom-style`, or `border-left-style` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-end-width": "The **`border-inline-end-width`** CSS property defines the width of the logical inline-end border of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width`, `border-right-width`, `border-bottom-width`, or `border-left-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-start": "The **`border-inline-start`** CSS property is a shorthand property for setting the individual logical inline-start border property values in a single place in the style sheet.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-start-color": "The **`border-inline-start-color`** CSS property defines the color of the logical inline start border of an element, which maps to a physical border color depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-color`, `border-right-color`, `border-bottom-color`, or `border-left-color` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-start-style": "The **`border-inline-start-style`** CSS property defines the style of the logical inline start border of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style`, `border-right-style`, `border-bottom-style`, or `border-left-style` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-start-width": "The **`border-inline-start-width`** CSS property defines the width of the logical inline-start border of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width`, `border-right-width`, `border-bottom-width`, or `border-left-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-style": "The **`border-inline-style`** CSS property defines the style of the logical inline borders of an element, which maps to a physical border style depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-style` and `border-bottom-style`, or `border-left-style` and `border-right-style` properties depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-inline-width": "The **`border-inline-width`** CSS property defines the width of the logical inline borders of an element, which maps to a physical border width depending on the element's writing mode, directionality, and text orientation. It corresponds to the `border-top-width` and `border-bottom-width`, or `border-left-width`, and `border-right-width` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/border-left": "The **`border-left`** shorthand CSS property set an element's left border.", + "https://developer.mozilla.org/docs/Web/CSS/border-left-color": "The **`border-left-color`** CSS property sets the color of an element's left border. It can also be set with the shorthand CSS properties `border-color` or `border-left`.", + "https://developer.mozilla.org/docs/Web/CSS/border-left-style": "The **`border-left-style`** CSS property sets the line style of an element's left `border`.", + "https://developer.mozilla.org/docs/Web/CSS/border-left-width": "The **`border-left-width`** CSS property sets the width of the left border of an element.", + "https://developer.mozilla.org/docs/Web/CSS/border-radius": "The **`border-radius`** CSS property rounds the corners of an element's outer border edge. You can set a single radius to make circular corners, or two radii to make elliptical corners.", + "https://developer.mozilla.org/docs/Web/CSS/border-right": "The **`border-right`** shorthand CSS property sets the properties of an element's right border.", + "https://developer.mozilla.org/docs/Web/CSS/border-right-color": "The **`border-right-color`** CSS property sets the color of an element's right border. It can also be set with the shorthand CSS properties `border-color` or `border-right`.", + "https://developer.mozilla.org/docs/Web/CSS/border-right-style": "The **`border-right-style`** CSS property sets the line style of an element's right `border`.", + "https://developer.mozilla.org/docs/Web/CSS/border-right-width": "The **`border-right-width`** CSS property sets the width of the right border of an element.", + "https://developer.mozilla.org/docs/Web/CSS/border-spacing": "The **`border-spacing`** CSS property sets the distance between the borders of adjacent `
` cells. This property applies only when `border-collapse` is `separate`.", + "https://developer.mozilla.org/docs/Web/CSS/border-start-end-radius": "The **`border-start-end-radius`** CSS property defines a logical border radius on an element, which maps to a physical border radius depending on the element's `writing-mode`, `direction`, and `text-orientation`. This is useful when building styles to work regardless of the text orientation and writing mode.", + "https://developer.mozilla.org/docs/Web/CSS/border-start-start-radius": "The **`border-start-start-radius`** CSS property defines a logical border radius on an element, which maps to a physical border radius that depends on the element's `writing-mode`, `direction`, and `text-orientation`. This is useful when building styles to work regardless of the text orientation and writing mode.", + "https://developer.mozilla.org/docs/Web/CSS/border-style": "The **`border-style`** shorthand CSS property sets the line style for all four sides of an element's border.", + "https://developer.mozilla.org/docs/Web/CSS/border-top": "The **`border-top`** shorthand CSS property sets all the properties of an element's top border.", + "https://developer.mozilla.org/docs/Web/CSS/border-top-color": "The **`border-top-color`** CSS property sets the color of an element's top border. It can also be set with the shorthand CSS properties `border-color` or `border-top`.", + "https://developer.mozilla.org/docs/Web/CSS/border-top-left-radius": "The **`border-top-left-radius`** CSS property rounds the top-left corner of an element by specifying the radius (or the radius of the semi-major and semi-minor axes) of the ellipse defining the curvature of the corner.", + "https://developer.mozilla.org/docs/Web/CSS/border-top-right-radius": "The **`border-top-right-radius`** CSS property rounds the top-right corner of an element by specifying the radius (or the radius of the semi-major and semi-minor axes) of the ellipse defining the curvature of the corner.", + "https://developer.mozilla.org/docs/Web/CSS/border-top-style": "The **`border-top-style`** CSS property sets the line style of an element's top `border`.", + "https://developer.mozilla.org/docs/Web/CSS/border-top-width": "The **`border-top-width`** CSS property sets the width of the top border of an element.", + "https://developer.mozilla.org/docs/Web/CSS/border-width": "The **`border-width`** shorthand CSS property sets the width of an element's border.", + "https://developer.mozilla.org/docs/Web/CSS/bottom": "The **`bottom`** CSS property participates in setting the vertical position of a positioned element. It has no effect on non-positioned elements.", + "https://developer.mozilla.org/docs/Web/CSS/box-align": "The **`box-align`** CSS property specifies how an element aligns its contents across its layout in a perpendicular direction. The effect of the property is only visible if there is extra space in the box.", + "https://developer.mozilla.org/docs/Web/CSS/box-decoration-break": "The **`box-decoration-break`** CSS property specifies how an element's fragments should be rendered when broken across multiple lines, columns, or pages.", + "https://developer.mozilla.org/docs/Web/CSS/box-direction": "The **`box-direction`** CSS property specifies whether a box lays out its contents normally (from the top or left edge), or in reverse (from the bottom or right edge).", + "https://developer.mozilla.org/docs/Web/CSS/box-flex": "The **`-moz-box-flex`** and **`-webkit-box-flex`** CSS properties specify how a `-moz-box` or `-webkit-box` grows to fill the box that contains it, in the direction of the containing box's layout.", + "https://developer.mozilla.org/docs/Web/CSS/box-flex-group": "The **`box-flex-group`** CSS property assigns the flexbox's child elements to a flex group.", + "https://developer.mozilla.org/docs/Web/CSS/box-lines": "The **`box-lines`** CSS property determines whether the box may have a single or multiple lines (rows for horizontally oriented boxes, columns for vertically oriented boxes).", + "https://developer.mozilla.org/docs/Web/CSS/box-ordinal-group": "The **`box-ordinal-group`** CSS property assigns the flexbox's child elements to an ordinal group.", + "https://developer.mozilla.org/docs/Web/CSS/box-orient": "The **`box-orient`** CSS property sets whether an element lays out its contents horizontally or vertically.", + "https://developer.mozilla.org/docs/Web/CSS/box-pack": "The **`-moz-box-pack`** and **`-webkit-box-pack`** CSS properties specify how a `-moz-box` or `-webkit-box` packs its contents in the direction of its layout. The effect of this is only visible if there is extra space in the box.", + "https://developer.mozilla.org/docs/Web/CSS/box-shadow": "The **`box-shadow`** CSS property adds shadow effects around an element's frame. You can set multiple effects separated by commas. A box shadow is described by X and Y offsets relative to the element, blur and spread radius, and color.", + "https://developer.mozilla.org/docs/Web/CSS/box-sizing": "The **`box-sizing`** CSS property sets how the total width and height of an element is calculated.", + "https://developer.mozilla.org/docs/Web/CSS/break-after": "The **`break-after`** CSS property sets how page, column, or region breaks should behave after a generated box. If there is no generated box, the property is ignored.", + "https://developer.mozilla.org/docs/Web/CSS/break-before": "The **`break-before`** CSS property sets how page, column, or region breaks should behave before a generated box. If there is no generated box, the property is ignored.", + "https://developer.mozilla.org/docs/Web/CSS/break-inside": "The **`break-inside`** CSS property sets how page, column, or region breaks should behave inside a generated box. If there is no generated box, the property is ignored.", + "https://developer.mozilla.org/docs/Web/CSS/caption-side": "The **`caption-side`** CSS property puts the content of a table's `
` on the specified side. The values are relative to the `writing-mode` of the table.", + "https://developer.mozilla.org/docs/Web/CSS/caret-color": "The **`caret-color`** CSS property sets the color of the **insertion caret**, the visible marker where the next character typed will be inserted. This is sometimes referred to as the **text input cursor**. The caret appears in elements such as `` or those with the `contenteditable` attribute. The caret is typically a thin vertical line that flashes to help make it more noticeable. By default, it is black, but its color can be altered with this property.", + "https://developer.mozilla.org/docs/Web/CSS/clear": "The **`clear`** CSS property sets whether an element must be moved below (cleared) floating elements that precede it. The `clear` property applies to floating and non-floating elements.", + "https://developer.mozilla.org/docs/Web/CSS/clip": "The **`clip`** CSS property defines a visible portion of an element. The `clip` property applies only to absolutely positioned elements — that is, elements with `position:absolute` or `position:fixed`.", + "https://developer.mozilla.org/docs/Web/CSS/clip-path": "The `**clip-path**` CSS property creates a clipping region that sets what part of an element should be shown. Parts that are inside the region are shown, while those outside are hidden.", + "https://developer.mozilla.org/docs/Web/CSS/color": "The **`color`** CSS property sets the foreground color value of an element's text and text decorations, and sets the `currentcolor` value. `currentcolor` may be used as an indirect value on _other_ properties and is the default for other color properties, such as `border-color`.", + "https://developer.mozilla.org/docs/Web/CSS/color-adjust": "The **`color-adjust`** CSS property sets what, if anything, the user agent may do to optimize the appearance of the element on the output device. By default, the browser is allowed to make any adjustments to the element's appearance it determines to be necessary and prudent given the type and capabilities of the output device.", + "https://developer.mozilla.org/docs/Web/CSS/column-count": "The **`column-count`** CSS property breaks an element's content into the specified number of columns.", + "https://developer.mozilla.org/docs/Web/CSS/column-fill": "The **`column-fill`** CSS property controls how an element's contents are balanced when broken into columns.", + "https://developer.mozilla.org/docs/Web/CSS/column-gap": "The **`column-gap`** CSS property sets the size of the gap (gutter) between an element's columns.", + "https://developer.mozilla.org/docs/Web/CSS/column-rule": "The **`column-rule`** shorthand CSS property sets the width, style, and color of the line drawn between columns in a multi-column layout.", + "https://developer.mozilla.org/docs/Web/CSS/column-rule-color": "The **`column-rule-color`** CSS property sets the color of the line drawn between columns in a multi-column layout.", + "https://developer.mozilla.org/docs/Web/CSS/column-rule-style": "The **`column-rule-style`** CSS property sets the style of the line drawn between columns in a multi-column layout.", + "https://developer.mozilla.org/docs/Web/CSS/column-rule-width": "The **`column-rule-width`** CSS property sets the width of the line drawn between columns in a multi-column layout.", + "https://developer.mozilla.org/docs/Web/CSS/column-span": "The **`column-span`** CSS property makes it possible for an element to span across all columns when its value is set to `all`.", + "https://developer.mozilla.org/docs/Web/CSS/column-width": "The **`column-width`** CSS property sets the ideal column width in a multi-column layout. The container will have as many columns as can fit without any of them having a width less than the `column-width` value. If the width of the container is narrower than the specified value, the single column's width will be smaller than the declared column width.", + "https://developer.mozilla.org/docs/Web/CSS/columns": "The **`columns`** CSS shorthand property sets the number of columns to use when drawing an element's contents, as well as those columns' widths.", + "https://developer.mozilla.org/docs/Web/CSS/contain": "The **`contain`** CSS property allows an author to indicate that an element and its contents are, as much as possible, _independent_ of the rest of the document tree. This allows the browser to recalculate layout, style, paint, size, or any combination of them for a limited area of the DOM and not the entire page, leading to obvious performance benefits.", + "https://developer.mozilla.org/docs/Web/CSS/content": "The **`content`** CSS property replaces an element with a generated value. Objects inserted using the `content` property are **anonymous replaced elements**_._", + "https://developer.mozilla.org/docs/Web/CSS/counter-increment": "The **`counter-increment`** CSS property increases or decreases the value of a CSS counter by a given value.", + "https://developer.mozilla.org/docs/Web/CSS/counter-reset": "The **`counter-reset`** CSS property resets a CSS counter to a given value.", + "https://developer.mozilla.org/docs/Web/CSS/counter-set": "The **`counter-set`** CSS property sets a CSS counter to a given value. It manipulates the value of existing counters, and will only create new counters if there isn't already a counter of the given name on the element.", + "https://developer.mozilla.org/docs/Web/CSS/cursor": "The **`cursor`** CSS property sets the type of mouse cursor, if any, to show when the mouse pointer is over an element.", + "https://developer.mozilla.org/docs/Web/CSS/direction": "The **`direction`** CSS property sets the direction of text, table columns, and horizontal overflow. Use `rtl` for languages written from right to left (like Hebrew or Arabic), and `ltr` for those written from left to right (like English and most other languages).", + "https://developer.mozilla.org/docs/Web/CSS/display": "The **`display`** CSS property sets whether an element is treated as a block or inline element and the layout used for its children, such as flow layout, grid or flex.", + "https://developer.mozilla.org/docs/Web/CSS/empty-cells": "The **`empty-cells`** CSS property sets whether borders and backgrounds appear around `` cells that have no visible content.", + "https://developer.mozilla.org/docs/Web/CSS/filter": "The **`filter`** CSS property applies graphical effects like blur or color shift to an element. Filters are commonly used to adjust the rendering of images, backgrounds, and borders.", + "https://developer.mozilla.org/docs/Web/CSS/flex": "The **`flex`** CSS shorthand property sets how a flex _item_ will grow or shrink to fit the space available in its flex container.", + "https://developer.mozilla.org/docs/Web/CSS/flex-basis": "The **`flex-basis`** CSS property sets the initial main size of a flex item. It sets the size of the content box unless otherwise set with `box-sizing`.", + "https://developer.mozilla.org/docs/Web/CSS/flex-direction": "The **`flex-direction`** CSS property sets how flex items are placed in the flex container defining the main axis and the direction (normal or reversed).", + "https://developer.mozilla.org/docs/Web/CSS/flex-flow": "The **`flex-flow`** CSS shorthand property specifies the direction of a flex container, as well as its wrapping behavior.", + "https://developer.mozilla.org/docs/Web/CSS/flex-grow": "The **`flex-grow`** CSS property sets the flex grow factor of a flex item main size.", + "https://developer.mozilla.org/docs/Web/CSS/flex-shrink": "The **`flex-shrink`** CSS property sets the flex shrink factor of a flex item. If the size of all flex items is larger than the flex container, items shrink to fit according to `flex-shrink`.", + "https://developer.mozilla.org/docs/Web/CSS/flex-wrap": "The **`flex-wrap`** CSS property sets whether flex items are forced onto one line or can wrap onto multiple lines. If wrapping is allowed, it sets the direction that lines are stacked.", + "https://developer.mozilla.org/docs/Web/CSS/float": "The **`float`** CSS property places an element on the left or right side of its container, allowing text and inline elements to wrap around it. The element is removed from the normal flow of the page, though still remaining a part of the flow (in contrast to absolute positioning).", + "https://developer.mozilla.org/docs/Web/CSS/font": "The **`font`** CSS shorthand property sets all the different properties of an element's font. Alternatively, it sets an element's font to a system font.", + "https://developer.mozilla.org/docs/Web/CSS/font-family": "The **`font-family`** CSS property specifies a prioritized list of one or more font family names and/or generic family names for the selected element.", + "https://developer.mozilla.org/docs/Web/CSS/font-feature-settings": "The **`font-feature-settings`** CSS property controls advanced typographic features in OpenType fonts.", + "https://developer.mozilla.org/docs/Web/CSS/font-kerning": "The **`font-kerning`** CSS property sets the use of the kerning information stored in a font.", + "https://developer.mozilla.org/docs/Web/CSS/font-language-override": "The **`font-language-override`** CSS property controls the use of language-specific glyphs in a typeface.", + "https://developer.mozilla.org/docs/Web/CSS/font-optical-sizing": "The **`font-optical-sizing`** CSS property sets whether text rendering is optimized for viewing at different sizes.", + "https://developer.mozilla.org/docs/Web/CSS/font-size": "The **`font-size`** CSS property sets the size of the font. Changing the font size also updates the sizes of the font size-relative `` units, such as `em`, `ex`, and so forth.", + "https://developer.mozilla.org/docs/Web/CSS/font-size-adjust": "The **`font-size-adjust`** CSS property sets the size of lower-case letters relative to the current font size (which defines the size of upper-case letters).", + "https://developer.mozilla.org/docs/Web/CSS/font-smooth": "The **`font-smooth`** CSS property controls the application of anti-aliasing when fonts are rendered.", + "https://developer.mozilla.org/docs/Web/CSS/font-stretch": "The **`font-stretch`** CSS property selects a normal, condensed, or expanded face from a font.", + "https://developer.mozilla.org/docs/Web/CSS/font-style": "The **`font-style`** CSS property sets whether a font should be styled with a normal, italic, or oblique face from its `font-family`.", + "https://developer.mozilla.org/docs/Web/CSS/font-synthesis": "The **`font-synthesis`** CSS property controls which missing typefaces, bold or italic, may be synthesized by the browser.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant": "The **font-variant** CSS shorthand property allows you to set all the font variants for a font.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-alternates": "The **`font-variant-alternates`** CSS property controls the usage of alternate glyphs. These alternate glyphs may be referenced by alternative names defined in `@font-feature-values`.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-caps": "The **`font-variant-caps`** CSS property controls the use of alternate glyphs for capital letters.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-east-asian": "The **`font-variant-east-asian`** CSS property controls the use of alternate glyphs for East Asian scripts, like Japanese and Chinese.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-ligatures": "The **`font-variant-ligatures`** CSS property controls which ligatures and contextual forms are used in textual content of the elements it applies to. This leads to more harmonized forms in the resulting text.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-numeric": "The **`font-variant-numeric`** CSS property controls the usage of alternate glyphs for numbers, fractions, and ordinal markers.", + "https://developer.mozilla.org/docs/Web/CSS/font-variant-position": "The **`font-variant-position`** CSS property controls the use of alternate, smaller glyphs that are positioned as superscript or subscript.", + "https://developer.mozilla.org/docs/Web/CSS/font-variation-settings": "The **`font-variation-settings`** CSS property provides low-level control over variable font characteristics, by specifying the four letter axis names of the characteristics you want to vary, along with their values.", + "https://developer.mozilla.org/docs/Web/CSS/font-weight": "The **`font-weight`** CSS property sets the weight (or boldness) of the font. The weights available depend on the `font-family` that is currently set.", + "https://developer.mozilla.org/docs/Web/CSS/gap": "The **`gap`** CSS property sets the gaps (gutters) between rows and columns. It is a shorthand for `row-gap` and `column-gap`.", + "https://developer.mozilla.org/docs/Web/CSS/grid": "The **`grid`** CSS property is a shorthand property that sets all of the explicit and implicit grid properties in a single declaration.", + "https://developer.mozilla.org/docs/Web/CSS/grid-area": "The **`grid-area`** CSS shorthand property specifies a grid item’s size and location within a grid by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the edges of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-auto-columns": "The **`grid-auto-columns`** CSS property specifies the size of an implicitly-created grid column track or pattern of tracks.", + "https://developer.mozilla.org/docs/Web/CSS/grid-auto-flow": "The **`grid-auto-flow`** CSS property controls how the auto-placement algorithm works, specifying exactly how auto-placed items get flowed into the grid.", + "https://developer.mozilla.org/docs/Web/CSS/grid-auto-rows": "The **`grid-auto-rows`** CSS property specifies the size of an implicitly-created grid row track or pattern of tracks.", + "https://developer.mozilla.org/docs/Web/CSS/grid-column": "The **`grid-column`** CSS shorthand property specifies a grid item's size and location within a grid column by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the inline-start and inline-end edge of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-column-end": "The **`grid-column-end`** CSS property specifies a grid item’s end position within the grid column by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the block-end edge of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-column-start": "The **`grid-column-start`** CSS property specifies a grid item’s start position within the grid column by contributing a line, a span, or nothing (automatic) to its grid placement. This start position defines the block-start edge of the grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-row": "The **`grid-row`** CSS shorthand property specifies a grid item’s size and location within the grid row by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the inline-start and inline-end edge of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-row-end": "The **`grid-row-end`** CSS property specifies a grid item’s end position within the grid row by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the inline-end edge of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-row-start": "The **`grid-row-start`** CSS property specifies a grid item’s start position within the grid row by contributing a line, a span, or nothing (automatic) to its grid placement, thereby specifying the inline-start edge of its grid area.", + "https://developer.mozilla.org/docs/Web/CSS/grid-template": "The **`grid-template`** CSS property is a shorthand property for defining grid columns, rows, and areas.", + "https://developer.mozilla.org/docs/Web/CSS/grid-template-areas": "The **`grid-template-areas`** CSS property specifies named grid areas, establishing the cells in the grid and assigning them names.", + "https://developer.mozilla.org/docs/Web/CSS/grid-template-columns": "The **`grid-template-columns`** CSS property defines the line names and track sizing functions of the grid columns.", + "https://developer.mozilla.org/docs/Web/CSS/grid-template-rows": "The **`grid-template-rows`** CSS property defines the line names and track sizing functions of the grid rows.", + "https://developer.mozilla.org/docs/Web/CSS/hanging-punctuation": "The **`hanging-punctuation`** CSS property specifies whether a punctuation mark should hang at the start or end of a line of text. Hanging punctuation may be placed outside the line box.", + "https://developer.mozilla.org/docs/Web/CSS/height": "The **`height`** CSS property specifies the height of an element. By default, the property defines the height of the content area. If `box-sizing` is set to `border-box`, however, it instead determines the height of the border area.", + "https://developer.mozilla.org/docs/Web/CSS/hyphens": "The **`hyphens`** CSS property specifies how words should be hyphenated when text wraps across multiple lines. It can prevent hyphenation entirely, hyphenate at manually-specified points within the text, or let the browser automatically insert hyphens where appropriate.", + "https://developer.mozilla.org/docs/Web/CSS/image-orientation": "The **`image-orientation`** CSS property specifies a layout-independent correction to the orientation of an image. It should _not_ be used for any other orientation adjustments; instead, the `transform` property should be used with the `rotate` ``.", + "https://developer.mozilla.org/docs/Web/CSS/image-rendering": "The **`image-rendering`** CSS property sets an image scaling algorithm. The property applies to an element itself, to any images set in its other properties, and to its descendants.", + "https://developer.mozilla.org/docs/Web/CSS/ime-mode": "The **`ime-mode`** CSS property controls the state of the input method editor (IME) for text fields. This property is obsolete.", + "https://developer.mozilla.org/docs/Web/CSS/initial-letter": "The `initial-letter` CSS property sets styling for dropped, raised, and sunken initial letters.", + "https://developer.mozilla.org/docs/Web/CSS/inline-size": "The **`inline-size`** CSS property defines the horizontal or vertical size of an element's block, depending on its writing mode. It corresponds to either the `width` or the `height` property, depending on the value of `writing-mode`.", + "https://developer.mozilla.org/docs/Web/CSS/inset": "The **`inset`** CSS property, though part of the logical specification, doesn't define logical block or inline offsets, and instead defines physical offsets, regardless of the element's writing mode, directionality, and text orientation. It has the same multi-value syntax of the `margin` shorthand. It is a shorthand that corresponds to the `top`, `right`, `bottom`, and/or `left` properties.", + "https://developer.mozilla.org/docs/Web/CSS/inset-block": "The **`inset-block`** CSS property defines the logical block start and end offsets of an element, which maps to physical offsets depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top` and `bottom`, or `right` and `left` properties depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/inset-block-end": "The **`inset-block-end`** CSS property defines the logical block end offset of an element, which maps to a physical inset depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top`, `right`, `bottom`, or `left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/inset-block-start": "The **`inset-block-start`** CSS property defines the logical block start offset of an element, which maps to a physical inset depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top`, `right`, `bottom`, or `left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/inset-inline": "The **`inset-inline`** CSS property defines the logical start and end offsets of an element in the inline direction, which maps to physical offsets depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top` and `bottom`, or `right` and `left` properties depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/inset-inline-end": "The **`inset-inline-end`** CSS property defines the logical inline end inset of an element, which maps to a physical inset depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top`, `right`, `bottom`, or `left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/inset-inline-start": "The **`inset-inline-start`** CSS property defines the logical inline start inset of an element, which maps to a physical offset depending on the element's writing mode, directionality, and text orientation. It corresponds to the `top`, `right`, `bottom`, or `left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/isolation": "The **`isolation`** CSS property determines whether an element must create a new stacking context.", + "https://developer.mozilla.org/docs/Web/CSS/justify-content": "The CSS **`justify-content`** property defines how the browser distributes space between and around content items along the main-axis of a flex container, and the inline axis of a grid container.", + "https://developer.mozilla.org/docs/Web/CSS/justify-items": "The CSS **`justify-items`** property defines the default `justify-self` for all items of the box, giving them all a default way of justifying each box along the appropriate axis.", + "https://developer.mozilla.org/docs/Web/CSS/justify-self": "The CSS **`justify-self`** property sets the way a box is justified inside its alignment container along the appropriate axis.", + "https://developer.mozilla.org/docs/Web/CSS/left": "The **`left`** CSS property participates in specifying the horizontal position of a positioned element. It has no effect on non-positioned elements.", + "https://developer.mozilla.org/docs/Web/CSS/letter-spacing": "The **`letter-spacing`** CSS property sets the horizontal spacing behavior between text characters. This value is added to the natural spacing between characters while rendering the text. Positive values of `letter-spacing` causes characters to spread farther apart, while negative values of `letter-spacing` bring characters closer together.", + "https://developer.mozilla.org/docs/Web/CSS/line-break": "The **`line-break`** CSS property sets how to break lines of Chinese, Japanese, or Korean (CJK) text when working with punctuation and symbols.", + "https://developer.mozilla.org/docs/Web/CSS/line-height": "The **`line-height`** CSS property sets the height of a line box. It's commonly used to set the distance between lines of text. On block-level elements, it specifies the minimum height of line boxes within the element. On non-replaced inline elements, it specifies the height that is used to calculate line box height.", + "https://developer.mozilla.org/docs/Web/CSS/line-height-step": "The **`line-height-step`** CSS property sets the step unit for line box heights. When the property is set, line box heights are rounded up to the closest multiple of the unit.", + "https://developer.mozilla.org/docs/Web/CSS/list-style": "The **`list-style`** CSS shorthand property allows you set all the list style properties at once.", + "https://developer.mozilla.org/docs/Web/CSS/list-style-image": "The **`list-style-image`** CSS property sets an image to be used as the list item marker.", + "https://developer.mozilla.org/docs/Web/CSS/list-style-position": "The **`list-style-position`** CSS property sets the position of the `::marker` relative to a list item.", + "https://developer.mozilla.org/docs/Web/CSS/list-style-type": "The **`list-style-type`** CSS property sets the marker (such as a disc, character, or custom counter style) of a list item element.", + "https://developer.mozilla.org/docs/Web/CSS/margin": "The **`margin`** CSS property sets the margin area on all four sides of an element. It is a shorthand for `margin-top`, `margin-right`, `margin-bottom`, and `margin-left`.", + "https://developer.mozilla.org/docs/Web/CSS/margin-block": "The **`margin-block`** CSS shorthand property defines the logical block start and end margins of an element, which maps to physical margins depending on the element's writing mode, directionality, and text orientation.", + "https://developer.mozilla.org/docs/Web/CSS/margin-block-end": "The **`margin-block-end`** CSS property defines the logical block end margin of an element, which maps to a physical margin depending on the element's writing mode, directionality, and text orientation.", + "https://developer.mozilla.org/docs/Web/CSS/margin-block-start": "The **`margin-block-start`** CSS property defines the logical block start margin of an element, which maps to a physical margin depending on the element's writing mode, directionality, and text orientation.", + "https://developer.mozilla.org/docs/Web/CSS/margin-bottom": "The **`margin-bottom`** CSS property sets the margin area on the bottom of an element. A positive value places it farther from its neighbors, while a negative value places it closer.", + "https://developer.mozilla.org/docs/Web/CSS/margin-inline": "The **`margin-inline`** CSS shorthand property is a shorthand property that defines both the logical inline start and end margins of an element, which maps to physical margins depending on the element's writing mode, directionality, and text orientation.", + "https://developer.mozilla.org/docs/Web/CSS/margin-inline-end": "The **`margin-inline-end`** CSS property defines the logical inline end margin of an element, which maps to a physical margin depending on the element's writing mode, directionality, and text orientation. In other words, it corresponds to the `margin-top`, `margin-right`, `margin-bottom` or `margin-left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/margin-inline-start": "The **`margin-inline-start`** CSS property defines the logical inline start margin of an element, which maps to a physical margin depending on the element's writing mode, directionality, and text orientation. It corresponds to the `margin-top`, `margin-right`, `margin-bottom`, or `margin-left` property depending on the values defined for `writing-mode`, `direction`, and `text-orientation`.", + "https://developer.mozilla.org/docs/Web/CSS/margin-left": "The **`margin-left`** CSS property sets the margin area on the left side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.", + "https://developer.mozilla.org/docs/Web/CSS/margin-right": "The **`margin-right`** CSS property sets the margin area on the right side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.", + "https://developer.mozilla.org/docs/Web/CSS/margin-top": "The **`margin-top`** CSS property sets the margin area on the top of an element. A positive value places it farther from its neighbors, while a negative value places it closer.", + "https://developer.mozilla.org/docs/Web/CSS/mask": "The **`mask`** CSS shorthand property hides an element (partially or fully) by masking or clipping the image at specific points.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border": "The **`mask-border`** CSS shorthand property lets you create a mask along the edge of an element's border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-mode": "The **`mask-border-mode`** CSS property specifies the blending mode used in a mask border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-outset": "The **`mask-border-outset`** CSS property specifies the distance by which an element's mask border is set out from its border box.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-repeat": "The **`mask-border-repeat`** CSS property sets how the edge regions of a source image are adjusted to fit the dimensions of an element's mask border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-slice": "The **`mask-border-slice`** CSS property divides the image set by `mask-border-source` into regions. These regions are used to form the components of an element's mask border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-source": "The **`mask-border-source`** CSS property sets the source image used to create an element's mask border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-border-width": "The **`mask-border-width`** CSS property sets the width of an element's mask border.", + "https://developer.mozilla.org/docs/Web/CSS/mask-clip": "The **`mask-clip`** CSS property determines the area which is affected by a mask. The painted content of an element must be restricted to this area.", + "https://developer.mozilla.org/docs/Web/CSS/mask-composite": "The **`mask-composite`** CSS property represents a compositing operation used on the current mask layer with the mask layers below it.", + "https://developer.mozilla.org/docs/Web/CSS/mask-image": "The **`mask-image`** CSS property sets the image that is used as mask layer for an element.", + "https://developer.mozilla.org/docs/Web/CSS/mask-mode": "The **`mask-mode`** CSS property sets whether the mask reference defined by `mask-image` is treated as a luminance or alpha mask.", + "https://developer.mozilla.org/docs/Web/CSS/mask-origin": "The **`mask-origin`** CSS property sets the origin of a mask.", + "https://developer.mozilla.org/docs/Web/CSS/mask-position": "The **`mask-position`** CSS property sets the initial position, relative to the mask position layer set by `mask-origin`, for each defined mask image.", + "https://developer.mozilla.org/docs/Web/CSS/mask-repeat": "The **`mask-repeat`** CSS property sets how mask images are repeated. A mask image can be repeated along the horizontal axis, the vertical axis, both axes, or not repeated at all.", + "https://developer.mozilla.org/docs/Web/CSS/mask-size": "The **`mask-size`** CSS property specifies the sizes of the mask images. The size of the image can be fully or partially constrained in order to preserve its intrinsic ratio.", + "https://developer.mozilla.org/docs/Web/CSS/mask-type": "The **`mask-type`** CSS property sets whether an SVG `` element is used as a _luminance_ or an _alpha_ mask. It applies to the `` element itself.", + "https://developer.mozilla.org/docs/Web/CSS/max-block-size": "The `**max-block-size**` CSS property specifies the maximum size of an element in the direction opposite that of the writing direction as specified by `writing-mode`. That is, if the writing direction is horizontal, then `max-block-size` is equivalent to `max-height`; if the writing direction is vertical, `max-block-size` is the same as `max-width`.", + "https://developer.mozilla.org/docs/Web/CSS/max-height": "The **`max-height`** CSS property sets the maximum height of an element. It prevents the used value of the `height` property from becoming larger than the value specified for `max-height`.", + "https://developer.mozilla.org/docs/Web/CSS/max-inline-size": "The **`max-inline-size`** CSS property defines the horizontal or vertical maximum size of an element's block depending on its writing mode. It corresponds to the `max-width` or the `max-height` property depending on the value defined for `writing-mode`. If the writing mode is vertically oriented, the value of `max-inline-size` relates to the maximal height of the element, otherwise it relates to the maximal width of the element. It relates to `max-block-size`, which defines the other dimension of the element.", + "https://developer.mozilla.org/docs/Web/CSS/max-width": "The **`max-width`** CSS property sets the maximum width of an element. It prevents the used value of the `width` property from becoming larger than the value specified by `max-width`.", + "https://developer.mozilla.org/docs/Web/CSS/min-block-size": "The **`min-block-size`** CSS property defines the minimum horizontal or vertical size of an element's block, depending on its writing mode. It corresponds to either the `min-width` or the `min-height` property, depending on the value of `writing-mode`.", + "https://developer.mozilla.org/docs/Web/CSS/min-height": "The **`min-height`** CSS property sets the minimum height of an element. It prevents the used value of the `height` property from becoming smaller than the value specified for `min-height`.", + "https://developer.mozilla.org/docs/Web/CSS/min-inline-size": "The **`min-inline-size`** CSS property defines the horizontal or vertical minimal size of an element's block, depending on its writing mode. It corresponds to either the `min-width` or the `min-height` property, depending on the value of `writing-mode`.", + "https://developer.mozilla.org/docs/Web/CSS/min-width": "The **`min-width`** CSS property sets the minimum width of an element. It prevents the used value of the `width` property from becoming smaller than the value specified for `min-width`.", + "https://developer.mozilla.org/docs/Web/CSS/mix-blend-mode": "The **`mix-blend-mode`** CSS property sets how an element's content should blend with the content of the element's parent and the element's background.", + "https://developer.mozilla.org/docs/Web/CSS/object-fit": "The **`object-fit`** CSS property sets how the content of a replaced element, such as an `` or `
` cells, rows, and columns.", + "https://developer.mozilla.org/docs/Web/CSS/text-align": "The **`text-align`** CSS property sets the horizontal alignment of a block element or table-cell box. This means it works like `vertical-align` but in the horizontal direction.", + "https://developer.mozilla.org/docs/Web/CSS/text-align-last": "The **`text-align-last`** CSS property sets how the last line of a block or a line, right before a forced line break, is aligned.", + "https://developer.mozilla.org/docs/Web/CSS/text-combine-upright": "The **`text-combine-upright`** CSS property sets the combination of characters into the space of a single character. If the combined text is wider than 1em, the user agent must fit the contents within 1em. The resulting composition is treated as a single upright glyph for layout and decoration. This property only has an effect in vertical writing modes.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration": "The **`text-decoration`** shorthand CSS property sets the appearance of decorative lines on text. It is a shorthand for `text-decoration-line`, `text-decoration-color`, `text-decoration-style`, and the newer `text-decoration-thickness` property.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-color": "The **`text-decoration-color`** CSS property sets the color of decorations added to text by `text-decoration-line`.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-line": "The **`text-decoration-line`** CSS property sets the kind of decoration that is used on text in an element, such as an underline or overline.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-skip": "The **`text-decoration-skip`** CSS property sets what parts of an element’s content any text decoration affecting the element must skip over. It controls all text decoration lines drawn by the element and also any text decoration lines drawn by its ancestors.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-skip-ink": "The **`text-decoration-skip-ink`** CSS property specifies how overlines and underlines are drawn when they pass over glyph ascenders and descenders.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-style": "The **`text-decoration-style`** CSS property sets the style of the lines specified by `text-decoration-line`. The style applies to all lines that are set with `text-decoration-line`.", + "https://developer.mozilla.org/docs/Web/CSS/text-decoration-thickness": "The **`text-decoration-thickness`** CSS property sets the stroke thickness of the decoration line that is used on text in an element, such as a line-through, underline, or overline.", + "https://developer.mozilla.org/docs/Web/CSS/text-emphasis": "The **`text-emphasis`** CSS property applies emphasis marks to text (except spaces and control characters). It is a shorthand for `text-emphasis-style` and `text-emphasis-color`.", + "https://developer.mozilla.org/docs/Web/CSS/text-emphasis-color": "The **`text-emphasis-color`** CSS property sets the color of emphasis marks. This value can also be set using the `text-emphasis` shorthand.", + "https://developer.mozilla.org/docs/Web/CSS/text-emphasis-position": "The **`text-emphasis-position`** CSS property sets where emphasis marks are drawn. Like ruby text, if there isn't enough room for emphasis marks, the line height is increased.", + "https://developer.mozilla.org/docs/Web/CSS/text-emphasis-style": "The **`text-emphasis-style`** CSS property sets the appearance of emphasis marks. It can also be set, and reset, using the `text-emphasis` shorthand.", + "https://developer.mozilla.org/docs/Web/CSS/text-indent": "The **`text-indent`** CSS property sets the length of empty space (indentation) that is put before lines of text in a block.", + "https://developer.mozilla.org/docs/Web/CSS/text-justify": "The **`text-justify`** CSS property sets what type of justification should be applied to text when `text-align``: justify;` is set on an element.", + "https://developer.mozilla.org/docs/Web/CSS/text-orientation": "The **`text-orientation`** CSS property sets the orientation of the text characters in a line. It only affects text in vertical mode (when `writing-mode` is not `horizontal-tb`). It is useful for controlling the display of languages that use vertical script, and also for making vertical table headers.", + "https://developer.mozilla.org/docs/Web/CSS/text-overflow": "The **`text-overflow`** CSS property sets how hidden overflow content is signaled to users. It can be clipped, display an ellipsis ('`…`'), or display a custom string.", + "https://developer.mozilla.org/docs/Web/CSS/text-rendering": "The **`text-rendering`** CSS property provides information to the rendering engine about what to optimize for when rendering text.", + "https://developer.mozilla.org/docs/Web/CSS/text-shadow": "The **`text-shadow`** CSS property adds shadows to text. It accepts a comma-separated list of shadows to be applied to the text and any of its `decorations`. Each shadow is described by some combination of X and Y offsets from the element, blur radius, and color.", + "https://developer.mozilla.org/docs/Web/CSS/text-size-adjust": "The **`text-size-adjust`** CSS property controls the text inflation algorithm used on some smartphones and tablets. Other browsers will ignore this property.", + "https://developer.mozilla.org/docs/Web/CSS/text-transform": "The **`text-transform`** CSS property specifies how to capitalize an element's text. It can be used to make text appear in all-uppercase or all-lowercase, or with each word capitalized. It also can help improve legibility for ruby.", + "https://developer.mozilla.org/docs/Web/CSS/text-underline-offset": "The **`text-underline-offset`** CSS property sets the offset distance of an underline text decoration line (applied using `text-decoration`) from its original position.", + "https://developer.mozilla.org/docs/Web/CSS/text-underline-position": "The **`text-underline-position`** CSS property specifies the position of the underline which is set using the `text-decoration` property's `underline` value.", + "https://developer.mozilla.org/docs/Web/CSS/top": "The **`top`** CSS property participates in specifying the vertical position of a positioned element. It has no effect on non-positioned elements.", + "https://developer.mozilla.org/docs/Web/CSS/touch-action": "The **`touch-action`** CSS property sets how an element's region can be manipulated by a touchscreen user (for example, by zooming features built into the browser).", + "https://developer.mozilla.org/docs/Web/CSS/transform": "The **`transform`** CSS property lets you rotate, scale, skew, or translate an element. It modifies the coordinate space of the CSS visual formatting model.", + "https://developer.mozilla.org/docs/Web/CSS/transform-box": "The **`transform-box`** CSS property defines the layout box to which the `transform` and `transform-origin` properties relate.", + "https://developer.mozilla.org/docs/Web/CSS/transform-origin": "The **`transform-origin`** CSS property sets the origin for an element's transformations.", + "https://developer.mozilla.org/docs/Web/CSS/transform-style": "The **`transform-style`** CSS property sets whether children of an element are positioned in the 3D space or are flattened in the plane of the element.", + "https://developer.mozilla.org/docs/Web/CSS/transition": "The **`transition`** CSS property is a shorthand property for `transition-property`, `transition-duration`, `transition-timing-function`, and `transition-delay`.", + "https://developer.mozilla.org/docs/Web/CSS/transition-delay": "The **`transition-delay`** CSS property specifies the duration to wait before starting a property's transition effect when its value changes.", + "https://developer.mozilla.org/docs/Web/CSS/transition-duration": "The **`transition-duration`** CSS property sets the length of time a transition animation should take to complete. By default, the value is `0s`, meaning that no animation will occur.", + "https://developer.mozilla.org/docs/Web/CSS/transition-property": "The **`transition-property`** CSS property sets the CSS properties to which a transition effect should be applied.", + "https://developer.mozilla.org/docs/Web/CSS/transition-timing-function": "The **`transition-timing-function`** CSS property sets how intermediate values are calculated for CSS properties being affected by a transition effect.", + "https://developer.mozilla.org/docs/Web/CSS/translate": "The **`translate`** CSS property allows you to specify translation transforms individually and independently of the `transform` property. This maps better to typical user interface usage, and saves having to remember the exact order of transform functions to specify in the `transform` value.", + "https://developer.mozilla.org/docs/Web/CSS/unicode-bidi": "The **`unicode-bidi`** CSS property, together with the `direction` property, determines how bidirectional text in a document is handled. For example, if a block of content contains both left-to-right and right-to-left text, the user-agent uses a complex Unicode algorithm to decide how to display the text. The `unicode-bidi` property overrides this algorithm and allows the developer to control the text embedding.", + "https://developer.mozilla.org/docs/Web/CSS/user-select": "The `**user-select**` CSS property controls whether the user can select text. This doesn't have any effect on content loaded as chrome, except in textboxes.", + "https://developer.mozilla.org/docs/Web/CSS/vertical-align": "The **`vertical-align`** CSS property sets vertical alignment of an inline, inline-block or table-cell box.", + "https://developer.mozilla.org/docs/Web/CSS/visibility": "The **`visibility`** CSS property shows or hides an element without changing the layout of a document. The property can also hide rows or columns in a `
`.", + "https://developer.mozilla.org/docs/Web/CSS/white-space": "The **`white-space`** CSS property sets how white space inside an element is handled.", + "https://developer.mozilla.org/docs/Web/CSS/widows": "The **`widows`** CSS property sets the minimum number of lines in a block container that must be shown at the _top_ of a page, region, or column.", + "https://developer.mozilla.org/docs/Web/CSS/width": "The **`width`** CSS property sets an element's width. By default, it sets the width of the content area, but if `box-sizing` is set to `border-box`, it sets the width of the border area.", + "https://developer.mozilla.org/docs/Web/CSS/will-change": "The **`will-change`** CSS property hints to browsers how an element is expected to change. Browsers may set up optimizations before an element is actually changed. These kinds of optimizations can increase the responsiveness of a page by doing potentially expensive work before they are actually required.", + "https://developer.mozilla.org/docs/Web/CSS/word-break": "The **`word-break`** CSS property sets whether line breaks appear wherever the text would otherwise overflow its content box.", + "https://developer.mozilla.org/docs/Web/CSS/word-spacing": "The **`word-spacing`** CSS property sets the length of space between words and between tags.", + "https://developer.mozilla.org/docs/Web/CSS/writing-mode": "The **`writing-mode`** CSS property sets whether lines of text are laid out horizontally or vertically, as well as the direction in which blocks progress. When set for an entire document, it should be set on the root element (`html` element for HTML documents).", + "https://developer.mozilla.org/docs/Web/CSS/z-index": "The **`z-index`** CSS property sets the z-order of a positioned element and its descendants or flex items. Overlapping elements with a larger z-index cover those with a smaller one.", + "https://developer.mozilla.org/docs/Web/CSS/zoom": "The non-standard **`zoom`** CSS property can be used to control the magnification level of an element. `transform: scale()` should be used instead of this property, if possible. However, unlike CSS Transforms, `zoom` affects the layout size of the element." +} \ No newline at end of file diff --git a/packages/tacky-css/csstype/declarator.ts b/packages/tacky-css/csstype/declarator.ts new file mode 100644 index 0000000..354ebde --- /dev/null +++ b/packages/tacky-css/csstype/declarator.ts @@ -0,0 +1,467 @@ +import { getDataTypesOf } from "./collections/data-types"; +import { + getGlobals, + getHtmlProperties, + getSvgProperties, + isVendorProperty, +} from "./collections/properties"; +import { IDataType, Type, TypeType } from "./syntax/typer"; +import { toCamelCase, toPascalCase, toVendorPrefixCase } from "./utils/casing"; + +export interface ArgType { + name: string; + types: DeclarableType[]; +} + +export interface IFunction { + args: ArgType[]; + return: string; +} + +export interface IArray { + type: Type.Array; + of: DeclarableType; +} + +export interface IAlias { + type: Type.Alias; + name: string; + generics: IGenerics[]; + namespace: INamespace | undefined; +} + +export interface IGenerics { + name: string; + defaults?: SimpleType[]; + extend?: string; +} + +export type Interface = IInterfaceProperties; + +export interface IInterfaceProperties { + name: string; + generics: IGenerics[]; + extends: Interface[]; + export: boolean; + properties: PropertyType[]; + comment: () => Promise; +} + +export function isInterface( + value: Interface | IDeclaration | EmptyEnum +): value is Interface { + return !!(value as IInterfaceProperties).properties; +} + +export function isEmptyEnum( + value: Interface | IDeclaration | EmptyEnum +): value is EmptyEnum { + return (value as EmptyEnum).type === "enum"; +} + +export function isInterfaceProperties( + value: Interface +): value is IInterfaceProperties { + return !!(value as IInterfaceProperties).properties; +} + +export function isInterfaceFunction(value: PropertyType): value is IFunction { + return !!(value as IFunction).args; +} + +interface IPropertyAlias { + name: string; + generics: IGenerics[]; + alias: IAlias; + comment: () => Promise; + namespace: INamespace | undefined; +} + +export interface IPropertyType { + name: string; + type: DeclarableType; + comment: () => Promise; +} + +export type PropertyType = IPropertyAlias | IPropertyType | IFunction; + +type MixedType = TypeType | IAlias> | IArray; +export type DeclarableType = TypeType | IArray; +export type SimpleType = Exclude; + +export interface INamespace { + name: string; + export: boolean; + body: () => (Interface | IDeclaration | EmptyEnum)[]; +} + +export interface IDeclaration { + name: string; + export: boolean; + types: DeclarableType[]; + generics: IGenerics[]; + namespace: INamespace | undefined; + comment?: () => Promise; +} + +export interface EmptyEnum { + type: "enum"; + name: string; +} + +export const lengthGeneric: IGenerics = { + name: "TLength", + defaults: [{ type: Type.String }, { type: Type.NumericLiteral, literal: 0 }], +}; + +export const timeGeneric: IGenerics = { + name: "TTime", + defaults: [{ type: Type.String }], +}; + +export async function declarator(minTypesInDataTypes: number) { + const [ + dataTypes, + [htmlProperties, svgProperties, globals], + ] = await getDataTypesOf(dictionary => + Promise.all([ + getHtmlProperties(dictionary, minTypesInDataTypes), + getSvgProperties(dictionary, minTypesInDataTypes), + getGlobals(dictionary, minTypesInDataTypes), + ]) + ); + + debugger; + + function getGenericsFrom(types: MixedType[]): IGenerics[] { + let hasLength = false; + let hasTime = false; + + const hasGeneric = (type: MixedType) => { + switch (type.type) { + case Type.Length: + hasLength = true; + break; + case Type.Time: + hasTime = true; + break; + case Type.DataType: + if (type.name in dataTypes) { + dataTypes[type.name].forEach(hasGeneric); + } else { + console.error("DATATYPE MISSING: ", type.name); + } + break; + } + }; + + types.forEach(hasGeneric); + + const generics: IGenerics[] = []; + + // if (hasLength) { + // generics.push(lengthGeneric); + // } + + // if (hasTime) { + // generics.push(timeGeneric); + // } + + return generics; + } + + function alias( + name: string, + generics: IGenerics[] = [], + namespace?: INamespace + ): IAlias { + return { + type: Type.Alias, + name, + generics, + namespace, + }; + } + + function aliasOf({ name, generics, namespace }: IDeclaration): IAlias { + return alias(name, generics, namespace); + } + + function declarable(types: MixedType[]): DeclarableType[] { + const dataTypeToAlias = (type: IDataType) => + type.name in dataTypes + ? alias( + toPascalCase(type.name), + getGenericsFrom(dataTypes[type.name]), + dataTypeNamespace + ) + : alias(toPascalCase(type.name)); + + return types.sort(sorter).map(type => { + if (type.type === Type.DataType) { + return dataTypeToAlias(type); + } + + return type; + }); + } + + const globalDeclarations: Map = new Map(); + + function declarationNameExists( + map: Map, + name: string + ): boolean { + for (const declaration of map.values()) { + if (declaration.name === name) { + return true; + } + } + + return false; + } + + const globalsDeclaration: IDeclaration = { + name: "Globals", + export: true, + types: declarable(globals), + generics: [], + namespace: undefined, + }; + + globalDeclarations.set(globals, globalsDeclaration); + + const propertyDeclarations: Map = new Map(); + const valueEnumDeclarations: EmptyEnum[] = []; + const dataTypeDeclarations: Map = new Map(); + const manualDataTypeDeclarations = new Map< + string, + [Interface, IDeclaration] + >(); + + const propertyNamespace: INamespace = { + name: "Property", + export: true, + body: () => Array.from(propertyDeclarations.values()), + }; + const valueEnumNamespace: INamespace = { + name: "ValueEnum", + export: false, + body: () => valueEnumDeclarations, + }; + const dataTypeNamespace: INamespace = { + name: "DataType", + export: false, + body: () => Array.from(dataTypeDeclarations.values()), + }; + const manualDataTypeNamespace: INamespace = { + name: "ManualDataType", + export: true, + body: () => Array.from(manualDataTypeDeclarations.values()).flat(), + }; + + const valueInterface: Interface = { + name: "Values", + export: true, + generics: [], + extends: [], + comment: async () => undefined, + properties: [], + }; + + const extra: string[] = []; + + for (const properties of [htmlProperties, svgProperties]) { + // Sort alphabetical, starting with standard properties + const propertyNames = ([] as string[]).concat( + Object.keys(properties) + .filter(name => name[0] !== "-") + .sort(), + Object.keys(properties) + .filter(name => name[0] === "-") + .sort() + ); + + for (const name of propertyNames) { + const property = properties[name]; + + if (property.obsolete) { + // Obsolete properties are unsupported + continue; + } + + if (property.name === "default") { + // what is this and where does it come from? + continue; + } + + const generics = getGenericsFrom(property.types); + + const validTypes = property.types.filter( + ({ type }) => ![Type.String, Type.Length, Type.Time].includes(type) + ); + + // Some properties are prefixed and share the same type so we + // make sure to reuse the same declaration of that type + let declaration = propertyDeclarations.get(validTypes); + + if (!declaration) { + const realName = property.vendor + ? toVendorPrefixCase(name) + : toCamelCase(name); + const declarationName = toPascalCase(name); + + const returnType = `PropertyTuple<"${realName}">`; + + const properties = []; + + if (validTypes.length > 0) { + properties.push({ + args: [{ name: "global", types: [aliasOf(globalsDeclaration)] }], + return: returnType, + }); + + properties.push({ + args: [{ name: "value", types: declarable(validTypes) }], + return: returnType, + }); + } + + declaration = { + name: declarationName, + export: true, + // types: declarable(property.types), + // types: [aliasOf(globalsDeclaration), ...declarable(property.types)], + generics, + // namespace: propertyNamespace, + comment: property.comment, + extends: [], + properties, + }; + + // Some SVG properties are shared with regular style properties + // and we assume here that they are identical + if (!declarationNameExists(propertyDeclarations, declarationName)) { + propertyDeclarations.set(validTypes, declaration); + + valueEnumDeclarations.push({ + type: "enum", + name: declarationName, + }); + + if (validTypes.length === property.types.length) { + extra.push( + `export const ${realName}: Property.${declarationName} = (arg: unknown) => + ["${realName}", arg as Values["${realName}"]] as const;` + ); + } + + valueInterface.properties.push({ + name: realName, + comment: async () => undefined, + generics: [], + namespace: undefined, + alias: aliasOf({ + export: false, + types: [], + generics: [], + name: declarationName, + namespace: valueEnumNamespace, + }), + }); + } + } + } + } + + // Loop in alphabetical order + for (const name of Object.keys(dataTypes).sort()) { + const declarableDataType = declarable(dataTypes[name]); + + for (const [index, type] of declarableDataType.entries()) { + if (type.type === Type.String) { + let group = manualDataTypeDeclarations.get("name"); + if (!group) { + const newInterface = { + name: "I" + toPascalCase(name), + export: true, + properties: [], + generics: [], + extends: [], + comment: async () => undefined, + }; + + const alias: IAlias = { + type: Type.Alias, + name: "keyof I" + toPascalCase(name), + generics: [], + namespace: manualDataTypeNamespace, + }; + + const newType = { + name: toPascalCase(name), + export: true, + generics: [], + namespace: manualDataTypeNamespace, + types: [alias], + }; + + group = [newInterface, newType]; + + manualDataTypeDeclarations.set(name, group); + } + + declarableDataType[index] = { + type: Type.Alias, + name: group[1].name, + generics: [], + namespace: manualDataTypeNamespace, + }; + } + } + + dataTypeDeclarations.set(declarableDataType, { + name: toPascalCase(name), + export: false, + types: declarableDataType, + generics: getGenericsFrom(dataTypes[name]), + namespace: dataTypeNamespace, + }); + } + + const namespaces: INamespace[] = [ + propertyNamespace, + manualDataTypeNamespace, + dataTypeNamespace, + valueEnumNamespace, + ]; + + return { + declarations: Array.from(globalDeclarations.values()), + interfaces: [valueInterface], + namespaces, + extra, + }; +} + +export function isAliasProperty(value: PropertyType): value is IPropertyAlias { + return "alias" in value; +} + +function sorter(a: MixedType, b: MixedType) { + if (a.type === Type.StringLiteral && b.type === Type.StringLiteral) { + return a.literal < b.literal ? -1 : a.literal > b.literal ? 1 : 0; + } + if (a.type === Type.NumericLiteral && b.type === Type.NumericLiteral) { + return a.literal - b.literal; + } + return a.type - b.type; +} + +function onlyContainsString(types: MixedType[]): boolean { + return types.length === 1 && types[0].type === Type.String; +} + +function onlyContainsNumber(types: MixedType[]): boolean { + return types.length === 1 && types[0].type === Type.Number; +} diff --git a/packages/tacky-css/csstype/syntax/parser.ts b/packages/tacky-css/csstype/syntax/parser.ts new file mode 100644 index 0000000..13cbde0 --- /dev/null +++ b/packages/tacky-css/csstype/syntax/parser.ts @@ -0,0 +1,319 @@ +export enum Entity { + Component, + Combinator, + Function, + Unknown, +} + +export enum Component { + Keyword, + DataType, + Group, +} + +// Higher number is higher precedence +export enum Combinator { + /** Components are mandatory and should appear in that order */ + Juxtaposition = 0, + /** Components are mandatory but may appear in any order */ + DoubleAmpersand = 1, + /** At least one of the components must be present, and they may appear in any order */ + DoubleBar = 2, + /** Exactly one of the components must be present */ + SingleBar = 3, +} + +export enum Multiplier { + /** 0 or more times */ + Asterisk, + /** 1 or more times */ + PlusSign, + /** 0 or 1 time (that is optional) */ + QuestionMark, + /** 1 or more times, but each occurrence separated by a comma (',') */ + HashMark, + /** Group must produce at least 1 value */ + ExclamationPoint, + /** At least A times, at most B times */ + CurlyBracet, +} + +export interface IMultiplierCurlyBracet { + sign: Multiplier.CurlyBracet; + min: number; + max: number; +} + +export interface IMultiplierSimple { + sign: + | Multiplier.Asterisk + | Multiplier.PlusSign + | Multiplier.QuestionMark + | Multiplier.HashMark + | Multiplier.ExclamationPoint; +} + +export type MultiplierType = IMultiplierCurlyBracet | IMultiplierSimple; + +export interface INonGroupData { + entity: Entity.Component; + multiplier: MultiplierType | null; + component: Component.Keyword | Component.DataType; + value: string; +} + +export interface IGroupData { + entity: Entity.Component; + multiplier: MultiplierType | null; + component: Component.Group; + entities: EntityType[]; +} + +export type ComponentType = INonGroupData | IGroupData; + +export interface ICombinator { + entity: Entity.Combinator; + combinator: Combinator; +} + +export interface IFunction { + entity: Entity.Function; + multiplier: MultiplierType | null; +} + +export interface IUnknown { + entity: Entity.Unknown; + multiplier: MultiplierType | null; +} + +export type EntityType = ComponentType | ICombinator | IFunction | IUnknown; + +const REGEX_ENTITY = /(?:^|\s)((?:<[^>]+>)|(?:[\w]+\([^\)]*\))|[^\s*+?#!{]+)([*+?#!]|{(\d+),(\d+)})?/g; +const REGEX_DATA_TYPE = /^(<[^>]+>)/g; +const REGEX_KEYWORD = /^([\w-]+)/g; + +export const combinators: { [key: number]: ICombinator } = { + [Combinator.Juxtaposition]: { + entity: Entity.Combinator, + combinator: Combinator.Juxtaposition, + }, + [Combinator.DoubleAmpersand]: { + entity: Entity.Combinator, + combinator: Combinator.DoubleAmpersand, + }, + [Combinator.DoubleBar]: { + entity: Entity.Combinator, + combinator: Combinator.DoubleBar, + }, + [Combinator.SingleBar]: { + entity: Entity.Combinator, + combinator: Combinator.SingleBar, + }, +}; + +export default function parse(syntax: string): EntityType[] { + const levels: EntityType[][] = [[]]; + let previousMatchWasComponent = false; + + let entityMatch: RegExpExecArray | null; + while ((entityMatch = REGEX_ENTITY.exec(syntax))) { + const [, value, ...rawMultiplier] = entityMatch; + if (value.indexOf('(') !== -1) { + deepestLevel().push({ entity: Entity.Function, multiplier: multiplierData(rawMultiplier) }); + previousMatchWasComponent = false; + continue; + } else if (value.indexOf('&&') === 0) { + deepestLevel().push(combinators[Combinator.DoubleAmpersand]); + previousMatchWasComponent = false; + continue; + } else if (value.indexOf('||') === 0) { + deepestLevel().push(combinators[Combinator.DoubleBar]); + previousMatchWasComponent = false; + continue; + } else if (value.indexOf('|') === 0) { + deepestLevel().push(combinators[Combinator.SingleBar]); + previousMatchWasComponent = false; + continue; + } else if (value.indexOf(']') === 0) { + const definitions = levels.pop(); + if (definitions) { + deepestLevel().push(componentGroupData(groupByPrecedence(definitions), multiplierData(rawMultiplier))); + } + previousMatchWasComponent = true; + continue; + } else { + if (previousMatchWasComponent) { + deepestLevel().push(combinators[Combinator.Juxtaposition]); + } + + if (value.indexOf('[') === 0) { + levels.push([]); + previousMatchWasComponent = false; + continue; + } + + let componentMatch: RegExpMatchArray | null; + if ((componentMatch = value.match(REGEX_DATA_TYPE))) { + const name = componentMatch[0]; + deepestLevel().push(componentData(Component.DataType, name, multiplierData(rawMultiplier))); + previousMatchWasComponent = true; + continue; + } else if ((componentMatch = value.match(REGEX_KEYWORD))) { + const name = componentMatch[0]; + deepestLevel().push(componentData(Component.Keyword, name, multiplierData(rawMultiplier))); + previousMatchWasComponent = true; + continue; + } + } + deepestLevel().push({ entity: Entity.Unknown, multiplier: multiplierData(rawMultiplier) }); + } + + function deepestLevel() { + return levels[levels.length - 1]; + } + + return groupByPrecedence(levels[0]); +} + +export function isComponent(entity: EntityType): entity is ComponentType { + return entity.entity === Entity.Component; +} + +export function isCombinator(entity: EntityType): entity is ICombinator { + return entity.entity === Entity.Combinator; +} + +export function isCurlyBracetMultiplier(multiplier: MultiplierType): multiplier is IMultiplierCurlyBracet { + return multiplier.sign === Multiplier.CurlyBracet; +} + +export function isMandatoryMultiplied(multiplier: MultiplierType | null) { + return multiplier !== null && isCurlyBracetMultiplier(multiplier) && multiplier.min > 1; +} + +export function isOptionallyMultiplied(multiplier: MultiplierType | null) { + return ( + multiplier !== null && + ((isCurlyBracetMultiplier(multiplier) && multiplier.min < multiplier.max && multiplier.max > 1) || + multiplier.sign === Multiplier.Asterisk || + multiplier.sign === Multiplier.PlusSign || + multiplier.sign === Multiplier.HashMark || + multiplier.sign === Multiplier.ExclamationPoint) + ); +} + +export function isMandatoryEntity(entity: EntityType) { + if (isCombinator(entity)) { + return entity === combinators[Combinator.DoubleAmpersand] || entity === combinators[Combinator.Juxtaposition]; + } + + if (entity.multiplier) { + return ( + (isCurlyBracetMultiplier(entity.multiplier) && entity.multiplier.min > 0) || + entity.multiplier.sign === Multiplier.PlusSign || + entity.multiplier.sign === Multiplier.HashMark || + entity.multiplier.sign === Multiplier.ExclamationPoint + ); + } + + return true; +} + +export function componentData( + component: Component.Keyword | Component.DataType, + value: string, + multiplier: MultiplierType | null = null, +): ComponentType { + return { + entity: Entity.Component, + component, + multiplier, + value, + }; +} + +export function componentGroupData(entities: EntityType[], multiplier: MultiplierType | null = null): ComponentType { + return { + entity: Entity.Component, + component: Component.Group, + multiplier, + entities, + }; +} + +function multiplierData(raw: string[]): MultiplierType | null { + if (!raw[0]) { + return null; + } + switch (raw[0].slice(0, 1)) { + case '*': + return { sign: Multiplier.Asterisk }; + case '+': + return { sign: Multiplier.PlusSign }; + case '?': + return { sign: Multiplier.QuestionMark }; + case '#': + return { sign: Multiplier.HashMark }; + case '!': + return { sign: Multiplier.ExclamationPoint }; + case '{': + return { sign: Multiplier.CurlyBracet, min: Number(raw[1]), max: Number(raw[2]) }; + default: + return null; + } +} + +function groupByPrecedence(entities: EntityType[], precedence: number = Combinator.SingleBar): EntityType[] { + if (precedence < 0) { + // We've reached the lowest precedence possible + return entities; + } + + const combinator = combinators[precedence]; + const combinatorIndexes: number[] = []; + + // Search for indexes where the combinator is used + for (let i = entities.indexOf(combinator); i > -1; i = entities.indexOf(combinator, i + 1)) { + combinatorIndexes.push(i); + } + + const nextPrecedence = precedence - 1; + + if (combinatorIndexes.length === 0) { + return groupByPrecedence(entities, nextPrecedence); + } + + const groupedEntities: EntityType[] = []; + + // Yes, what you see is correct: it's index of indexes + for ( + let i = 0; + // Add one loop to finnish up the last entities + i < combinatorIndexes.length + 1; + i++ + ) { + const sectionEntities = entities.slice( + i > 0 + ? combinatorIndexes[i - 1] + 1 + : // Slice from beginning + 0, + i < combinatorIndexes.length + ? combinatorIndexes[i] + : // Slice to end + entities.length, + ); + + // Only group if there's more than one entity in between + if (sectionEntities.length > 1) { + groupedEntities.push(componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence))); + } else { + groupedEntities.push(...sectionEntities); + } + + if (i < combinatorIndexes.length) { + groupedEntities.push(entities[combinatorIndexes[i]]); + } + } + + return groupedEntities; +} diff --git a/packages/tacky-css/csstype/syntax/typer.ts b/packages/tacky-css/csstype/syntax/typer.ts new file mode 100644 index 0000000..412ef1c --- /dev/null +++ b/packages/tacky-css/csstype/syntax/typer.ts @@ -0,0 +1,349 @@ +import * as cssTypes from 'mdn-data/css/types.json'; +import { isProperty, isSyntax } from '../collections/syntaxes'; +import { warn } from '../utils/logger'; +import { + Combinator, + Component, + EntityType, + isCombinator, + isComponent, + isMandatoryEntity, + isMandatoryMultiplied, + isOptionallyMultiplied, +} from './parser'; + +export enum Type { + Alias, + DataType, + PropertyReference, + Length, + Time, + StringLiteral, + NumericLiteral, + Array, + String, + Number, + Never, +} + +interface IBasic { + type: Type.String | Type.Number | Type.Length | Type.Time | Type.Never; +} + +export interface IDataType { + type: TType; + name: string; +} + +export interface IStringLiteral { + type: Type.StringLiteral; + literal: string; +} + +interface INumericLiteral { + type: Type.NumericLiteral; + literal: number; +} + +export type DataType = IDataType; + +// Yet another reminder; naming is hard +export type TypeType = IBasic | IStringLiteral | INumericLiteral | TDataType; + +export type ResolvedType = TypeType; + +let getBasicDataTypes = () => { + const types = Object.keys(cssTypes).reduce<{ [name: string]: IBasic }>((dataTypes, name) => { + switch (name) { + case 'number': + case 'integer': + dataTypes[name] = { + type: Type.Number, + }; + break; + case 'length': + dataTypes[name] = { + type: Type.Length, + }; + break; + case 'time': + dataTypes[name] = { + type: Type.Time, + }; + break; + default: + if (!isSyntax(name)) { + dataTypes[name] = { + type: Type.String, + }; + } + } + return dataTypes; + }, {}); + + // Cache + getBasicDataTypes = () => types; + + return types; +}; + +export default function typing(entities: EntityType[]): TypeType[] { + let mandatoryCombinatorCount = 0; + let mandatoryNonCombinatorsCount = 0; + for (const entity of entities) { + if (isMandatoryEntity(entity)) { + if (isCombinator(entity)) { + mandatoryCombinatorCount++; + } else { + mandatoryNonCombinatorsCount++; + } + } + } + + let types: TypeType[] = []; + + for (const entity of entities) { + if (isComponent(entity)) { + if (isMandatoryEntity(entity)) { + // In case of `something another-thing` we want to fall back to string until component combinations is solved + if (mandatoryCombinatorCount > 0 && mandatoryNonCombinatorsCount > 1) { + types = addString(types); + continue; + } + } else { + // In case of `something another-thing?` we want to add string until component combinations is solved + if (mandatoryCombinatorCount > 0 && mandatoryNonCombinatorsCount > 0) { + types = addString(types); + continue; + } + } + + if (isMandatoryMultiplied(entity.multiplier)) { + // In case of `something{2,3}` we fallback to `string` and stop as it needs to be multiplied + types = addString(types); + continue; + } else if (isOptionallyMultiplied(entity.multiplier)) { + // In case of `something{1,2}` or `something+` we fallback to `string` but moves on + // as it doesn't necessary needs to be multiplied + types = addString(types); + } + + switch (entity.component) { + case Component.Keyword: + if (String(Number(entity.value)) === entity.value) { + types = addNumericLiteral(types, Number(entity.value)); + } else { + types = addStringLiteral(types, entity.value); + } + break; + case Component.DataType: { + const value = valueOfDataType(entity.value); + if (value in getBasicDataTypes()) { + types = addType(types, getBasicDataTypes()[value]); + } else if (isSyntax(value)) { + types = addDataType(types, value); + } else { + const property = /'([^']+)'/.exec(value); + if (property && isProperty(property[1])) { + types = addPropertyReference(types, property[1]); + } else if (isProperty(value)) { + warn('Property reference `%s` was malformed', value); + types = addPropertyReference(types, value); + } else { + warn('Data type `%s` was missing', value); + types = addString(types); + } + } + break; + } + case Component.Group: { + for (const type of typing(entity.entities)) { + types = addType(types, type); + } + } + } + } else if (isCombinator(entity)) { + if (entity.combinator === Combinator.DoubleBar || isMandatoryEntity(entity)) { + types = addString(types); + } + } else { + types = addString(types); + } + } + + if (mandatoryNonCombinatorsCount > 1 && mandatoryCombinatorCount > 1) { + return [{ type: Type.String }]; + } + + return types; +} + +function addLength(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.Length)) { + return [ + ...types, + { + type: Type.Length, + }, + ]; + } + + return types; +} + +function addTime(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.Time)) { + return [ + ...types, + { + type: Type.Time, + }, + ]; + } + + return types; +} + +function addString(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.String)) { + return [ + ...types, + { + type: Type.String, + }, + ]; + } + + return types; +} + +function addNumber(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.Number)) { + return [ + ...types, + { + type: Type.Number, + }, + ]; + } + + return types; +} + +function addNever(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.Never)) { + return [ + ...types, + { + type: Type.Never, + }, + ]; + } + + return types; +} + +function addStringLiteral( + types: TypeType[], + literal: string, +): TypeType[] { + if (types.every(type => !(type.type === Type.StringLiteral && type.literal === literal))) { + return [ + ...types, + { + type: Type.StringLiteral, + literal, + }, + ]; + } + + return types; +} + +function addNumericLiteral( + types: TypeType[], + literal: number, +): TypeType[] { + if (types.every(type => !(type.type === Type.NumericLiteral && type.literal === literal))) { + return [ + ...types, + { + type: Type.NumericLiteral, + literal, + }, + ]; + } + + return types; +} + +function addDataType(types: TypeType[], name: string): TypeType[] { + if (types.every(type => !(type.type === Type.DataType && type.name === name))) { + return [ + ...types, + { + type: Type.DataType, + name, + } as TDataType, + ]; + } + + return types; +} + +function addPropertyReference( + types: TypeType[], + name: string, +): TypeType[] { + if (types.every(type => !(type.type === Type.PropertyReference && type.name === name))) { + return [ + ...types, + { + type: Type.PropertyReference, + name, + } as TDataType, + ]; + } + + return types; +} + +export function addType( + types: TypeType[], + type: TypeType, +): TypeType[] { + switch (type.type) { + case Type.Length: + return addLength(types); + case Type.Time: + return addTime(types); + case Type.String: + return addString(types); + case Type.Number: + return addNumber(types); + case Type.Never: + return addNever(types); + case Type.StringLiteral: + return addStringLiteral(types, type.literal); + case Type.NumericLiteral: + return addNumericLiteral(types, type.literal); + case Type.DataType: + return addDataType(types, type.name); + case Type.PropertyReference: + return addPropertyReference(types, type.name); + } +} + +export function hasType(originalTypes: TypeType[], type: TypeType): boolean { + const testTypes = addType(originalTypes, type); + return originalTypes === testTypes; +} + +const VALUE_OF_DATA_TYPE = /<([^\s>]+)/; +function valueOfDataType(value: string) { + try { + return value.match(VALUE_OF_DATA_TYPE)![1]; + } catch { + throw new Error(`Was not able to get value of \`${value}\``); + } +} diff --git a/packages/tacky-css/csstype/typescript.ts b/packages/tacky-css/csstype/typescript.ts new file mode 100644 index 0000000..5aaf8be --- /dev/null +++ b/packages/tacky-css/csstype/typescript.ts @@ -0,0 +1,212 @@ +import { + DeclarableType, + IDeclaration, + INamespace, + Interface, + isAliasProperty, + isEmptyEnum, + isInterface, + isInterfaceFunction, + isInterfaceProperties, + SimpleType, +} from "./declarator"; +import { Type } from "./syntax/typer"; +import { + createStringifyType, + EOL, + generatingDeclarations, + stringifyGenerics, +} from "./utils/output"; + +export default async function typescript(): Promise { + const { + namespaces, + interfaces, + declarations, + extra, + } = await generatingDeclarations; + + let interfacesOutput = ""; + for (const entry of interfaces) { + if (interfacesOutput) { + interfacesOutput += EOL; + } + + interfacesOutput += (await outputInterface(entry, undefined)) + EOL; + } + + let declarationsOutput = ""; + for (const entry of declarations) { + if (declarationsOutput) { + declarationsOutput += EOL; + } + + declarationsOutput += (await outputDeclaration(entry, undefined)) + EOL; + } + + let namespaceOutput = ""; + for (const namespace of namespaces) { + if (namespaceOutput) { + namespaceOutput += EOL; + } + + if (namespace.export) { + namespaceOutput += "export "; + } else { + namespaceOutput += "declare "; + } + + namespaceOutput += `namespace ${namespace.name} {${EOL}`; + + const body = namespace.body(); + + for (const entry of body) { + if (namespaceOutput) { + namespaceOutput += EOL; + } + + if (isInterface(entry)) { + namespaceOutput += (await outputInterface(entry, namespace)) + EOL; + } else if (isEmptyEnum(entry)) { + namespaceOutput += `export enum ${entry.name} { _ = "" }` + EOL; + } else { + namespaceOutput += (await outputDeclaration(entry, namespace)) + EOL; + } + } + + namespaceOutput += `}${EOL}`; + } + + const disableAutoExport = "export {};" + EOL; + const propertyTuple = + "export type PropertyTuple = readonly [T, Values[T]]" + EOL; + + return ( + disableAutoExport + + EOL + + propertyTuple + + EOL + + interfacesOutput + + EOL + + declarationsOutput + + EOL + + namespaceOutput + + EOL + + extra.join("\n\n") + ); +} + +async function outputInterface( + entry: Interface, + currentNamespace: INamespace | undefined +): Promise { + let output = ""; + + const comment = await entry.comment?.(); + if (comment) { + output += comment + EOL; + } + + if (entry.export) { + output += "export "; + } + + if (isInterfaceProperties(entry)) { + const extendList = entry.extends + .map(extend => extend.name + stringifyGenerics(extend.generics)) + .join(", "); + output += + "interface " + + entry.name + + stringifyGenerics(entry.generics, true, stringifySimpleTypes); + + if (extendList) { + output += ` extends ${extendList}`; + } + + output += "{" + EOL; + + for (const property of entry.properties) { + if (isInterfaceFunction(property)) { + output += "("; + const args = property.args + .map( + arg => `${arg.name}: ${stringifyTypes(arg.types, currentNamespace)}` + ) + .join(", "); + + output += args; + output += `): ${property.return};`; + output += EOL; + } else { + const comment = await property.comment(); + if (comment) { + output += comment + EOL; + } + + const type = isAliasProperty(property) ? property.alias : property.type; + const value = stringifyTypes([type], currentNamespace); + + // appalling + const withString = + isAliasProperty(property) && + property.alias.namespace?.name === "ValueEnum" + ? " & string" + : ""; + + output += `${JSON.stringify(property.name)}: ${value}${withString};`; + + output += EOL; + } + } + output += "}"; + } + + return output; +} + +async function outputDeclaration( + entry: IDeclaration, + currentNamespace: INamespace | undefined +): Promise { + let output = ""; + + const comment = await entry.comment?.(); + if (comment) { + output += comment + EOL; + } + + if (entry.export) { + output += "export "; + } + + output += `type ${ + entry.name + + stringifyGenerics(entry.generics, entry.export, stringifySimpleTypes) + } = ${stringifyTypes(entry.types, currentNamespace)}`; + + return output; +} + +function stringifyTypes( + types: DeclarableType[], + currentNamespace: INamespace | undefined +): string { + const stringifyType = createStringifyType(type => { + // The type is in its own namespace so keep it empty + const namespace = + type.namespace && type.namespace !== currentNamespace + ? `${type.namespace.name}.` + : ""; + return namespace + type.name; + }, currentNamespace); + + return types + // .filter(({ type }) => ![Type.String, Type.Length, Type.Time].includes(type)) + .map(type => stringifyType(type)) + .join(" | "); +} + +function stringifySimpleTypes(types: SimpleType[]): string { + return stringifyTypes(types, undefined); +} diff --git a/packages/tacky-css/csstype/utils/casing.ts b/packages/tacky-css/csstype/utils/casing.ts new file mode 100644 index 0000000..1d49f4f --- /dev/null +++ b/packages/tacky-css/csstype/utils/casing.ts @@ -0,0 +1,27 @@ +const REGEX_LEADING_LETTER = /^(\w)/; +const REGEX_KEBAB_SEPARATOR = /-(\w)/g; +const REGEX_VENDOR_PREFIXED = /^-(\w)/; +const REGEX_MS_PREFIXED = /^-ms-/; + +export function toPascalCase(kebabCase: string) { + return kebabCase + .replace(REGEX_VENDOR_PREFIXED, toUpperReplacer) + .replace(REGEX_LEADING_LETTER, toUpperReplacer) + .replace(REGEX_KEBAB_SEPARATOR, toUpperReplacer); +} + +export function toVendorPrefixCase(property: string) { + return REGEX_MS_PREFIXED.test(property) ? 'ms' + toPascalCase(property.slice(4)) : toPascalCase(property); +} + +export function toCamelCase(kebabCase: string) { + return kebabCase.replace(REGEX_VENDOR_PREFIXED, toLowerReplacer).replace(REGEX_KEBAB_SEPARATOR, toUpperReplacer); +} + +function toUpperReplacer(substring: string, ...args: string[]): string { + return args[0].toUpperCase(); +} + +function toLowerReplacer(substring: string, ...args: string[]): string { + return args[0].toLowerCase(); +} diff --git a/packages/tacky-css/csstype/utils/comment.ts b/packages/tacky-css/csstype/utils/comment.ts new file mode 100644 index 0000000..4d310ed --- /dev/null +++ b/packages/tacky-css/csstype/utils/comment.ts @@ -0,0 +1,255 @@ +import * as l10n from 'mdn-data/l10n/css.json'; +import { format } from 'prettier'; +import { IExtendedProperty } from '../data/patches'; +import { getCompats } from '../utils/compat'; +import { warn } from './logger'; +import { getSummary } from './urls'; + +const BLANK_ROW = ''; +const L10N_TAGS_REGEX = /(<[^>]+>|\{\{[^\}]+\}\})/; + +export async function composeCommentBlock( + compatibilityData: MDN.CompatData | undefined, + data: IExtendedProperty, + vendor = false, + obsolete = false, +) { + const rows: string[] = []; + const includeCompatibility = !vendor && !obsolete && compatibilityData; + + if (data.mdn_url) { + const summary = await getSummary(data.mdn_url); + if (summary) { + rows.push(summary, BLANK_ROW); + } + } + + if (data.syntax) { + rows.push(`**Syntax**: \`${data.syntax}\``, BLANK_ROW); + } + + if (typeof data.initial === 'string') { + if (data.initial in l10n) { + if (typeof l10n[data.initial]['en-US'] === 'string') { + rows.push(`**Initial value**: ${formatL10n(l10n[data.initial]['en-US'])}`, BLANK_ROW); + } + } else { + rows.push(`**Initial value**: \`${data.initial}\``, BLANK_ROW); + } + } + + // Skip compatibility table for obsolete and vendor properties + if (includeCompatibility) { + rows.push(...getCompatRows(compatibilityData!)); + } + + if (obsolete) { + rows.push('@deprecated', BLANK_ROW); + } + + // Trim ending + if (rows[rows.length - 1] === BLANK_ROW) { + rows.pop(); + } + + if (rows.length === 1) { + return '/** ' + rows[0] + ' */'; + } + + if (rows.length > 1) { + return '/**\n * ' + rows.join('\n * ') + '\n */'; + } +} + +function getCompatRows(compatibilityData: MDN.CompatData) { + const compats = getCompats(compatibilityData); + const rows: string[] = []; + + if (compats.length > 1) { + rows.push('---', BLANK_ROW); + } + + for (const compat of compats) { + if (compats.length > 1 && compat.description) { + rows.push(`_${compat.description}_`, BLANK_ROW); + } + + const chrome = supportVersion(compat.support.chrome); + const firefox = supportVersion(compat.support.firefox); + const safari = supportVersion(compat.support.safari); + const edge = supportVersion(compat.support.edge); + const ie = supportVersion(compat.support.ie); + + const versions = [chrome, firefox, safari, edge, ie]; + + rows.push( + ...format( + [ + '| Chrome | Firefox | Safari | Edge | IE |', + '| :---: | :---: | :---: | :---: | :---: |', + '| ' + versions.map(version => version[0] || '').join(' | ') + ' |', + versions.some(version => !!version[1]) + ? '| ' + versions.map(version => version[1] || '').join(' | ') + ' |' + : '', + ].join('\n'), + { parser: 'markdown' }, + ) + .trim() + .split('\n'), + BLANK_ROW, + ); + + if (compats.length > 1) { + rows.push('---', BLANK_ROW); + } + } + + const urls = compats.reduce( + (list, { mdn_url }) => (mdn_url && !list.includes(mdn_url) ? [...list, mdn_url] : list), + [], + ); + + if (urls.length > 0) { + rows.push(...urls.map(url => '@see ' + url), BLANK_ROW); + } + + return rows; +} + +function supportVersion(supports: MDN.Support | MDN.Support[] | undefined): string[] { + supports = supports ? (Array.isArray(supports) ? supports : [supports]).reverse() : []; + + // Ignore versions hidden under flags + supports = supports.filter(({ flags }) => !flags); + + const supportsVersions = supports.filter(({ version_added }) => typeof version_added === 'string'); + + if (supportsVersions.length > 0) { + // Find lowest version of standard implementation + const supportsStandard = supportsVersions.reduce((previous, current) => { + if (!current.prefix && !current.alternative_name) { + if ( + !previous || + (previous.version_added !== true && current.version_added === true) || + typeof previous.version_added !== 'string' || + typeof current.version_added !== 'string' || + // Lower version + versionDiff(previous.version_added, current.version_added) < 0 || + // Prioritize version of full implementation + (previous.partial_implementation && !current.partial_implementation) + ) { + return current; + } + } + + return previous; + }, null); + + // Find lowest version of non-standard or prefix implementation + const supportsPrefixed = supportsVersions.reduce((previous, current) => { + if ( + (current.prefix || current.alternative_name) && + // Ignore removed versions + !current.version_removed && + // Only display prefixed or alternative if this version is lower than standard implementation + (!supportsStandard || + (supportsStandard.version_added !== true && current.version_added === true) || + typeof supportsStandard.version_added !== 'string' || + typeof current.version_added !== 'string' || + versionDiff(supportsStandard.version_added, current.version_added) < 0) + ) { + if ( + !previous || + (previous.version_added !== true && current.version_added === true) || + typeof previous.version_added !== 'string' || + typeof current.version_added !== 'string' || + // Lower version + versionDiff(previous.version_added, current.version_added) < 0 || + // Prioritize version of full implementation + (previous.partial_implementation && !current.partial_implementation) + ) { + return current; + } + } + + return previous; + }, null); + + const version: string[] = []; + + if (supportsStandard) { + version.push( + supportsStandard.version_removed + ? `${supportsStandard.version_added}-${supportsStandard.version_removed}` + : `**${supportsStandard.version_added}**`, + ); + } + + if (supportsPrefixed) { + version.push( + (supportsStandard ? (supportsPrefixed.version_added as string) : `**${supportsPrefixed.version_added}**`) + + (supportsPrefixed.prefix + ? ` _-x-_` + : supportsPrefixed.alternative_name + ? ` _(${supportsPrefixed.alternative_name})_` + : ''), + ); + } + + return version; + } else { + const supportYes = supports.find(({ version_added }) => version_added === true); + if (supportYes) { + return ['Yes']; + } + + const supportsNo = supports.find(({ version_added }) => version_added === false); + if (supportsNo) { + return ['No']; + } + } + + return ['n/a']; +} + +function versionDiff(a: string, b: string) { + const aNumber = Number(a); + const bNumber = Number(b); + + if (String(aNumber) !== a) { + warn('Version `%s` is not properly handled', a); + } + if (String(bNumber) !== b) { + warn('Version `%s` is not properly handled', b); + } + + return bNumber - aNumber; +} + +function formatL10n(phrase: string) { + return phrase + .split(L10N_TAGS_REGEX) + .map(chunk => { + if (chunk === '' || chunk === '') { + return '`'; + } + + if (chunk.startsWith('<') && chunk.endsWith('>')) { + return ''; + } + + // References to another property + const curlyBlock = chunk.match(/\{\{cssxref\("([^"]*)"\)\}\}/); + if (curlyBlock) { + return `_${curlyBlock[1]}_`; + } + + if (chunk.startsWith('{{') && chunk.endsWith('}}')) { + warn('Unknown curly bracket block `%s` in i10n', chunk); + return chunk; + } + + return chunk; + }) + .join(''); +} diff --git a/packages/tacky-css/csstype/utils/compat.ts b/packages/tacky-css/csstype/utils/compat.ts new file mode 100644 index 0000000..2a43a12 --- /dev/null +++ b/packages/tacky-css/csstype/utils/compat.ts @@ -0,0 +1,256 @@ +import { + Combinator, + combinators, + Component, + componentData, + componentGroupData, + Entity, + EntityType, +} from '../syntax/parser'; + +const importsCache: { [cssPath: string]: MDN.CompatData | undefined } = {}; + +interface IRegularCompat { + __compat: MDN.Compat; +} + +interface IContextCompat { + [context: string]: IRegularCompat; +} + +export function getCompats(data: IRegularCompat | IContextCompat): MDN.Compat[] { + return '__compat' in data + ? [(data as IRegularCompat).__compat] + : Object.keys(data).map(context => (data as IContextCompat)[context].__compat); +} + +export function getSupport(support: MDN.Support | MDN.Support[]): MDN.Support[] { + return Array.isArray(support) ? support : [support]; +} + +export function getAtRuleData(name: string) { + return getData('at-rules', name); +} + +export function getPropertyData(name: string) { + return getData('properties', name); +} + +export function getSelectorsData(name: string) { + return getData('selectors', name); +} + +export function getTypesData(name: string) { + return getData('types', name); +} + +async function getData( + type: 'at-rules' | 'properties' | 'selectors' | 'types', + name: string, +): Promise { + const cssPath = type + '/' + name; + if (cssPath in importsCache) { + return importsCache[cssPath]; + } + + try { + const data = await import(`mdn-browser-compat-data/css/${cssPath}.json`); + + if (!data) { + return (importsCache[cssPath] = undefined); + } + + const cssData = data.css[type][name]; + return (importsCache[cssPath] = cssData); + } catch { + return (importsCache[cssPath] = undefined); + } +} + +export function compatNames(compat: MDN.Compat, name: string, onlyRemoved = false): string[] { + const properties: string[] = []; + + let browser: MDN.Browsers; + for (browser in compat.support) { + const support = compat.support[browser]; + + for (const version of getSupport(support)) { + // Assume that the version has the property implemented if `null` + const isAdded = !!version.version_added || version.version_added === null; + const isRemoved = !!version.version_removed; + + if (isAdded && isRemoved === onlyRemoved) { + if (version.prefix) { + properties.push(version.prefix + name); + } + if (version.alternative_name) { + properties.push(version.alternative_name); + } + } + } + } + + return properties; +} + +export function compatSyntax(data: MDN.CompatData, entities: EntityType[]): EntityType[] { + const compatEntities: EntityType[] = []; + + for (const entity of entities) { + if (entity.entity === Entity.Component) { + switch (entity.component) { + case Component.Keyword: { + if (entity.value in data) { + const compats = getCompats(data[entity.value]); + if (compats.every(compat => !isAddedBySome(compat))) { + // The keyword needs to be added by some browsers so we remove previous + // combinator and skip this keyword + compatEntities.pop(); + continue; + } + } + + const alternatives = alternativeKeywords(data, entity.value); + + if (alternatives.length > 0) { + const alternativeEntities: EntityType[] = [entity]; + + for (const keyword of alternatives) { + alternativeEntities.push(combinators[Combinator.SingleBar], componentData(Component.Keyword, keyword)); + } + + compatEntities.push(componentGroupData(alternativeEntities)); + continue; + } + break; + } + case Component.Group: { + compatEntities.push(componentGroupData(compatSyntax(data, entity.entities), entity.multiplier)); + continue; + } + } + } + + compatEntities.push(entity); + } + + return compatEntities; +} + +function alternativeKeywords(data: MDN.CompatData, value: string): string[] { + const alternatives: string[] = []; + + if (value in data) { + const compats = getCompats(data[value]); + + for (const compat of compats) { + let browser: MDN.Browsers; + for (browser in compat.support) { + const support = compat.support[browser]; + + for (const version of Array.isArray(support) ? support : [support]) { + const isCurrent = + // Assume that the version has the value implemented if `null` + !!version.version_added || version.version_added === null; + + if (isCurrent) { + if (version.prefix && !alternatives.includes(version.prefix + value)) { + alternatives.push(version.prefix + value); + } + if (version.alternative_name && !alternatives.includes(version.alternative_name)) { + alternatives.push(version.alternative_name); + } + } + } + } + } + } + + return alternatives; +} + +export function isDeprecated(data: { status?: string }, compat?: MDN.Compat) { + // Assume not deprecated if is status i missing + return data.status === 'obsolete' || (!!compat && !!compat.status && compat.status.deprecated); +} + +export function isAddedBySome(compat: MDN.Compat): boolean { + let browser: MDN.Browsers; + for (browser in compat.support) { + const support = compat.support[browser]; + + for (const version of getSupport(support)) { + // Assume that the version has the property implemented if `null` + if (!!version.version_added || version.version_added === null) { + return true; + } + } + } + + return false; +} + +export async function alternativeSelectors(selector: string): Promise { + const alternatives: string[] = []; + + // Pseudo without ':' + const colons = ':'.repeat(selector.lastIndexOf(':') + 1); + const name = selector.slice(colons.length); + const compatibilityData = await getSelectorsData(name); + + if (compatibilityData) { + const compats = getCompats(compatibilityData); + + for (const compat of compats) { + let browser: MDN.Browsers; + for (browser in compat.support) { + const support = compat.support[browser]; + + for (const version of getSupport(support)) { + // Assume that the version has the property implemented if `null` + const isAdded = !!version.version_added || version.version_added === null; + + if (isAdded) { + if (version.prefix) { + alternatives.push(colons + version.prefix + name); + } + if (version.alternative_name) { + alternatives.push(version.alternative_name); + } + } + } + } + } + + return alternatives; + } + + return alternatives; +} + +export function alternativeAttributes(name: string, data: MDN.CompatData): string[] { + const alternatives: string[] = []; + const compats = getCompats(data); + + for (const compat of compats) { + for (const browser in compat.support) { + const support = (compat.support as any)[browser]; + + for (const version of getSupport(support)) { + // Assume that the version has the property implemented if `null` + const isAdded = !!version.version_added || version.version_added === null; + + if (isAdded) { + if (version.prefix) { + alternatives.push(version.prefix + name); + } + if (version.alternative_name) { + alternatives.push(version.alternative_name); + } + } + } + } + } + + return alternatives; +} diff --git a/packages/tacky-css/csstype/utils/logger.ts b/packages/tacky-css/csstype/utils/logger.ts new file mode 100644 index 0000000..91495da --- /dev/null +++ b/packages/tacky-css/csstype/utils/logger.ts @@ -0,0 +1,11 @@ +import chalk = require('chalk'); + +export const error: typeof console.error = (message: any, ...params: any[]) => { + // Complete the build process but exit with failure when done + process.exitCode = 1; + console.error(typeof message === 'string' ? chalk.magenta('ERROR! ' + message) : message, ...params); +}; + +export const warn: typeof console.warn = (message: any, ...params: any[]) => { + console.info(typeof message === 'string' ? chalk.yellow(message) : message, ...params); +}; diff --git a/packages/tacky-css/csstype/utils/output.ts b/packages/tacky-css/csstype/utils/output.ts new file mode 100644 index 0000000..9a2e3a7 --- /dev/null +++ b/packages/tacky-css/csstype/utils/output.ts @@ -0,0 +1,89 @@ +import { + DeclarableType, + declarator, + IAlias, + IGenerics, + INamespace, + lengthGeneric, + SimpleType, + timeGeneric, +} from '../declarator'; +import { Type } from '../syntax/typer'; + +export const EOL = '\n'; + +export const generatingDeclarations = declarator(3); +export function createStringifyType(): (type: SimpleType) => string; + +export function createStringifyType( + createTypeAliasName: (type: IAlias) => string, + currentNamespace?: INamespace, +): (type: DeclarableType) => string; + +export function createStringifyType( + createTypeAliasName: (type: IAlias, currentNamespace: INamespace | undefined) => string = type => type.name, + currentNamespace?: INamespace, +) { + const stringifyType = ((type: DeclarableType) => { + switch (type.type) { + case Type.String: + return 'string'; + case Type.Number: + return 'number'; + case Type.Never: + return 'never'; + case Type.StringLiteral: + return JSON.stringify(type.literal); + case Type.NumericLiteral: + return type.literal; + case Type.Array: + return `Array<${stringifyType(type.of)}>`; + case Type.Alias: { + return createTypeAliasName(type, currentNamespace) + stringifyGenerics(type.generics); + } + case Type.Length: + return lengthGeneric.name; + case Type.Time: + return timeGeneric.name; + } + }) as (type: DeclarableType) => string; + + return stringifyType; +} + +export function stringifyGenerics(items: IGenerics[] | undefined): string; +export function stringifyGenerics( + items: IGenerics[] | undefined, + applyDefault: boolean, + stringifyTypes: (types: SimpleType[]) => string, +): string; + +export function stringifyGenerics( + items: IGenerics[] | undefined, + applyDefault = false, + stringifyTypes?: (types: SimpleType[]) => string, +) { + if (!items || items.length === 0) { + return ''; + } + + return `<${items + .map(({ name, extend, defaults }) => { + let generic = name; + + if (extend) { + generic += ` extends ${extend}`; + } + + if (applyDefault && defaults) { + if (typeof stringifyTypes !== 'function') { + throw new Error('Type stringifier needed'); + } + + generic += ` = ${stringifyTypes(defaults)}`; + } + + return generic; + }) + .join(', ')}>`; +} diff --git a/packages/tacky-css/csstype/utils/urls.ts b/packages/tacky-css/csstype/utils/urls.ts new file mode 100644 index 0000000..e3ef5e1 --- /dev/null +++ b/packages/tacky-css/csstype/utils/urls.ts @@ -0,0 +1,74 @@ +import * as fs from 'fs'; +import * as jsdom from 'jsdom'; +import * as path from 'path'; +import request from 'request'; +import Turndown from 'turndown'; +import { error, warn } from './logger'; + +const pathToCache = path.join(__dirname, '../data/urls.json'); + +// tslint:disable-next-line:no-var-requires +const urlData: Record = require(pathToCache); + +const turndown = new Turndown(); + +// change anchors to plain text so we don't end up with a bunch of relative urls +turndown.addRule('anchor', { + filter: 'a', + replacement: (content: string) => content, +}); + +async function scrapeSummary(url: string): Promise { + try { + const htmlContents: string = await new Promise((resolve, reject) => { + request(url, (e, response, body) => { + if (e) { + reject(e); + } else { + resolve(body); + } + }); + }); + + const { window } = new jsdom.JSDOM(htmlContents); + const summaryElement = window.document.querySelector('#wikiArticle > p:not(:empty)'); + window.close(); + + if (summaryElement) { + return turndown.turndown(summaryElement.innerHTML); + } + + return ''; + } catch { + warn(`Could not fetch summary for '${url}'`); + return ''; + } +} + +function saveToFile(): void { + try { + const sortedUrlData = Object.keys(urlData) + .sort() + .reduce>((data, url) => { + data[url] = urlData[url]; + return data; + }, {}); + + const fileContents = JSON.stringify(sortedUrlData, undefined, 2); + fs.writeFileSync(pathToCache, fileContents, { encoding: 'utf-8' }); + } catch (ex) { + error(ex.toString()); + } +} + +export async function getSummary(url: string): Promise { + let summaryData = urlData[url]; + + if (url && !summaryData) { + console.log('Fetching summary for ' + url); + urlData[url] = summaryData = (await scrapeSummary(url)) || ''; + saveToFile(); + } + + return summaryData; +} diff --git a/packages/tacky-css/package.json b/packages/tacky-css/package.json index 4209a17..6811555 100644 --- a/packages/tacky-css/package.json +++ b/packages/tacky-css/package.json @@ -7,12 +7,28 @@ "author": "Tom Picton (tom@tompicton.com)", "license": "MIT", "private": false, + "scripts": { + "generate": "ts-node --files build.ts", + "build": "rm -rf lib; tsc && cp package.json lib" + }, "devDependencies": { "@emotion/react": "^11.0.0-next.15", "@types/babel-plugin-macros": "^2.8.2", + "@types/chalk": "^2.2.0", + "@types/jsdom": "^16.2.4", + "@types/node": "^14.10.2", + "@types/request": "^2.48.5", + "@types/turndown": "^5.0.0", "babel-plugin-macros": "^2.8.0", + "chalk": "^4.1.0", "csstype": "^3.0.3", + "fast-glob": "^3.2.4", + "mdn-browser-compat-data": "git+https://github.com/mdn/browser-compat-data.git#bce11b3da31a3b3425280730360ee42d7cc55328", + "mdn-data": "git+https://github.com/mdn/data.git#3191d4b889364bca969dfcc84e490928d84710ef", "nanoid": "^3.1.12", + "request": "^2.88.2", + "ts-node": "^9.0.0", + "turndown": "^6.0.0", "typescript": "^4.0.2" } } diff --git a/packages/tacky-css/src/animation.ts b/packages/tacky-css/src/animation.ts deleted file mode 100644 index 6c92578..0000000 --- a/packages/tacky-css/src/animation.ts +++ /dev/null @@ -1,189 +0,0 @@ -// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties -// TODO: Figure out a way to generate this from MDN -export type AnimatableProperties = - | "all" - | "backdropFilter" - | "background" - | "backgroundColor" - | "backgroundPosition" - | "backgroundSize" - | "blockSize" - | "border" - | "borderBlockEnd" - | "borderBlockEndColor" - | "borderBlockEndWidth" - | "borderBlockStart" - | "borderBlockStartColor" - | "borderBlockStartWidth" - | "borderBottom" - | "borderBottomColor" - | "borderBottomLeftRadius" - | "borderBottomRightRadius" - | "borderBottomWidth" - | "borderColor" - | "borderEndEndRadius" - | "borderEndStartRadius" - | "borderImageOutset" - | "borderImageSlice" - | "borderImageWidth" - | "borderInlineEnd" - | "borderInlineEndColor" - | "borderInlineEndWidth" - | "borderInlineStart" - | "borderInlineStartColor" - | "borderInlineStartWidth" - | "borderLeft" - | "borderLeftColor" - | "borderLeftWidth" - | "borderRadius" - | "borderRight" - | "borderRightColor" - | "borderRightWidth" - | "borderStartEndRadius" - | "borderStartStartRadius" - | "borderTop" - | "borderTopColor" - | "borderTopLeftRadius" - | "borderTopRightRadius" - | "borderTopWidth" - | "borderWidth" - | "bottom" - | "boxShadow" - | "caretColor" - | "clip" - | "clipPath" - | "color" - | "columnCount" - | "columnGap" - | "columnRule" - | "columnRuleColor" - | "columnRuleWidth" - | "columnWidth" - | "columns" - | "filter" - | "flex" - | "flexBasis" - | "flexGrow" - | "flexShrink" - | "font" - | "fontSize" - | "fontSizeAdjust" - | "fontStretch" - | "fontVariationSettings" - | "fontWeight" - | "gap" - | "gridColumnGap" - | "gridGap" - | "gridRowGap" - | "gridTemplateColumns" - | "gridTemplateRows" - | "height" - | "inlineSize" - | "inset" - | "insetBlock" - | "insetBlockEnd" - | "insetBlockStart" - | "insetInline" - | "insetInlineEnd" - | "insetInlineStart" - | "left" - | "letterSpacing" - | "lineClamp" - | "lineHeight" - | "margin" - | "marginBlockEnd" - | "marginBlockStart" - | "marginBottom" - | "marginInlineEnd" - | "marginInlineStart" - | "marginLeft" - | "marginRight" - | "marginTop" - | "mask" - | "maskBorder" - | "maskPosition" - | "maskSize" - | "maxBlockSize" - | "maxHeight" - | "maxInlineSize" - | "maxLines" - | "maxWidth" - | "minBlockSize" - | "minHeight" - | "minInlineSize" - | "minWidth" - | "objectPosition" - | "offset" - | "offsetAnchor" - | "offsetDistance" - | "offsetPath" - | "offsetPosition" - | "offsetRotate" - | "opacity" - | "order" - | "outline" - | "outlineColor" - | "outlineOffset" - | "outlineWidth" - | "padding" - | "paddingBlockEnd" - | "paddingBlockStart" - | "paddingBottom" - | "paddingInlineEnd" - | "paddingInlineStart" - | "paddingLeft" - | "paddingRight" - | "paddingTop" - | "perspective" - | "perspectiveOrigin" - | "right" - | "rotate" - | "rowGap" - | "scale" - | "scrollMargin" - | "scrollMarginBlock" - | "scrollMarginBlockEnd" - | "scrollMarginBlockStart" - | "scrollMarginBottom" - | "scrollMarginInline" - | "scrollMarginInlineEnd" - | "scrollMarginInlineStart" - | "scrollMarginLeft" - | "scrollMarginRight" - | "scrollMarginTop" - | "scrollPadding" - | "scrollPaddingBlock" - | "scrollPaddingBlockEnd" - | "scrollPaddingBlockStart" - | "scrollPaddingBottom" - | "scrollPaddingInline" - | "scrollPaddingInlineEnd" - | "scrollPaddingInlineStart" - | "scrollPaddingLeft" - | "scrollPaddingRight" - | "scrollPaddingTop" - | "scrollSnapCoordinate" - | "scrollSnapDestination" - | "scrollbarColor" - | "shapeImageThreshold" - | "shapeMargin" - | "shapeOutside" - | "tabSize" - | "textDecoration" - | "textDecorationColor" - | "textDecorationThickness" - | "textEmphasis" - | "textEmphasisColor" - | "textIndent" - | "textShadow" - | "textUnderlineOffset" - | "top" - | "transform" - | "transformOrigin" - | "translate" - | "verticalAlign" - | "visibility" - | "width" - | "wordSpacing" - | "zIndex" - | "zoom"; diff --git a/packages/tacky-css/src/color.ts b/packages/tacky-css/src/color.ts deleted file mode 100644 index 7be2ffd..0000000 --- a/packages/tacky-css/src/color.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TackyVariant } from "./types"; - -export type Rgb = TackyVariant<"rgb">; -export const rgb = (red: number, green: number, blue: number): Rgb => - `rgb(${red}, ${green}, ${blue})` as Rgb; - -export type Rgba = TackyVariant<"rgba">; -export const rgba = ( - red: number, - green: number, - blue: number, - alpha: number -): Rgba => `rgba(${red}, ${green}, ${blue}, ${alpha})` as Rgba; - -export type CSSColor = Rgb | Rgba | "currentcolor" | "transparent"; diff --git a/packages/tacky-css/src/function.ts b/packages/tacky-css/src/function.ts deleted file mode 100644 index 79f7d95..0000000 --- a/packages/tacky-css/src/function.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TackyVariant } from "./types"; -import { CSSLengthPercentage } from "./unit"; - -export type CSSURL = TackyVariant<"url">; -export const url = (url: URL): CSSURL => `url(${url})` as CSSURL; - -export type FitContent = TackyVariant<"fitContent">; -export const fitContent = (arg: CSSLengthPercentage): FitContent => - `fitContent(${arg})` as FitContent; diff --git a/packages/tacky-css/src/image.ts b/packages/tacky-css/src/image.ts deleted file mode 100644 index 320fc43..0000000 --- a/packages/tacky-css/src/image.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { BackgroundPositionArgs } from "./property"; -import { CSSColor } from "./color"; -import { CSSLengthPercentage, Percent, CSSAngle } from "./unit"; -import { TackyVariant } from "./types"; -import { CSSURL } from "./function"; - -type SideOrCorner = - | "to top" - | "to top left" - | "to top right" - | "to bottom" - | "to bottom left" - | "to bottom right" - | "to left" - | "to left top" - | "to left bottom" - | "to right" - | "to right top" - | "to right bottom"; - -type InitialLinearColorStop = [ - color: CSSColor, - stopStart?: CSSLengthPercentage, - stopEnd?: CSSLengthPercentage -]; - -type LinearColorStop = InitialLinearColorStop; -type ColorHint = Percent; -type LinearColorStopOrHint = LinearColorStop | [ColorHint, ...LinearColorStop]; - -// Ideally would be expressed in a way that matches the CSS syntax -// more closely, i.e. -// -// linearGradient( -// "to top", -// ["red", percent(10), percent(40)], -// percent(50), <- -// ["blue"] -// ); -// -// The constraints we have are: -// -// 1. must be after at least one -// 2. No two neighboring arguments can both be a -// 3. Final argument must be a -// -// This is difficult because: -// - Variadic tuples lose type strictness for elements following a spread -// over a tuple of unknown length, and the "middle" arguments in a linear -// gradient can be of any length -// - Recursively spreading a tuple isn't officially supported -// https://github.com/microsoft/TypeScript/issues/40298 -// -// The current solution combines and for all -// but the 3rd argument to the nth argument. I dislike this departure from the -// CSS spec, but it does satisfy all three constaints. - -export interface LinearGradientFunction { - < - T extends [CSSAngle | SideOrCorner] | [], - V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]] - >( - ...args: [ - ...angle: T, - colorStop: InitialLinearColorStop, - ...colorStopOrHint: V - ] - ): Return; -} - -export type LinearGradient = TackyVariant<"linearGradient">; -export const linearGradient: LinearGradientFunction = ( - ...args -) => { - return `linear-gradient(${args - .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) - .join(", ")})` as LinearGradient; -}; - -export type RepeatingLinearGradient = TackyVariant<"repeatingLinearGradient">; -export const repeatingLinearGradient: LinearGradientFunction = ( - ...args -) => { - return `repeating-linear-gradient(${args - .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) - .join(", ")})` as RepeatingLinearGradient; -}; - -export type RadialGradient = TackyVariant<"RadialGradient">; -type RadialGradientShape = "circle" | "ellipse"; -type RadialGradientExtentKeyword = - | "closest-side" - | "closest-corner" - | "farthest-side" - | "farthest-corner"; -export interface RadialGradientFunction { - < - T extends - | [ - RadialGradientShape | RadialGradientExtentKeyword, - ...([] | ["at", ...BackgroundPositionArgs]) - ] - | [], - V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]] - >( - ...args: [ - ...shape: T, - colorStop: InitialLinearColorStop, - ...colorStopOrHint: V - ] - ): Return; -} -export const radialGradient: RadialGradientFunction = ( - ...args -) => { - return `radial-gradient(${args - .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) - .join(", ")})` as RadialGradient; -}; - -export type RepeatingRadialGradient = TackyVariant<"RepeatingRadialGradient">; -export const repeatingRadialGradient: RadialGradientFunction = ( - ...args -) => { - return `repeating-radial-gradient(${args - .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) - .join(", ")})` as RepeatingRadialGradient; -}; - -// TODO: conical-gradient() -export type CSSGradient = - | LinearGradient - | RepeatingLinearGradient - | RadialGradient - | RepeatingRadialGradient; - -// TODO: cross-fade() -// TODO: image-set() -export type CSSImage = CSSURL | CSSGradient; diff --git a/packages/tacky-css/src/index.ts b/packages/tacky-css/src/index.ts index 37def18..eee76b4 100644 --- a/packages/tacky-css/src/index.ts +++ b/packages/tacky-css/src/index.ts @@ -1,35 +1,11 @@ -import { CSSObject } from "@emotion/react"; +import { color } from "./generated"; -import * as color from "./color"; -import * as funktion from "./function"; -import * as image from "./image"; -import { media } from "./media"; -import * as property from "./property"; -import * as unit from "./unit"; +declare module "./generated" { + namespace ManualDataType { + export interface IColor { + cool: never; + } + } +} -export { property, unit, color, funktion }; - -import { TypedCSSArray } from "./types"; - -export const tackyArg = { - ...color, - ...funktion, - ...image, - ...property, - ...unit, - media, -} as const; - -type TackyArg = typeof tackyArg; - -export const compile = (styles: TypedCSSArray): CSSObject => - styles.reduce((acc, [key, value]) => { - // Investigate TS2590 without this cast - acc[key as string] = Array.isArray(value) ? compile(value) : value; - return acc; - }, {} as CSSObject); - -export const tacky = (callback: (_: TackyArg) => TypedCSSArray): CSSObject => - compile(callback(tackyArg)); - -export type Tacky = typeof tacky; +const test = color("cool")[1]; diff --git a/packages/tacky-css/src/macro.ts b/packages/tacky-css/src/macro.ts deleted file mode 100644 index 488cb7a..0000000 --- a/packages/tacky-css/src/macro.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { createMacro } from "babel-plugin-macros"; -import { Expression } from "@babel/types"; -import generate from "@babel/generator"; -import { parseExpression } from "@babel/parser"; -import { nanoid } from "nanoid"; - -import { tackyArg as _, compile } from "./index"; -import type { Tacky } from "./index"; - -const generalError = new Error( - "Tacky has been called in a manner unsupported by its macro implementation. Please see the usage example in the README or consider using the runtime implementation instead." -); - -const macro = createMacro(({ babel, references }) => { - const t = babel.types; - const { tacky = [] } = references; - - tacky.forEach(path => { - const functionPath = path.parentPath; - if (!functionPath.isCallExpression()) { - // Tacky wasn't called - throw generalError; - } - - const args = functionPath.get("arguments"); - if (!Array.isArray(args)) { - // When does this ever happen? - throw generalError; - } - - const callback = args[0]; - if ( - !callback.isArrowFunctionExpression() && - !callback.isFunctionExpression() - ) { - // Function wasn't declared inline, or invalid call - throw generalError; - } - - const stylesArray = callback.get("body"); - if (Array.isArray(stylesArray) || !stylesArray.isArrayExpression()) { - throw generalError; - } - - const tackyParam = callback.node.params[0]; - if (tackyParam.type !== "Identifier") { - // Called in an odd way. Is there any reason to do this? - throw generalError; - } - - const tackyParamName = tackyParam.name; - const idToOldNodeMap: Record = {}; - - // First, pre-evaluate as much as possible. - stylesArray.traverse({ - CallExpression: { - exit(path) { - // Check that we're calling a Tacky helper - const callee = path.get("callee"); - if (!callee.isMemberExpression()) { - return; - } - - // Go backwards through property accesses until we reach the "root". - // This is to handle e.g. _.media.minWidth(...) - let calleeIdentifier = callee.get("object"); - while ( - !Array.isArray(calleeIdentifier) && - calleeIdentifier.isMemberExpression() - ) { - calleeIdentifier = calleeIdentifier.get("object"); - } - - if ( - Array.isArray(calleeIdentifier) || - !calleeIdentifier.isIdentifier() - ) { - return; - } - - if (calleeIdentifier.node.name !== tackyParamName) { - // Someone else's code, don't touch it - return; - } - - // For each arg, replace any non-literal expression with a string ID - path.get("arguments").forEach(argPath => { - if (argPath.isSpreadElement()) { - // not supported yet, spreading the arguments precludes - // pre-evaluation... for now - throw generalError; - } - - const node = argPath.node; - if (node.type !== "Identifier") { - return node; - } - - const id = nanoid(); - idToOldNodeMap[id] = node; - argPath.replaceWith(t.stringLiteral(id)); - }); - - // Replace Tacky param name with "this" so we can give the eval - // call the relevant context - calleeIdentifier.replaceWith(t.identifier("this")); - - // Evaluate the function and insert into AST - const code = generate(path.node).code; - - function evalInContext(): string { - return JSON.stringify(eval(code)); - } - - const evaluationResult = evalInContext.call(_); - const newNode = parseExpression(evaluationResult); - - path.replaceWith(newNode); - }, - }, - }); - - // stylesArray should no longer have runtime dependencies, so we can - // compile it to a styles object (keeping any escaped expressions). - const generatedStyles = eval(generate(stylesArray.node).code); - const compiledStyles = JSON.stringify(compile(eval(generatedStyles))); - const compiledAst = parseExpression(compiledStyles); - functionPath.replaceWith(compiledAst); - - // Go back and insert expressions back into the AST. - functionPath.traverse({ - StringLiteral(path) { - for (const [id, node] of Object.entries(idToOldNodeMap)) { - const split = path.node.value.split(id); - if (split.length === 1) { - return; - } - - if (split[0] === "" && split[1] === "") { - // No interpolation required in this case. - path.replaceWith(node); - } else { - // Otherwise, insert the expression into a template literal. - const head = t.templateElement( - { raw: split[0], cooked: split[0] }, - false - ); - const tail = t.templateElement( - { raw: split[1], cooked: split[1] }, - true - ); - - path.replaceWith(t.templateLiteral([head, tail], [node])); - } - - // Each ID should only appear once in the AST so there's no need to - // check for this one again. - delete idToOldNodeMap[id]; - - break; - } - }, - }); - }); -}); - -export const tacky: Tacky = (undefined as unknown) as Tacky; - -const macroExport: never = macro as never; -export default macroExport; diff --git a/packages/tacky-css/src/media/index.ts b/packages/tacky-css/src/media/index.ts deleted file mode 100644 index 54c5488..0000000 --- a/packages/tacky-css/src/media/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TypedCSSArray } from "../types"; -import * as mediaFeatures from "./mediaFeatures"; -import * as mediaTypes from "./mediaTypes"; -import * as operators from "./mediaTypes"; -import { - AnyMediaMember, - MediaType, - MediaTypeExpression, - MediaQuery, -} from "./types"; - -type RequiredCSSArray = [TypedCSSArray[number], ...TypedCSSArray]; - -const mediaHelpers = { - ...mediaFeatures, - ...mediaTypes, - ...operators, -} as const; - -type MediaHelpers = typeof mediaHelpers; - -export interface Media extends MediaHelpers { - ( - expressions: [AnyMediaMember, ...AnyMediaMember[]], - ...styles: RequiredCSSArray - ): [MediaQuery, TypedCSSArray]; - ( - expressions: [ - "not" | "only", - MediaType | MediaTypeExpression, - ...AnyMediaMember[] - ], - ...styles: RequiredCSSArray - ): [MediaQuery, TypedCSSArray]; -} - -export const media: Media = Object.assign( - (((expressions: string[], ...styles: unknown[]) => { - let joinedExpressions: string; - if (["not", "only"].includes(expressions[0])) { - joinedExpressions = `${expressions[0]} ${expressions - .slice(1) - .join(", ")}`; - } else { - joinedExpressions = expressions.join(", "); - } - - return [`@media ${joinedExpressions}`, styles]; - }) as unknown) as Media, - mediaHelpers -); diff --git a/packages/tacky-css/src/media/mediaFeatures.ts b/packages/tacky-css/src/media/mediaFeatures.ts deleted file mode 100644 index 74b19c4..0000000 --- a/packages/tacky-css/src/media/mediaFeatures.ts +++ /dev/null @@ -1,70 +0,0 @@ -// TODO: Ratio is tricky at the moment as it doesn't support decimal values and -// TS can't currently enforce integer values - -import { CSSLength, CSSResolution } from "../unit"; -import { MediaExpression } from "./types"; - -export const anyHover = (support: "none" | "hover"): MediaExpression => - `(any-hover: ${support})` as MediaExpression; - -export const anyPointer = ( - accuracy: "none" | "coarse" | "fine" -): MediaExpression => `(any-pointer: ${accuracy})` as MediaExpression; - -export const color = (bits?: number): MediaExpression => - `(color${bits ? `: ${bits}` : ""})` as MediaExpression; - -export const colorGamut = (gamut: "srgb" | "p3" | "rec2020"): MediaExpression => - `(colorGamut: ${gamut})` as MediaExpression; - -export const height = (value: CSSLength): MediaExpression => - `(height: ${value})` as MediaExpression; - -export const hover = (support: "none" | "hover"): MediaExpression => - `(hover: ${support})` as MediaExpression; - -export const maxColor = (bits: number): MediaExpression => - `(max-color: ${bits})` as MediaExpression; - -export const maxHeight = (value: CSSLength): MediaExpression => - `(max-height: ${value})` as MediaExpression; - -export const maxMonochrome = (bits: number): MediaExpression => - `(max-monochrome: ${bits})` as MediaExpression; - -export const maxResolution = (value: CSSResolution): MediaExpression => - `(max-resolution: ${value})` as MediaExpression; - -export const maxWidth = (value: CSSLength): MediaExpression => - `(max-width: ${value})` as MediaExpression; - -export const minColor = (bits: number): MediaExpression => - `(min-color: ${bits})` as MediaExpression; - -export const minHeight = (value: CSSLength): MediaExpression => - `(min-height: ${value})` as MediaExpression; - -export const minMonochrome = (bits: number): MediaExpression => - `(min-monochrome: ${bits})` as MediaExpression; - -export const minResolution = (value: CSSResolution): MediaExpression => - `(min-resolution: ${value})` as MediaExpression; - -export const minWidth = (value: CSSLength): MediaExpression => - `(min-width: ${value})` as MediaExpression; - -export const monochrome = (bits?: number): MediaExpression => - `(monochrome${bits ? `: ${bits}` : ""})` as MediaExpression; - -export const orientation = (value: "portrait" | "landscape"): MediaExpression => - `(orientation: ${value})` as MediaExpression; - -export const pointer = ( - accuracy: "none" | "coarse" | "fine" -): MediaExpression => `(pointer: ${accuracy})` as MediaExpression; - -export const resolution = (value: CSSResolution): MediaExpression => - `(resolution: ${value})` as MediaExpression; - -export const width = (value: CSSLength): MediaExpression => - `(width: ${value})` as MediaExpression; diff --git a/packages/tacky-css/src/media/mediaTypes.ts b/packages/tacky-css/src/media/mediaTypes.ts deleted file mode 100644 index 90149a9..0000000 --- a/packages/tacky-css/src/media/mediaTypes.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { MediaType, MediaTypeExpression, MediaExpression } from "./types"; - -export function screen(): MediaType; -export function screen( - ...expressions: [MediaExpression, ...MediaExpression[]] -): MediaTypeExpression; -export function screen( - ...expressions: unknown[] -): MediaType | MediaTypeExpression { - if (expressions.length === 0) { - return "screen" as MediaType; - } - - return `screen and ${expressions.join(" and ")}` as MediaTypeExpression; -} - -export function print(): MediaType; -export function print( - ...expressions: [MediaExpression, ...MediaExpression[]] -): MediaTypeExpression; -export function print( - ...expressions: unknown[] -): MediaType | MediaTypeExpression { - if (expressions.length === 0) { - return "print" as MediaType; - } - - return `print and ${expressions.join(" and ")}` as MediaTypeExpression; -} - -export function speech(): MediaType; -export function speech( - ...expressions: [MediaExpression, ...MediaExpression[]] -): MediaTypeExpression; -export function speech( - ...expressions: unknown[] -): MediaType | MediaTypeExpression { - if (expressions.length === 0) { - return "speech" as MediaType; - } - - return `speech and ${expressions.join(" and ")}` as MediaTypeExpression; -} diff --git a/packages/tacky-css/src/media/operators.ts b/packages/tacky-css/src/media/operators.ts deleted file mode 100644 index 4a22596..0000000 --- a/packages/tacky-css/src/media/operators.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { MediaExpression } from "./types"; - -export const and = (...expressions: MediaExpression[]): MediaExpression => - expressions.join(" and ") as MediaExpression; diff --git a/packages/tacky-css/src/media/types.ts b/packages/tacky-css/src/media/types.ts deleted file mode 100644 index 87b8538..0000000 --- a/packages/tacky-css/src/media/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TackyVariant } from "../types"; - -// e.g. (min-width: 30rem) -export type MediaExpression = TackyVariant<"media_expression">; - -// e.g. screen -export type MediaType = TackyVariant<"media_type">; - -// e.g. screen and (min-width: 30rem) -export type MediaTypeExpression = TackyVariant<"media_expression">; - -export type AnyMediaMember = MediaType | MediaExpression | MediaTypeExpression; - -// e.g. @media screen and (min-width: 30rem) -export type MediaQuery = TackyVariant<"media_query">; diff --git a/packages/tacky-css/src/property/animations.ts b/packages/tacky-css/src/property/animations.ts deleted file mode 100644 index 8420c9f..0000000 --- a/packages/tacky-css/src/property/animations.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { KnownCSSValues } from "../types"; -import { CSSTime } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const animationDelay = variantProperty< - "animationDelay", - KnownCSSValues<"animationDelay"> | CSSTime ->("animationDelay"); - -export const animationDirection = knownUnionProperty("animationDirection"); - -export const animationDuration = variantProperty< - "animationDuration", - KnownCSSValues<"animationDuration"> | CSSTime ->("animationDuration"); - -export const animationFillMode = knownUnionProperty("animationFillMode"); - -export const animationIterationCount = variantProperty< - "animationIterationCount", - KnownCSSValues<"animationIterationCount"> | CSSTime ->("animationIterationCount"); - -export const animationPlayState = knownUnionProperty("animationPlayState"); diff --git a/packages/tacky-css/src/property/backgroundsAndBorders.ts b/packages/tacky-css/src/property/backgroundsAndBorders.ts deleted file mode 100644 index 596a5db..0000000 --- a/packages/tacky-css/src/property/backgroundsAndBorders.ts +++ /dev/null @@ -1,407 +0,0 @@ -import * as CSS from "csstype"; -import { CSSColor } from "../color"; -import { CSSImage } from "../image"; -import { KnownCSSValues, TypedCSSProperties } from "../types"; -import { CSSLength, CSSLengthPercentage, Percent } from "../unit"; -import { - knownUnionProperty, - variantProperty, - PropertyTuple, - FourDimensionalArgs, - FourDimensionalProperty, -} from "../utils"; - -type BackgroundAttachmentKeyword = Exclude< - KnownCSSValues<"backgroundAttachment">, - CSS.Globals ->; - -export interface BackgroundAttachment { - (global: CSS.Globals): PropertyTuple<"backgroundAttachment">; - ( - ...attachments: [ - BackgroundAttachmentKeyword, - ...BackgroundAttachmentKeyword[] - ] - ): PropertyTuple<"backgroundAttachment">; -} - -export const backgroundAttachment: BackgroundAttachment = ( - ...args: unknown[] -) => - [ - "backgroundAttachment", - args.join(", ") as TypedCSSProperties["backgroundAttachment"], - ] as const; - -// TODO: This is just -type BackgroundClipKeyword = Exclude< - KnownCSSValues<"backgroundClip">, - CSS.Globals ->; - -export interface BackgroundClip { - (global: CSS.Globals): PropertyTuple<"backgroundClip">; - (...clip: [BackgroundClipKeyword, ...BackgroundClipKeyword[]]): PropertyTuple< - "backgroundClip" - >; -} - -export const backgroundClip: BackgroundClip = (...clip: unknown[]) => - [ - "backgroundClip", - clip.join(", ") as TypedCSSProperties["backgroundClip"], - ] as const; - -export const backgroundColor = variantProperty<"backgroundColor", CSSColor>( - "backgroundColor" -); - -export interface BackgroundImage { - (global: CSS.Globals): PropertyTuple<"backgroundImage">; - (...backgrounds: [CSSImage, ...CSSImage[]]): PropertyTuple<"backgroundImage">; -} - -export const backgroundImage: BackgroundImage = (...args: unknown[]) => - [ - "backgroundImage", - args.join(", ") as TypedCSSProperties["backgroundImage"], - ] as const; - -// TODO: This is just -type BackgroundOriginKeyword = Exclude< - KnownCSSValues<"backgroundOrigin">, - CSS.Globals ->; - -export interface BackgroundOrigin { - (global: CSS.Globals): PropertyTuple<"backgroundOrigin">; - ( - ...origin: [BackgroundOriginKeyword, ...BackgroundOriginKeyword[]] - ): PropertyTuple<"backgroundOrigin">; -} - -export const backgroundOrigin: BackgroundOrigin = (...origin: unknown[]) => - [ - "backgroundOrigin", - origin.join(", ") as TypedCSSProperties["backgroundOrigin"], - ] as const; - -type BackgroundPositionKeyword = "top" | "left" | "bottom" | "right" | "center"; - -export type BackgroundPositionArgs = - | [all: BackgroundPositionKeyword] - | [left: CSSLengthPercentage] - | [x: "left" | "right", y: "top" | "bottom" | CSSLengthPercentage] - | [y: "top" | "bottom", x: "left" | "right" | CSSLengthPercentage] - | [x: CSSLengthPercentage, y: CSSLengthPercentage] - | [ - x: "left" | "right", - xOffset: CSSLengthPercentage, - y: "top" | "bottom", - yOffset: CSSLengthPercentage - ]; - -export const backgroundPosition = ( - ...args: - | [global: CSS.Globals] - | BackgroundPositionArgs - | [BackgroundPositionArgs, ...BackgroundPositionArgs[]] -): PropertyTuple<"backgroundPosition"> => - [ - "backgroundPosition", - (Array.isArray(args[0]) - ? (args as string[][]).map(position => position.join(" ")).join(", ") - : args.join(" ")) as TypedCSSProperties["backgroundPosition"], - ] as const; - -type BackgroundRepeatShorthandKeyword = "repeat-x" | "repeat-y"; - -type BackgroundRepeatOptionKeyword = "repeat" | "space" | "round" | "no-repeat"; - -type BackgroundRepeatArgs = - | [xy: BackgroundRepeatShorthandKeyword | BackgroundRepeatOptionKeyword] - | [x: BackgroundRepeatOptionKeyword, y: BackgroundRepeatOptionKeyword]; - -export const backgroundRepeat = ( - ...args: - | [global: CSS.Globals] - | BackgroundRepeatArgs - | [BackgroundRepeatArgs, ...BackgroundRepeatArgs[]] -): PropertyTuple<"backgroundRepeat"> => - [ - "backgroundRepeat", - (Array.isArray(args[0]) - ? (args as string[][]).map(repeat => repeat.join(" ")).join(", ") - : args.join(" ")) as TypedCSSProperties["backgroundRepeat"], - ] as const; - -type BackgroundSizeKeyword = "cover" | "contain"; - -type BackgroundSizeArgs = - | [keyword: CSS.Globals | BackgroundSizeKeyword] - | [width: CSSLengthPercentage | "auto"] - | [width: CSSLengthPercentage | "auto", height: CSSLengthPercentage | "auto"]; - -export const backgroundSize = ( - ...args: BackgroundSizeArgs | [BackgroundSizeArgs, ...BackgroundSizeArgs[]] -): PropertyTuple<"backgroundSize"> => - [ - "backgroundSize", - (Array.isArray(args[0]) - ? (args as string[][]).map(position => position.join(" ")).join(", ") - : args.join(" ")) as TypedCSSProperties["backgroundSize"], - ] as const; - -type BorderStyle = TypedCSSProperties["borderTopStyle"]; - -export interface Border { - (style: BorderStyle): PropertyTuple; - (style: BorderStyle, color: CSSColor): PropertyTuple; - (width: CSSLengthPercentage, style: BorderStyle): PropertyTuple; - ( - width: CSSLengthPercentage, - style: BorderStyle, - color: CSSColor - ): PropertyTuple; -} - -export const border: Border<"border"> = (...args: unknown[]) => - ["border", args.join(" ") as TypedCSSProperties["border"]] as const; - -export const borderBottom: Border<"borderBottom"> = (...args: unknown[]) => - [ - "borderBottom", - args.join(" ") as TypedCSSProperties["borderBottom"], - ] as const; - -export const borderBottomColor = variantProperty< - "borderBottomColor", - CSSColor | "none" ->("borderBottomColor"); - -export const borderBottomLeftRadius = variantProperty< - "borderBottomLeftRadius", - CSSLengthPercentage ->("borderBottomLeftRadius"); - -export const borderBottomRightRadius = variantProperty< - "borderBottomRightRadius", - CSSLengthPercentage ->("borderBottomRightRadius"); - -export const borderBottomStyle = knownUnionProperty("borderBottomStyle"); - -export const borderBottomWidth = variantProperty< - "borderBottomWidth", - CSSLengthPercentage ->("borderBottomWidth"); - -export const borderColor = variantProperty<"borderColor", CSSColor>( - "borderColor" -); - -export const borderImageOutset: FourDimensionalProperty< - PropertyTuple<"borderImageOutset">, - CSSLength | number -> = (...args: unknown[]) => - [ - "borderImageOutset", - args.join(" ") as TypedCSSProperties["borderImageOutset"], - ] as const; - -type BorderImageRepeatKeyword = "stretch" | "repeat" | "round" | "space"; - -type BorderImageRepeatArgs = - | [allSides: BorderImageRepeatKeyword] - | [ - topAndBottom: BorderImageRepeatKeyword, - leftAndRight: BorderImageRepeatKeyword - ]; - -export const borderImageRepeat = ( - ...args: [global: CSS.Globals] | BorderImageRepeatArgs -): PropertyTuple<"borderImageRepeat"> => - [ - "borderImageRepeat", - args.join(" ") as TypedCSSProperties["borderImageRepeat"], - ] as const; - -// TODO: Allow "fill" keyword at any position -export type BorderImageSliceArgs = - | [all: number | Percent, fill?: "fill"] - | [vertical: number | Percent, horizontal: number | Percent, fill?: "fill"] - | [ - top: number | Percent, - horizontal: number | Percent, - bottom: number | Percent, - fill?: "fill" - ]; - -export const borderImageSlice = ( - ...args: [global: CSS.Globals] | BorderImageSliceArgs -): PropertyTuple<"borderImageSlice"> => - [ - "borderImageSlice", - args.join(" ") as TypedCSSProperties["borderImageSlice"], - ] as const; - -export const borderImageSource = variantProperty< - "borderImageSource", - KnownCSSValues<"borderImageSource"> | CSSImage ->("borderImageSource"); - -export const borderImageWidth = ( - ...args: FourDimensionalArgs -): PropertyTuple<"borderImageWidth"> => - [ - "borderImageWidth", - args.join(" ") as TypedCSSProperties["borderImageWidth"], - ] as const; - -export const borderLeft: Border<"borderLeft"> = (...args: unknown[]) => - ["borderLeft", args.join(" ") as TypedCSSProperties["borderLeft"]] as const; - -export const borderLeftColor = variantProperty< - "borderLeftColor", - CSSColor | "none" ->("borderLeftColor"); - -export const borderLeftStyle = knownUnionProperty("borderLeftStyle"); - -export const borderLeftWidth = variantProperty< - "borderLeftWidth", - CSSLengthPercentage ->("borderLeftWidth"); - -type BorderRadiusCorners = - | [all: CSSLengthPercentage] - | [ - topLeftAndBottomRight: CSSLengthPercentage, - topRightAndBottomLeft: CSSLengthPercentage - ] - | [ - topLeft: CSSLengthPercentage, - topRightAndBottomLeft: CSSLengthPercentage, - bottomRight: CSSLengthPercentage - ] - | [ - topLeft: CSSLengthPercentage, - topRight: CSSLengthPercentage, - bottomRight: CSSLengthPercentage, - bottomLeft: CSSLengthPercentage - ]; - -export type BorderRadiusEllipticalCorners = [ - ...BorderRadiusCorners, - ...([] | ["/", ...BorderRadiusCorners]) -]; - -export interface BorderRadius { - (global: CSS.Globals): T; - (...args: BorderRadiusEllipticalCorners): T; -} - -export const borderRadius: BorderRadius> = ( - ...args: unknown[] -) => - [ - "borderRadius", - args.join(" ") as TypedCSSProperties["borderRadius"], - ] as const; - -export const borderRight: Border<"borderRight"> = (...args: unknown[]) => - ["borderRight", args.join(" ") as TypedCSSProperties["borderRight"]] as const; - -export const borderRightColor = variantProperty< - "borderRightColor", - CSSColor | "none" ->("borderRightColor"); - -export const borderRightStyle = knownUnionProperty("borderRightStyle"); - -export const borderRightWidth = variantProperty< - "borderRightWidth", - CSSLengthPercentage ->("borderRightWidth"); - -export const borderStyle = knownUnionProperty("borderStyle"); - -export const borderTop: Border<"borderTop"> = (...args: unknown[]) => - ["borderTop", args.join(" ") as TypedCSSProperties["borderTop"]] as const; - -export const borderTopColor = variantProperty< - "borderTopColor", - CSSColor | "none" ->("borderTopColor"); - -export const borderTopLeftRadius = variantProperty< - "borderTopLeftRadius", - CSSLengthPercentage ->("borderTopLeftRadius"); - -export const borderTopRightRadius = variantProperty< - "borderTopRightRadius", - CSSLengthPercentage ->("borderTopRightRadius"); - -export const borderTopStyle = knownUnionProperty("borderTopStyle"); - -export const borderTopWidth = variantProperty< - "borderTopWidth", - CSSLengthPercentage ->("borderTopWidth"); - -export const borderWidth = ( - ...args: FourDimensionalArgs -): PropertyTuple<"borderWidth"> => - ["borderWidth", args.join(" ") as TypedCSSProperties["borderWidth"]] as const; - -export interface BoxShadow { - ( - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage, - spreadLength: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; - ( - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; - ( - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; - ( - inset: "inset", - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage, - spreadLength: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; - ( - inset: "inset", - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; - ( - inset: "inset", - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - color?: CSSColor - ): PropertyTuple<"boxShadow">; -} - -export const boxShadow: BoxShadow = ( - ...args: unknown[] -): PropertyTuple<"boxShadow"> => [ - "boxShadow", - args.join(" ") as TypedCSSProperties["boxShadow"], -]; diff --git a/packages/tacky-css/src/property/basicUserInterface.ts b/packages/tacky-css/src/property/basicUserInterface.ts deleted file mode 100644 index d3a89dd..0000000 --- a/packages/tacky-css/src/property/basicUserInterface.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CSSColor } from "../color"; -import { KnownCSSValues } from "../types"; -import { CSSLength } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const boxSizing = knownUnionProperty("boxSizing"); - -export const caretColor = variantProperty<"caretColor", CSSColor | "auto">( - "caretColor" -); - -export const cursor = knownUnionProperty("cursor"); - -export const outlineColor = variantProperty<"outlineColor", CSSColor>( - "outlineColor" -); - -export const outlineStyle = knownUnionProperty("outlineStyle"); - -export const outlineWidth = variantProperty< - "outlineWidth", - KnownCSSValues<"outlineWidth"> | CSSLength ->("outlineWidth"); - -export const resize = knownUnionProperty("resize"); - -export const textOverflow = knownUnionProperty("textOverflow"); - -export const userSelect = knownUnionProperty("userSelect"); diff --git a/packages/tacky-css/src/property/boxAlignment.ts b/packages/tacky-css/src/property/boxAlignment.ts deleted file mode 100644 index 5bc61e6..0000000 --- a/packages/tacky-css/src/property/boxAlignment.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { KnownCSSValues } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const alignContent = knownUnionProperty("alignContent"); - -export const alignItems = knownUnionProperty("alignItems"); - -export const alignSelf = knownUnionProperty("alignSelf"); - -export const gridRowGap = variantProperty< - "gridRowGap", - KnownCSSValues<"rowGap"> | CSSLengthPercentage ->("gridRowGap"); - -export const justifyContent = knownUnionProperty("justifyContent"); - -export const justifyItems = knownUnionProperty("justifyItems"); - -export const justifySelf = knownUnionProperty("justifySelf"); - -export const rowGap = variantProperty< - "rowGap", - KnownCSSValues<"rowGap"> | CSSLengthPercentage ->("rowGap"); diff --git a/packages/tacky-css/src/property/boxModel.ts b/packages/tacky-css/src/property/boxModel.ts deleted file mode 100644 index 5f33be2..0000000 --- a/packages/tacky-css/src/property/boxModel.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { FitContent } from "../function"; -import { KnownCSSValues, TypedCSSProperties } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { - knownUnionProperty, - variantProperty, - PropertyTuple, - FourDimensionalArgs, -} from "../utils"; - -export const height = variantProperty< - "height", - KnownCSSValues<"height"> | CSSLengthPercentage | FitContent ->("height"); - -// TODO: This is missing from the MDN page on Box Model module -export const margin = (...args: FourDimensionalArgs) => - ["margin", args.join(" ") as TypedCSSProperties["margin"]] as const; - -export const marginBottom = variantProperty< - "marginBottom", - CSSLengthPercentage ->("marginBottom"); - -export const marginLeft = variantProperty<"marginLeft", CSSLengthPercentage>( - "marginLeft" -); - -export const marginRight = variantProperty<"marginRight", CSSLengthPercentage>( - "marginRight" -); - -export const marginTop = variantProperty<"marginTop", CSSLengthPercentage>( - "marginTop" -); - -export const minHeight = variantProperty<"minHeight", CSSLengthPercentage>( - "minHeight" -); - -export const minWidth = variantProperty<"minWidth", CSSLengthPercentage>( - "minWidth" -); - -export const maxHeight = variantProperty<"maxHeight", CSSLengthPercentage>( - "maxHeight" -); - -export const maxWidth = variantProperty<"maxWidth", CSSLengthPercentage>( - "maxWidth" -); - -export const overscrollBehaviorBlock = knownUnionProperty( - "overscrollBehaviorBlock" -); - -export const overscrollBehaviorInline = knownUnionProperty( - "overscrollBehaviorInline" -); - -export const overscrollBehaviorX = knownUnionProperty("overscrollBehaviorX"); - -export const overscrollBehaviorY = knownUnionProperty("overscrollBehaviorY"); - -export const padding = ( - ...args: FourDimensionalArgs -): PropertyTuple<"padding"> => - ["padding", args.join(" ") as TypedCSSProperties["padding"]] as const; - -export const paddingBottom = variantProperty< - "paddingBottom", - CSSLengthPercentage ->("paddingBottom"); - -export const paddingLeft = variantProperty<"paddingLeft", CSSLengthPercentage>( - "paddingLeft" -); - -export const paddingRight = variantProperty< - "paddingRight", - CSSLengthPercentage ->("paddingRight"); - -export const paddingTop = variantProperty<"paddingTop", CSSLengthPercentage>( - "paddingTop" -); - -export const visibility = variantProperty<"visibility", CSSLengthPercentage>( - "visibility" -); - -export const width = variantProperty< - "width", - KnownCSSValues<"width"> | CSSLengthPercentage | FitContent ->("width"); diff --git a/packages/tacky-css/src/property/color.ts b/packages/tacky-css/src/property/color.ts deleted file mode 100644 index 39d55d7..0000000 --- a/packages/tacky-css/src/property/color.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CSSColor } from "../color"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const color = variantProperty<"color", CSSColor>("color"); -export const colorAdjust = knownUnionProperty("colorAdjust"); -export const opacity = knownUnionProperty("opacity"); diff --git a/packages/tacky-css/src/property/columns.ts b/packages/tacky-css/src/property/columns.ts deleted file mode 100644 index 2d82d06..0000000 --- a/packages/tacky-css/src/property/columns.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const columnCount = knownUnionProperty("columnCount"); - -export const columnFill = knownUnionProperty("columnFill"); - -export const columnSpan = knownUnionProperty("columnSpan"); diff --git a/packages/tacky-css/src/property/compositingAndBlending.ts b/packages/tacky-css/src/property/compositingAndBlending.ts deleted file mode 100644 index 1a1efbb..0000000 --- a/packages/tacky-css/src/property/compositingAndBlending.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as CSS from "csstype"; -import { KnownCSSValues, TypedCSSProperties } from "../types"; -import { knownUnionProperty, PropertyTuple } from "../utils"; - -type BackgroundBlendMode = Exclude< - KnownCSSValues<"backgroundBlendMode">, - CSS.Globals ->; - -export const backgroundBlendMode = ( - ...attachment: [CSS.Globals] | [BackgroundBlendMode, ...BackgroundBlendMode[]] -): PropertyTuple<"backgroundBlendMode"> => [ - "backgroundBlendMode", - attachment.join(", ") as TypedCSSProperties["backgroundBlendMode"], -]; - -export const isolation = knownUnionProperty("isolation"); - -export const mixBlendMode = knownUnionProperty("mixBlendMode"); diff --git a/packages/tacky-css/src/property/containment.ts b/packages/tacky-css/src/property/containment.ts deleted file mode 100644 index 4c3c2a7..0000000 --- a/packages/tacky-css/src/property/containment.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as CSS from "csstype"; -import { TackyVariant } from "../types"; -import { PropertyTuple } from "../utils"; - -// TODO: Prevent duplicate keyword arguments -export type ContainValue = TackyVariant<"contain">; - -type ContainUnaryKeyword = "none" | "strict" | "content"; - -type ContainMultipleKeyword = "size" | "layout" | "style" | "paint"; - -export interface Contain { - (global: CSS.Globals): PropertyTuple<"contain">; - (keyword: ContainUnaryKeyword | ContainMultipleKeyword): PropertyTuple< - "contain" - >; - (...keywords: ContainMultipleKeyword[]): PropertyTuple<"contain">; -} - -export const contain: Contain = (...args: unknown[]) => - ["contain", args.join(" ") as ContainValue] as const; diff --git a/packages/tacky-css/src/property/display.ts b/packages/tacky-css/src/property/display.ts deleted file mode 100644 index 4a6bb8b..0000000 --- a/packages/tacky-css/src/property/display.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -// TODO: This is incorrect -export const display = knownUnionProperty("display"); diff --git a/packages/tacky-css/src/property/flexibleBoxLayout.ts b/packages/tacky-css/src/property/flexibleBoxLayout.ts deleted file mode 100644 index e6424c8..0000000 --- a/packages/tacky-css/src/property/flexibleBoxLayout.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as CSS from "csstype"; -import { TypedCSSProperties } from "../types"; -import { knownUnionProperty, PropertyTuple } from "../utils"; - -export interface Flex { - (grow: FlexGrow): PropertyTuple<"flex">; - (grow: FlexGrow, shrink: FlexShrink): PropertyTuple<"flex">; - (grow: FlexGrow, basis: FlexBasis): PropertyTuple<"flex">; - (grow: FlexGrow, shrink: FlexShrink, basis: FlexBasis): PropertyTuple<"flex">; -} - -export const flex: Flex = (...args: unknown[]) => - ["flex", args.join(" ") as TypedCSSProperties["flex"]] as const; - -export const flexDirection = knownUnionProperty("flexDirection"); - -type FlexDirection = Exclude; - -type FlexWrap = Exclude; - -export interface FlexFlow { - (global: CSS.Globals): PropertyTuple<"flexFlow">; - (direction: FlexDirection): PropertyTuple<"flexFlow">; - (wrap: FlexWrap): PropertyTuple<"flexFlow">; - (direction: FlexDirection, wrap: FlexWrap): PropertyTuple<"flexFlow">; -} - -export const flexFlow: FlexFlow = (...args: unknown[]) => - ["flexFlow", args.join(" ") as TypedCSSProperties["flexFlow"]] as const; - -type FlexGrow = TypedCSSProperties["flexGrow"]; - -export const flexGrow = knownUnionProperty("flexGrow"); - -type FlexBasis = TypedCSSProperties["flexBasis"]; - -export const flexBasis = knownUnionProperty("flexBasis"); - -type FlexShrink = TypedCSSProperties["flexGrow"]; - -export const flexShrink = knownUnionProperty("flexShrink"); - -export const flexWrap = knownUnionProperty("flexWrap"); - -export const order = knownUnionProperty("order"); diff --git a/packages/tacky-css/src/property/fonts.ts b/packages/tacky-css/src/property/fonts.ts deleted file mode 100644 index a598001..0000000 --- a/packages/tacky-css/src/property/fonts.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { TypedCSSProperties } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, PropertyTuple, variantProperty } from "../utils"; - -// TODO: User-extendable interface -export const fontFamily = ( - ...fontFamilies: [string, ...string[]] -): PropertyTuple<"fontFamily"> => [ - "fontFamily", - fontFamilies - .map(family => (family.includes(" ") ? `"${family}"` : family)) - .join(", ") as TypedCSSProperties["fontFamily"], -]; - -export const fontKerning = knownUnionProperty("fontKerning"); - -export const fontOpticalSizing = knownUnionProperty("fontOpticalSizing"); - -export const fontSize = variantProperty<"fontSize", CSSLengthPercentage>( - "fontSize" -); - -export const fontSizeAdjust = knownUnionProperty("fontSizeAdjust"); - -export const fontStretch = knownUnionProperty("fontStretch"); - -export const fontVariantCaps = knownUnionProperty("fontVariantCaps"); - -export const fontVariantPosition = knownUnionProperty("fontVariantPosition"); - -export const lineHeight = variantProperty< - "lineHeight", - CSSLengthPercentage | number ->("lineHeight"); - -export const fontWeight = knownUnionProperty("fontWeight"); diff --git a/packages/tacky-css/src/property/fragmentation.ts b/packages/tacky-css/src/property/fragmentation.ts deleted file mode 100644 index 280fdc8..0000000 --- a/packages/tacky-css/src/property/fragmentation.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const breakAfter = knownUnionProperty("breakAfter"); - -export const breakBefore = knownUnionProperty("breakBefore"); - -export const breakInside = knownUnionProperty("breakInside"); - -export const boxDecorationBreak = knownUnionProperty("boxDecorationBreak"); - -export const orphans = knownUnionProperty("orphans"); - -export const widows = knownUnionProperty("widows"); diff --git a/packages/tacky-css/src/property/generatedContent.ts b/packages/tacky-css/src/property/generatedContent.ts deleted file mode 100644 index 0abc80d..0000000 --- a/packages/tacky-css/src/property/generatedContent.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TypedCSSProperties } from "../types"; -import { PropertyTuple } from "../utils"; - -type QuotesPair = [open: string, close: string]; - -export const quotes = (...args: QuotesPair[]): PropertyTuple<"quotes"> => [ - "quotes", - args - .flat() - .map(c => `"${c}"`) - .join(" ") as TypedCSSProperties["quotes"], -]; diff --git a/packages/tacky-css/src/property/images.ts b/packages/tacky-css/src/property/images.ts deleted file mode 100644 index 27ae2e9..0000000 --- a/packages/tacky-css/src/property/images.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const imageRendering = knownUnionProperty("imageRendering"); - -export const objectFit = knownUnionProperty("objectFit"); diff --git a/packages/tacky-css/src/property/index.ts b/packages/tacky-css/src/property/index.ts deleted file mode 100644 index beed4b3..0000000 --- a/packages/tacky-css/src/property/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from "./animations"; -export * from "./backgroundsAndBorders"; -export * from "./basicUserInterface"; -export * from "./boxAlignment"; -export * from "./boxModel"; -export * from "./color"; -export * from "./columns"; -export * from "./compositingAndBlending"; -export * from "./containment"; -export * from "./display"; -export * from "./flexibleBoxLayout"; -export * from "./fonts"; -export * from "./fragmentation"; -export * from "./generatedContent"; -export * from "./images"; -export * from "./listsAndCounters"; -export * from "./logical"; -export * from "./masking"; -export * from "./miscellaneous"; -export * from "./overflow"; -export * from "./pointerEvents"; -export * from "./positioning"; -export * from "./scrollAnchoring"; -export * from "./table"; -export * from "./text"; -export * from "./textDecoration"; -export * from "./transforms"; -export * from "./transitions"; -export * from "./view"; -export * from "./writingModes"; diff --git a/packages/tacky-css/src/property/listsAndCounters.ts b/packages/tacky-css/src/property/listsAndCounters.ts deleted file mode 100644 index 43fdf11..0000000 --- a/packages/tacky-css/src/property/listsAndCounters.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const listStylePosition = knownUnionProperty("listStylePosition"); diff --git a/packages/tacky-css/src/property/logical.ts b/packages/tacky-css/src/property/logical.ts deleted file mode 100644 index 2b14cdd..0000000 --- a/packages/tacky-css/src/property/logical.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FitContent } from "../function"; -import { KnownCSSValues } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { variantProperty } from "../utils"; - -export const blockSize = variantProperty< - "blockSize", - KnownCSSValues<"blockSize"> | CSSLengthPercentage | FitContent ->("blockSize"); - -export const inlineSize = variantProperty< - "inlineSize", - KnownCSSValues<"inlineSize"> | CSSLengthPercentage | FitContent ->("inlineSize"); diff --git a/packages/tacky-css/src/property/masking.ts b/packages/tacky-css/src/property/masking.ts deleted file mode 100644 index e802ea4..0000000 --- a/packages/tacky-css/src/property/masking.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as CSS from "csstype"; -import { CSSURL } from "../function"; -import { CSSShape } from "../shape"; -import { TypedCSSProperties } from "../types"; -import { knownUnionProperty, PropertyTuple } from "../utils"; - -// TODO: MDN defines a value which only seems to work in Firefox -export interface ClipPath { - (global: CSS.Globals): PropertyTuple<"clipPath">; - (clipSource: CSSURL): PropertyTuple<"clipPath">; - (basicShape: CSSShape): PropertyTuple<"clipPath">; -} - -export const clipPath: ClipPath = (value: unknown) => - ["clipPath", value as TypedCSSProperties["clipPath"]] as const; - -export const maskType = knownUnionProperty("maskType"); diff --git a/packages/tacky-css/src/property/miscellaneous.ts b/packages/tacky-css/src/property/miscellaneous.ts deleted file mode 100644 index bfbc111..0000000 --- a/packages/tacky-css/src/property/miscellaneous.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const all = knownUnionProperty("all"); - -export const textRendering = knownUnionProperty("textRendering"); diff --git a/packages/tacky-css/src/property/overflow.ts b/packages/tacky-css/src/property/overflow.ts deleted file mode 100644 index 6c49ca6..0000000 --- a/packages/tacky-css/src/property/overflow.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as CSS from "csstype"; -import { KnownCSSValues, TypedCSSProperties } from "../types"; -import { PropertyTuple, knownUnionProperty } from "../utils"; - -type OverflowKeyword = Exclude, CSS.Globals>; - -export interface Overflow { - (xy: OverflowKeyword | CSS.Globals): PropertyTuple<"overflow">; - (x: OverflowKeyword, y: OverflowKeyword): PropertyTuple<"overflow">; -} - -export const overflow: Overflow = (...args: unknown[]) => - ["overflow", args.join(" ") as TypedCSSProperties["overflow"]] as const; - -export const overflowX = knownUnionProperty("overflowX"); - -export const overflowY = knownUnionProperty("overflowY"); diff --git a/packages/tacky-css/src/property/pointerEvents.ts b/packages/tacky-css/src/property/pointerEvents.ts deleted file mode 100644 index 689821e..0000000 --- a/packages/tacky-css/src/property/pointerEvents.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const pointerEvents = knownUnionProperty("pointerEvents"); diff --git a/packages/tacky-css/src/property/positioning.ts b/packages/tacky-css/src/property/positioning.ts deleted file mode 100644 index 4a1efe2..0000000 --- a/packages/tacky-css/src/property/positioning.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const bottom = variantProperty<"bottom", CSSLengthPercentage | "auto">( - "bottom" -); - -export const clear = knownUnionProperty("clear"); - -export const float = knownUnionProperty("float"); - -export const left = variantProperty<"left", CSSLengthPercentage | "auto">( - "left" -); - -export const position = knownUnionProperty("position"); - -export const right = variantProperty<"right", CSSLengthPercentage | "auto">( - "right" -); - -export const top = variantProperty<"top", CSSLengthPercentage | "auto">("top"); - -export const zIndex = knownUnionProperty("zIndex"); diff --git a/packages/tacky-css/src/property/scrollAnchoring.ts b/packages/tacky-css/src/property/scrollAnchoring.ts deleted file mode 100644 index 9cef470..0000000 --- a/packages/tacky-css/src/property/scrollAnchoring.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const overflowAnchor = knownUnionProperty("overflowAnchor"); diff --git a/packages/tacky-css/src/property/table.ts b/packages/tacky-css/src/property/table.ts deleted file mode 100644 index fab7dd7..0000000 --- a/packages/tacky-css/src/property/table.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { KnownCSSValues } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const borderCollapse = knownUnionProperty("borderCollapse"); - -export const captionSide = knownUnionProperty("captionSide"); - -export const emptyCells = knownUnionProperty("emptyCells"); - -export const tableLayout = knownUnionProperty("tableLayout"); - -export const verticalAlign = variantProperty< - "verticalAlign", - KnownCSSValues<"verticalAlign"> | CSSLengthPercentage ->("verticalAlign"); diff --git a/packages/tacky-css/src/property/text.ts b/packages/tacky-css/src/property/text.ts deleted file mode 100644 index 9671717..0000000 --- a/packages/tacky-css/src/property/text.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as CSS from "csstype"; -import { KnownCSSValues } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, variantProperty } from "../utils"; - -export const hyphens = knownUnionProperty("hyphens"); - -export const letterSpacing = variantProperty< - "letterSpacing", - KnownCSSValues<"letterSpacing"> ->("letterSpacing"); - -export const lineBreak = knownUnionProperty("lineBreak"); - -export const overflowWrap = knownUnionProperty("overflowWrap"); - -export const tabSize = variantProperty< - "tabSize", - CSS.Properties["tabSize"] | CSSLengthPercentage ->("tabSize"); - -export const textAlign = knownUnionProperty("textAlign"); - -export const textAlignLast = knownUnionProperty("textAlignLast"); - -export const textJustify = knownUnionProperty("textJustify"); - -export const textTransform = knownUnionProperty("textTransform"); - -export const whiteSpace = knownUnionProperty("whiteSpace"); - -export const wordBreak = knownUnionProperty("wordBreak"); - -export const wordSpacing = variantProperty< - "wordSpacing", - KnownCSSValues<"wordSpacing"> | CSSLengthPercentage ->("wordSpacing"); diff --git a/packages/tacky-css/src/property/textDecoration.ts b/packages/tacky-css/src/property/textDecoration.ts deleted file mode 100644 index 1063ef5..0000000 --- a/packages/tacky-css/src/property/textDecoration.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as CSS from "csstype"; -import { CSSColor } from "../color"; -import { KnownCSSValues, TypedCSSProperties } from "../types"; -import { CSSLengthPercentage } from "../unit"; -import { knownUnionProperty, PropertyTuple, variantProperty } from "../utils"; - -export const textDecorationColor = variantProperty< - "textDecorationColor", - CSSColor ->("textDecorationColor"); - -type TextDecorationLineKeyword = "underline" | "overline" | "line-through"; - -// TODO: Prevent duplicate keyword arguments -export const textDecorationLineValue = ( - ...args: - | [keyword: CSS.Globals | "none"] - | [...lines: [TextDecorationLineKeyword, ...TextDecorationLineKeyword[]]] -): PropertyTuple<"textDecorationLine"> => - [ - "textDecorationLine", - args.join(" ") as TypedCSSProperties["textDecorationLine"], - ] as const; - -export const textDecorationSkipInk = knownUnionProperty( - "textDecorationSkipInk" -); - -export const textDecorationStyle = knownUnionProperty("textDecorationStyle"); - -export const textDecorationThickness = variantProperty< - "textDecorationThickness", - KnownCSSValues<"textDecorationThickness"> | CSSLengthPercentage ->("textDecorationThickness"); - -type TextShadowArgs = - | [offsetX: CSSLengthPercentage, offsetY: CSSLengthPercentage] - | [ - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - color: CSSColor - ] - | [ - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage - ] - | [ - offsetX: CSSLengthPercentage, - offsetY: CSSLengthPercentage, - blurRadius: CSSLengthPercentage, - color: CSSColor - ]; - -export interface TextShadow { - (...args: TextShadowArgs): PropertyTuple<"textShadow">; - (...args: [TextShadowArgs, ...TextShadowArgs[]]): PropertyTuple<"textShadow">; -} - -export const textShadow: TextShadow = ( - ...args: unknown[] -): PropertyTuple<"textShadow"> => [ - "textShadow", - (Array.isArray(args[0]) - ? args.map(shadow => (shadow as string[]).join(" ")).join("; ") - : args.join(" ")) as TypedCSSProperties["textShadow"], -]; diff --git a/packages/tacky-css/src/property/transforms.ts b/packages/tacky-css/src/property/transforms.ts deleted file mode 100644 index c12c860..0000000 --- a/packages/tacky-css/src/property/transforms.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const backfaceVisibility = knownUnionProperty("backfaceVisibility"); diff --git a/packages/tacky-css/src/property/transitions.ts b/packages/tacky-css/src/property/transitions.ts deleted file mode 100644 index 2a5871f..0000000 --- a/packages/tacky-css/src/property/transitions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as CSS from "csstype"; -import { AnimatableProperties } from "../animation"; -import { PropertyTuple } from "../utils"; -import { CSSTime } from "../unit"; -import { TypedCSSProperties } from "../types"; - -export const transitionDelay = ( - ...args: [keyword: CSS.Globals] | [...delay: [CSSTime, ...CSSTime[]]] -): PropertyTuple<"transitionDelay"> => - [ - "transitionDelay", - args.join(", ") as TypedCSSProperties["transitionDelay"], - ] as const; - -export const transitionDuration = ( - ...args: [keyword: CSS.Globals] | [...duration: [CSSTime, ...CSSTime[]]] -): PropertyTuple<"transitionDuration"> => - [ - "transitionDuration", - args.join(", ") as TypedCSSProperties["transitionDuration"], - ] as const; - -export const transitionProperty = ( - ...args: - | [keyword: CSS.Globals | "all" | "none"] - | [...properties: [AnimatableProperties, ...AnimatableProperties[]]] -): PropertyTuple<"transitionProperty"> => - [ - "transitionProperty", - args.join(", ") as TypedCSSProperties["transitionProperty"], - ] as const; diff --git a/packages/tacky-css/src/property/view.ts b/packages/tacky-css/src/property/view.ts deleted file mode 100644 index c89c2c9..0000000 --- a/packages/tacky-css/src/property/view.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const scrollBehavior = knownUnionProperty("scrollBehavior"); diff --git a/packages/tacky-css/src/property/writingModes.ts b/packages/tacky-css/src/property/writingModes.ts deleted file mode 100644 index 7642f46..0000000 --- a/packages/tacky-css/src/property/writingModes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { knownUnionProperty } from "../utils"; - -export const direction = knownUnionProperty("direction"); - -export const textCombineUpright = knownUnionProperty("textCombineUpright"); - -export const textOrientation = knownUnionProperty("textOrientation"); - -export const writingMode = knownUnionProperty("writingMode"); diff --git a/packages/tacky-css/src/shape.ts b/packages/tacky-css/src/shape.ts deleted file mode 100644 index 9a610aa..0000000 --- a/packages/tacky-css/src/shape.ts +++ /dev/null @@ -1,51 +0,0 @@ -// TODO: path() support -import { - BackgroundPositionArgs, - BorderRadiusEllipticalCorners, -} from "./property"; -import { TackyVariant } from "./types"; -import { CSSLengthPercentage } from "./unit"; -import { FourDimensionalArgs } from "./utils"; - -export type Inset = TackyVariant<"inset">; - -export const inset = ( - ...args: [ - ...FourDimensionalArgs, - ...([] | ["round", ...BorderRadiusEllipticalCorners]) - ] -): Inset => `inset(${args.join(" ")})` as Inset; - -type ShapeRadius = CSSLengthPercentage | "closest-side" | "farthest-side"; - -export type Circle = TackyVariant<"circle">; - -export const circle = ( - ...args: [ - ...([] | [radius: ShapeRadius]), - ...position: [] | ["at", ...BackgroundPositionArgs] - ] -): Circle => `circle(${args.join(" ")})` as Circle; - -export type Ellipse = TackyVariant<"ellipse">; - -export const ellipse = ( - ...args: [ - ...([] | [rx: ShapeRadius, ry: ShapeRadius]), - ...position: [] | ["at", ...BackgroundPositionArgs] - ] -): Ellipse => `ellipse(${args.join(" ")})` as Ellipse; - -type FillRule = "nonzero" | "evenodd"; -type Vertex = [xi: CSSLengthPercentage, yi: CSSLengthPercentage]; - -export type Polygon = TackyVariant<"polygon">; - -export const polygon = ( - ...args: [...([] | [fill: FillRule]), ...vertices: [Vertex, ...Vertex[]]] -): Polygon => - `polygon(${(args as (string | string[])[]) - .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) - .join(", ")})` as Polygon; - -export type CSSShape = Inset | Circle | Ellipse | Polygon; diff --git a/packages/tacky-css/src/types.ts b/packages/tacky-css/src/types.ts index fd79ee6..80b1d0e 100644 --- a/packages/tacky-css/src/types.ts +++ b/packages/tacky-css/src/types.ts @@ -1,244 +1,31 @@ -import * as CSS from "csstype"; - -import { MediaQuery } from "./media/types"; - -// These properties can be safely expressed without custom interfaces -type PrimitiveUnionProperties = Required< - Pick< - CSS.Properties, - | "all" - | "backfaceVisibility" - | "borderBottomStyle" - | "borderCollapse" - | "borderLeftStyle" - | "borderRightStyle" - | "borderStyle" - | "borderTopStyle" - | "boxDecorationBreak" - | "boxSizing" - | "breakAfter" - | "breakBefore" - | "breakInside" - | "captionSide" - | "clear" - | "colorAdjust" - | "columnCount" - | "columnFill" - | "columnSpan" - | "cursor" - | "direction" - | "display" - | "emptyCells" - | "flexBasis" - | "flexDirection" - | "flexGrow" - | "flexShrink" - | "flexWrap" - | "float" - | "fontKerning" - | "fontOpticalSizing" - | "fontSizeAdjust" - | "fontVariantCaps" - | "fontVariantPosition" - | "fontWeight" - | "hyphens" - | "imageRendering" - | "isolation" - | "lineBreak" - | "listStylePosition" - | "maskType" - | "mixBlendMode" - | "objectFit" - | "order" - | "orphans" - | "overflowAnchor" - | "overflowWrap" - | "overflowX" - | "overflowY" - | "overscrollBehaviorBlock" - | "overscrollBehaviorInline" - | "overscrollBehaviorX" - | "overscrollBehaviorY" - | "pageBreakAfter" - | "pageBreakBefore" - | "pageBreakInside" - | "pointerEvents" - | "position" - | "resize" - | "scrollBehavior" - | "tableLayout" - | "textAlign" - | "textAlignLast" - | "textDecorationSkipInk" - | "textDecorationStyle" - | "textJustify" - | "textOrientation" - | "textRendering" - | "textTransform" - | "userSelect" - | "visibility" - | "whiteSpace" - | "widows" - | "wordBreak" - | "wordWrap" - | "writingMode" - | "zIndex" - > ->; - -export type KnownCSSValues< - T extends keyof CSS.Properties, - U extends CSS.Properties[T] = CSS.Properties[T] -> = U extends string | number | symbol - ? { - [K in U]: string extends K ? never : number extends K ? never : K; - } extends { [_ in U]: infer U } - ? // eslint-disable-next-line @typescript-eslint/ban-types - {} extends U - ? never - : U - : never - : never; - -// These properties are typed as a union containing a member that would -// preclude maximal type safety e.g. string & {} -type UnionOfStringProperties = { - [P in - | "alignContent" - | "alignItems" - | "alignSelf" - | "justifyContent" - | "justifyItems" - | "justifySelf" - | "outlineStyle" - - // "Digits" syntax has minimal support - | "textCombineUpright" - - // Non-keyword syntaxes have minimal support - | "textOverflow" - - // TODO: Multiple animations - | "animationDirection" - | "animationFillMode" - | "animationPlayState" - - // Level 4 allows percentile values for these properties - | "fontStretch" - | "opacity"]: KnownCSSValues

; -}; - -// Terminology taken from ReasonML - the idea is something like a variant that -// takes a single string arg, so the type system can distinguish strings from -// different sources even if their values aren't known. -// -// TODO: In TS 4.1 use -// export type TackyVariant = string & {[_ in T as `_tacky_id_${T}`]: never }; -// This is safer as users will be less likely to access the "brand" property -// (which is removed by the Babel macro) -export type TackyVariant = string & { _tacky_id: T }; - -type TackyVariantRecord = { - [Property in T]: TackyVariant; -}; - -export type TypedCSSProperties = PrimitiveUnionProperties & - UnionOfStringProperties & - TackyVariantRecord< - | "backgroundAttachment" - | "backgroundBlendMode" - | "backgroundClip" - | "backgroundColor" - | "backgroundImage" - | "backgroundOrigin" - | "backgroundPosition" - | "backgroundRepeat" - | "backgroundSize" - | "blockSize" - | "border" - | "borderBottom" - | "borderBottomColor" - | "borderBottomLeftRadius" - | "borderBottomRightRadius" - | "borderBottomWidth" - | "borderColor" - | "borderImageOutset" - | "borderImageRepeat" - | "borderImageSlice" - | "borderImageSource" - | "borderImageWidth" - | "borderLeft" - | "borderLeftColor" - | "borderLeftWidth" - | "borderRadius" - | "borderRight" - | "borderRightColor" - | "borderRightWidth" - | "borderTop" - | "borderTopColor" - | "borderTopLeftRadius" - | "borderTopRightRadius" - | "borderTopWidth" - | "borderWidth" - | "bottom" - | "boxShadow" - | "caretColor" - | "clipPath" - | "color" - | "contain" - | "flex" - | "flexFlow" - | "fontFamily" - | "fontSize" - | "gridRowGap" - | "height" - | "inlineSize" - | "left" - | "letterSpacing" - | "lineHeight" - | "margin" - | "marginBottom" - | "marginLeft" - | "marginRight" - | "marginTop" - | "maxHeight" - | "maxWidth" - | "minHeight" - | "minWidth" - | "outlineColor" - | "outlineWidth" - | "overflow" - | "padding" - | "paddingBottom" - | "paddingLeft" - | "paddingRight" - | "paddingTop" - | "quotes" - | "right" - | "rowGap" - | "shapeMargin" - | "tabSize" - | "textDecorationColor" - | "textDecorationLine" - | "textDecorationThickness" - | "textShadow" - | "top" - | "transitionDelay" - | "transitionDuration" - | "transitionProperty" - | "verticalAlign" - | "width" - | "wordSpacing" - // TODO multiple animations =============================================== - | "animationDelay" - | "animationDuration" - | "animationIterationCount" - // ======================================================================== - >; - -export type TypedCSSArray = ( - | { - [P in keyof TypedCSSProperties]: readonly [P, TypedCSSProperties[P]]; - }[keyof TypedCSSProperties] - | [MediaQuery, TypedCSSArray] -)[]; +namespace CSS { + export type Global = "initial" | "unset"; + export type Clear = "left" | "right"; +} + +namespace ValueEnum { + export enum Clear { + _ = "", + } +} + +interface Value { + clear: string & ValueEnum.Clear; +} + +export type PropertyTuple = [T, Value[T]]; + +namespace Property { + export interface Clear { + (clear: CSS.Clear): PropertyTuple<"clear">; + (global: CSS.Global): PropertyTuple<"clear">; + } +} + +namespace Property { + export interface Clear { + (example: "primary"): PropertyTuple<"clear">; + } +} + +export const clear: Property.Clear = (arg: unknown) => ["clear", arg] as any; diff --git a/packages/tacky-css/src/unit.ts b/packages/tacky-css/src/unit.ts deleted file mode 100644 index 541e2b1..0000000 --- a/packages/tacky-css/src/unit.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TackyVariant } from "./types"; - -// Length -export type Rem = TackyVariant<"rem">; -export const rem = (magnitude: number): Rem => `${magnitude}rem` as Rem; - -export type Em = TackyVariant<"em">; -export const em = (magnitude: number): Em => `${magnitude}em` as Em; - -export type Px = TackyVariant<"px">; -export const px = (magnitude: number): Px => `${magnitude}px` as Px; - -export type Percent = TackyVariant<"percent">; -export const percent = (magnitude: number): Percent => - `${magnitude}%` as Percent; - -// Time -export type Ms = TackyVariant<"ms">; -export const ms = (magnitude: number): Ms => `${magnitude}ms` as Ms; - -export type S = TackyVariant<"s">; -export const s = (magnitude: number): S => `${magnitude}s` as S; - -// Angle -export type Deg = TackyVariant<"deg">; -export const deg = (magnitude: number): Deg => `${magnitude}deg` as Deg; - -export type Rad = TackyVariant<"rad">; -export const rad = (magnitude: number): Rad => `${magnitude}rad` as Rad; - -export type Grad = TackyVariant<"grad">; -export const grad = (magnitude: number): Grad => `${magnitude}grad` as Grad; - -export type Turn = TackyVariant<"turn">; -export const turn = (magnitude: number): Turn => `${magnitude}turn` as Turn; - -// Resolution -export type Dpi = TackyVariant<"dpi">; -export const dpi = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; - -export type Dpcm = TackyVariant<"dpcm">; -export const dpcm = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; - -export type Dppx = TackyVariant<"dppx">; -export const dppx = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; - -export type CSSLength = Rem | Em | Px | 0; -export type CSSLengthPercentage = CSSLength | Percent; -export type CSSTime = Ms | S | 0; -export type CSSAngle = Deg | Rad | Grad | Turn; -export type CSSResolution = Dpi | Dpcm | Dppx; diff --git a/packages/tacky-css/src/utils.ts b/packages/tacky-css/src/utils.ts deleted file mode 100644 index 9a0dab1..0000000 --- a/packages/tacky-css/src/utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as CSS from "csstype"; -import { TypedCSSProperties } from "./types"; -import { CSSLengthPercentage } from "./unit"; - -export type PropertyTuple = readonly [ - T, - TypedCSSProperties[T] -]; - -// A property which has a single value that can be represented by a union of -// known values. -export const knownUnionProperty = ( - property: T -) => (value: TypedCSSProperties[T] | CSS.Globals): PropertyTuple => - // TODO: Investigate TS2590 without this cast - ([property, value] as unknown) as [T, TypedCSSProperties[T]]; - -// A property which has a single value that cannot be represented by a union of -// known values. -export const variantProperty = < - T extends keyof TypedCSSProperties, - U extends unknown ->( - property: T -) => (value: U | CSS.Globals): [T, TypedCSSProperties[T]] => - // TODO: Investigate TS2590 without this cast - ([property, value] as unknown) as [T, TypedCSSProperties[T]]; - -export interface FourDimensionalProperty { - (global: CSS.Globals): ReturnType; - (all: ValueType): ReturnType; - (vertical: ValueType, horizontal: ValueType): ReturnType; - (top: ValueType, right: ValueType, bottom: ValueType): ReturnType; - ( - top: ValueType, - right: ValueType, - bottom: ValueType, - left: ValueType - ): ReturnType; -} - -export type FourDimensionalArgs = - | [all: Value] - | [vertical: Value, horizontal: Value] - | [top: Value, right: Value, bottom: Value] - | [top: Value, right: Value, bottom: Value, left: Value]; diff --git a/packages/tacky-css/tsconfig.json b/packages/tacky-css/tsconfig.json index 71c2513..fa94449 100644 --- a/packages/tacky-css/tsconfig.json +++ b/packages/tacky-css/tsconfig.json @@ -1,6 +1,6 @@ { - "include": ["./src/*"], - "exclude": ["./node_modules"], + //"include": ["./src/*", "./typings/*"], + //"exclude": ["./node_modules"], "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ @@ -10,6 +10,8 @@ "target": "ES5", "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": ["es2019", "dom"], + "downlevelIteration": true, + "resolveJsonModule": true, // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/packages/tacky-css/typings/mdn-browser-compat-data.d.ts b/packages/tacky-css/typings/mdn-browser-compat-data.d.ts new file mode 100644 index 0000000..08fdd28 --- /dev/null +++ b/packages/tacky-css/typings/mdn-browser-compat-data.d.ts @@ -0,0 +1,139 @@ +declare namespace MDN { + interface Support { + version_added: boolean | string | null; + version_removed?: boolean | string | null; + prefix?: string; + alternative_name?: string; + partial_implementation: boolean; + notes: string | string[]; + flags?: any[]; + } + + type Browsers = + | 'webview_android' + | 'chrome' + | 'chrome_android' + | 'edge' + | 'edge_mobile' + | 'firefox' + | 'firefox_android' + | 'ie' + | 'nodejs' + | 'opera' + | 'opera_android' + | 'qq_android' + | 'safari' + | 'safari_ios' + | 'samsunginternet_android' + | 'uc_android' + | 'uc_chinese_android'; + + type Supports = { [browser in Browsers]: Support | Support[] }; + + interface Compat { + support: Supports; + mdn_url?: string; + description?: string; + status?: { + experimental: boolean; + standard_track: boolean; + deprecated: boolean; + }; + } + + type CompatDataInner = { + __compat: MDN.Compat; + }; + + type CompatData = CompatDataInner & Record; + + interface AtRulesCompat { + css: { + 'at-rules': { + [rule: string]: CompatData; + }; + }; + } + + interface PropertiesCompat { + css: { + properties: { + [property: string]: CompatData; + }; + }; + } + + interface SelectorsCompat { + css: { + selectors: { + [selector: string]: CompatData; + }; + }; + } + + interface TypesCompat { + css: { + types: { + [type: string]: CompatData; + }; + }; + } + + interface GlobalAttributesCompat { + html: { + global_attributes: { + [attribute: string]: CompatData; + }; + }; + } + + interface AttributesCompat { + [htmlOrSvg: string]: { + [attributesOrElements: string]: { + [categoryOrElement: string]: { + [attribute: string]: CompatData; + }; + }; + }; + } +} + +declare module 'browser-compat-data/css/at-rules/*.json' { + var atRules: MDN.AtRulesCompat; + export = atRules; +} + +declare module 'browser-compat-data/css/properties/*.json' { + var properties: MDN.PropertiesCompat; + export = properties; +} + +declare module 'browser-compat-data/css/selectors/*.json' { + var selectors: MDN.SelectorsCompat; + export = selectors; +} + +declare module 'browser-compat-data/css/types/*.json' { + var types: MDN.TypesCompat; + export = types; +} + +declare module 'mdn-browser-compat-data/html/global_attributes.json' { + var types: MDN.GlobalAttributesCompat; + export = types; +} + +declare module 'mdn-browser-compat-data/html/elements/*.json' { + var types: MDN.AttributesCompat; + export = types; +} + +declare module 'mdn-browser-compat-data/svg/attributes/*.json' { + var types: MDN.AttributesCompat; + export = types; +} + +declare module 'mdn-browser-compat-data/svg/elements/*.json' { + var types: MDN.AttributesCompat; + export = types; +} diff --git a/packages/tacky-css/typings/mdn-data.d.ts b/packages/tacky-css/typings/mdn-data.d.ts new file mode 100644 index 0000000..f46d917 --- /dev/null +++ b/packages/tacky-css/typings/mdn-data.d.ts @@ -0,0 +1,103 @@ +declare namespace MDN { + interface Property { + syntax: string; + media: string; + inherited: boolean; + animationType: string; + percentages: string; + groups: string[]; + initial: string; + appliesto: string; + computed: string | string[]; + order: string; + status: string; + mdn_url?: string; + } + + interface Properties { + [property: string]: Property; + } + + interface Syntax { + syntax: string; + } + + interface Syntaxes { + [property: string]: Syntax; + } + + interface Selector { + syntax: string; + groups: string[]; + status: string; + } + + interface Selectors { + [selector: string]: Selector; + } + + interface Types { + [name: string]: any; + } + + interface Descriptor { + syntax: string; + media: string; + percentages: string | string[]; + initial: string | string[]; + computed: string | string[]; + order: string; + } + + interface Descriptors { + [descriptor: string]: Descriptor; + } + + interface AtRule { + syntax: string; + interfaces: string[]; + groups: string[]; + descriptors: Descriptors; + status: string; + } + + interface AtRules { + [name: string]: AtRule; + } + + interface L10N { + [key: string]: { + 'en-US': string; + }; + } +} + +declare module 'mdn-data/css/properties.json' { + var properties: MDN.Properties; + export = properties; +} + +declare module 'mdn-data/css/syntaxes.json' { + var syntaxes: MDN.Syntaxes; + export = syntaxes; +} + +declare module 'mdn-data/css/selectors.json' { + var selectors: MDN.Selectors; + export = selectors; +} + +declare module 'mdn-data/css/types.json' { + var types: MDN.Types; + export = types; +} + +declare module 'mdn-data/css/at-rules.json' { + var atRules: MDN.AtRules; + export = atRules; +} + +declare module 'mdn-data/l10n/css.json' { + var l10n: MDN.L10N; + export = l10n; +} diff --git a/packages/tacky-css/utils.ts b/packages/tacky-css/utils.ts new file mode 100644 index 0000000..9387932 --- /dev/null +++ b/packages/tacky-css/utils.ts @@ -0,0 +1,74 @@ +import { spawn, SpawnOptions } from 'child_process'; +import { writeFile } from 'fs'; +import { get, RequestOptions } from 'https'; +import { createInterface } from 'readline'; + +export const ROOT_DIR = __dirname; +export const TYPESCRIPT_FILENAME = 'index.d.ts'; +export const FLOW_FILENAME = 'index.js.flow'; + +export function writeFileAsync(filename: string, content: string) { + return new Promise((resolve, reject) => { + writeFile(filename, content, 'utf-8', error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +} + +export function spawnAsync(command: string, optionsOrArg: SpawnOptions | string, ...args: string[]): Promise { + let options: SpawnOptions | undefined; + + if (typeof optionsOrArg === 'string') { + args.unshift(optionsOrArg); + } else { + options = optionsOrArg; + } + + return new Promise((resolve, reject) => { + const cp = spawn(command, args, { + cwd: ROOT_DIR, + ...options, + }); + + if (cp.stdout) { + let data = ''; + cp.stdout.on('data', chunk => (data += chunk)); + cp.on('close', code => (code === 0 ? resolve(data) : reject(data))); + } else { + cp.on('close', code => (code === 0 ? resolve() : reject())); + } + + cp.on('error', reject); + }); +} + +export function getJsonAsync(url: RequestOptions): Promise { + return new Promise((resolve, reject) => { + const req = get(url, res => { + let data = ''; + res.on('data', chunk => (data += chunk)); + res.on('end', () => { + try { + const json = JSON.parse(data); + resolve(json); + } catch (e) { + reject(e); + } + }); + }); + req.on('error', reject); + req.end(); + }); +} + +const readline = createInterface(process.stdin, process.stdout); + +export function questionAsync(message: string): Promise { + return new Promise(resolve => { + readline.question(message, resolve); + }); +} diff --git a/packages/tacky-css/yarn.lock b/packages/tacky-css/yarn.lock index 3aab21a..a5909bc 100644 --- a/packages/tacky-css/yarn.lock +++ b/packages/tacky-css/yarn.lock @@ -108,6 +108,27 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@types/babel-plugin-macros@^2.8.2": version "2.8.2" resolved "https://registry.yarnpkg.com/@types/babel-plugin-macros/-/babel-plugin-macros-2.8.2.tgz#4c56035db90b3117bc22b02412b83ed264dc0874" @@ -148,11 +169,100 @@ dependencies: "@babel/types" "^7.3.0" +"@types/caseless@*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + +"@types/chalk@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" + integrity sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw== + dependencies: + chalk "*" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/jsdom@^16.2.4": + version "16.2.4" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.4.tgz#527ca99943e00561ca4056b1904fd5f4facebc3b" + integrity sha512-RssgLa5ptjVKRkHho/Ex0+DJWkVsYuV8oh2PSG3gKxFp8n/VNyB7kOrZGQkk2zgPlcBkIKOItUc/T5BXit9uhg== + dependencies: + "@types/node" "*" + "@types/parse5" "*" + "@types/tough-cookie" "*" + +"@types/node@*", "@types/node@^14.10.2": + version "14.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.2.tgz#9b47a2c8e4dabd4db73b57e750b24af689600514" + integrity sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/parse5@*": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + +"@types/request@^2.48.5": + version "2.48.5" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0" + integrity sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + +"@types/tough-cookie@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== + +"@types/turndown@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.0.tgz#2b763b36f783e4e237cea62cdc8f8592b72b9285" + integrity sha512-Y7KZn6SfSv1vzjByJGijrM9w99HoUbLiAgYwe+sGH6RYi5diIGLYOcr4Z1YqR2biFs9K5PnxKv/Fbkmh57pvzg== + +abab@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + +ajv@^6.12.3: + version "6.12.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" + integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -160,6 +270,46 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" @@ -169,11 +319,48 @@ babel-plugin-macros@^2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@*, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -190,11 +377,35 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" @@ -206,11 +417,79 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^3.0.2, csstype@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +decimal.js@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" + integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -223,11 +502,149 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escodegen@^1.14.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +extend@3.0.2, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -235,6 +652,29 @@ hoist-non-react-statics@^3.3.1: dependencies: react-is "^16.7.0" +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + import-fresh@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -243,36 +683,210 @@ import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^16.2.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" + integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== + dependencies: + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + ws "^7.2.3" + xml-name-validator "^3.0.0" + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + lodash@^4.17.19: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +"mdn-browser-compat-data@git+https://github.com/mdn/browser-compat-data.git#bce11b3da31a3b3425280730360ee42d7cc55328": + version "1.0.35" + resolved "git+https://github.com/mdn/browser-compat-data.git#bce11b3da31a3b3425280730360ee42d7cc55328" + dependencies: + extend "3.0.2" + +"mdn-data@git+https://github.com/mdn/data.git#3191d4b889364bca969dfcc84e490928d84710ef": + version "2.0.11" + resolved "git+https://github.com/mdn/data.git#3191d4b889364bca969dfcc84e490928d84710ef" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + nanoid@^3.1.12: version "3.1.12" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -290,6 +904,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -300,6 +919,36 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -310,6 +959,48 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -322,6 +1013,66 @@ resolve@^1.12.0: dependencies: path-parse "^1.0.6" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + stylis@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.2.tgz#84247b15c20688604b7a77db02b7fc071a127194" @@ -334,17 +1085,188 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" + +ts-node@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" + integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +turndown@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-6.0.0.tgz#c083d6109a9366be1b84b86b20af09140ea4b413" + integrity sha512-UVJBhSyRHCpNKtQ00mNWlYUM/i+tcipkb++F0PrOpt0L7EhNd0AX9mWEpL2dRFBu7LWXMp4HgAMA4OeKKnN7og== + dependencies: + jsdom "^16.2.0" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + typescript@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^8.0.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.2.2.tgz#85e7f9795108b53d554cec640b2e8aee2a0d4bfd" + integrity sha512-PcVnO6NiewhkmzV0qn7A+UZ9Xx4maNTI+O+TShmfE4pqjoCMwUMjkvoNhNHPTvgR7QH9Xt3R13iHuWy2sToFxQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +ws@^7.2.3: + version "7.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" + integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From 4793fba06714b6a781b49ede51a663433fd77cef Mon Sep 17 00:00:00 2001 From: Tom Picton Date: Fri, 18 Sep 2020 12:16:14 -0700 Subject: [PATCH 2/3] Augmentation --- .gitignore | 1 + packages/tacky-css/build.ts | 35 ++++++++------- packages/tacky-css/csstype/declarator.ts | 4 +- packages/tacky-css/csstype/syntax/typer.ts | 46 ++++++++++++++++++- packages/tacky-css/csstype/utils/output.ts | 8 +++- packages/tacky-css/src/augment.ts | 18 ++++++++ packages/tacky-css/src/color.ts | 15 +++++++ packages/tacky-css/src/index.ts | 11 ----- packages/tacky-css/src/types.ts | 40 ++++------------- packages/tacky-css/src/unit.ts | 51 ++++++++++++++++++++++ 10 files changed, 165 insertions(+), 64 deletions(-) create mode 100644 packages/tacky-css/src/augment.ts create mode 100644 packages/tacky-css/src/color.ts delete mode 100644 packages/tacky-css/src/index.ts create mode 100644 packages/tacky-css/src/unit.ts diff --git a/.gitignore b/.gitignore index f707001..aa9237d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ tags .build yarn-error.log lib +packages/tacky-css/src/generated.ts diff --git a/packages/tacky-css/build.ts b/packages/tacky-css/build.ts index 23653e0..82a9595 100644 --- a/packages/tacky-css/build.ts +++ b/packages/tacky-css/build.ts @@ -1,6 +1,6 @@ // import * as chokidar from 'chokidar'; import * as path from "path"; -// import * as prettier from 'prettier'; +import * as prettier from "prettier"; import { writeFileAsync } from "./utils"; // import { runCLI } from 'jest'; @@ -17,8 +17,9 @@ async function trigger(): Promise { } } const { default: generateTypescript } = await import("./csstype/typescript"); - const result = await generateTypescript(); - await writeFileAsync("./src/generated.ts", result); + const unformatted = await generateTypescript(); + const formatted = await format(unformatted); + await writeFileAsync("./src/generated.ts", formatted); } // if (process.argv.includes('--start')) { @@ -79,19 +80,21 @@ async function trigger(): Promise { // return { unformattedFlow: await generateFlow(), unformattedTypescript: await generateTypescript() }; // } -// async function format(output: string, parser: prettier.BuiltInParserName) { -// const options = await prettier.resolveConfig(path.join(__dirname, '.prettierrc')); -// try { -// return prettier.format(output, { -// ...options, -// printWidth: 180, -// singleQuote: false, -// parser, -// }); -// } catch (e) { -// throw new Error(e); -// } -// } +async function format(output: string): Promise { + const options = await prettier.resolveConfig( + path.join(__dirname, ".prettierrc") + ); + try { + return prettier.format(output, { + ...options, + printWidth: 180, + singleQuote: false, + parser: "typescript", + }); + } catch (e) { + throw new Error(e); + } +} // function testing() { // return runCLI({ testMatch: ['**/__tests__/dist.*.ts'] } as any, [__dirname]); diff --git a/packages/tacky-css/csstype/declarator.ts b/packages/tacky-css/csstype/declarator.ts index 354ebde..008b331 100644 --- a/packages/tacky-css/csstype/declarator.ts +++ b/packages/tacky-css/csstype/declarator.ts @@ -131,8 +131,6 @@ export async function declarator(minTypesInDataTypes: number) { ]) ); - debugger; - function getGenericsFrom(types: MixedType[]): IGenerics[] { let hasLength = false; let hasTime = false; @@ -298,7 +296,7 @@ export async function declarator(minTypesInDataTypes: number) { const generics = getGenericsFrom(property.types); const validTypes = property.types.filter( - ({ type }) => ![Type.String, Type.Length, Type.Time].includes(type) + ({ type }) => ![Type.String].includes(type) ); // Some properties are prefixed and share the same type so we diff --git a/packages/tacky-css/csstype/syntax/typer.ts b/packages/tacky-css/csstype/syntax/typer.ts index 412ef1c..7e0767f 100644 --- a/packages/tacky-css/csstype/syntax/typer.ts +++ b/packages/tacky-css/csstype/syntax/typer.ts @@ -24,10 +24,12 @@ export enum Type { String, Number, Never, + LengthPercentage, + Percentage } interface IBasic { - type: Type.String | Type.Number | Type.Length | Type.Time | Type.Never; + type: Type.String | Type.Number | Type.Length | Type.Time | Type.Never | Type.LengthPercentage | Type.Percentage; } export interface IDataType { @@ -54,6 +56,8 @@ export type ResolvedType = TypeType; let getBasicDataTypes = () => { const types = Object.keys(cssTypes).reduce<{ [name: string]: IBasic }>((dataTypes, name) => { + console.log(name); + switch (name) { case 'number': case 'integer': @@ -66,6 +70,16 @@ let getBasicDataTypes = () => { type: Type.Length, }; break; + case 'length-percentage': + dataTypes[name] = { + type: Type.LengthPercentage, + }; + break; + case 'percentage': + dataTypes[name] = { + type: Type.Percentage, + }; + break; case 'time': dataTypes[name] = { type: Type.Time, @@ -191,6 +205,32 @@ function addLength(types: TypeType[]): T return types; } +function addLengthPercentage(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.LengthPercentage)) { + return [ + ...types, + { + type: Type.LengthPercentage, + }, + ]; + } + + return types; +} + +function addPercentage(types: TypeType[]): TypeType[] { + if (types.every(type => type.type !== Type.Percentage)) { + return [ + ...types, + { + type: Type.Percentage, + }, + ]; + } + + return types; +} + function addTime(types: TypeType[]): TypeType[] { if (types.every(type => type.type !== Type.Time)) { return [ @@ -315,6 +355,10 @@ export function addType( switch (type.type) { case Type.Length: return addLength(types); + case Type.Percentage: + return addPercentage(types); + case Type.LengthPercentage: + return addLengthPercentage(types); case Type.Time: return addTime(types); case Type.String: diff --git a/packages/tacky-css/csstype/utils/output.ts b/packages/tacky-css/csstype/utils/output.ts index 9a2e3a7..35d039e 100644 --- a/packages/tacky-css/csstype/utils/output.ts +++ b/packages/tacky-css/csstype/utils/output.ts @@ -42,9 +42,13 @@ export function createStringifyType( return createTypeAliasName(type, currentNamespace) + stringifyGenerics(type.generics); } case Type.Length: - return lengthGeneric.name; + return "ManualDataType." + lengthGeneric.name; + case Type.Percentage: + return "ManualDataType." + "TPercentage"; + case Type.LengthPercentage: + return "ManualDataType." + "TLengthPercentage"; case Type.Time: - return timeGeneric.name; + return "ManualDataType." + timeGeneric.name; } }) as (type: DeclarableType) => string; diff --git a/packages/tacky-css/src/augment.ts b/packages/tacky-css/src/augment.ts new file mode 100644 index 0000000..d221663 --- /dev/null +++ b/packages/tacky-css/src/augment.ts @@ -0,0 +1,18 @@ +import { color, marginLeft } from "./generated"; +import { CSSColor } from "./color"; +import { CSSLength, CSSLengthPercentage, Percent, CSSTime } from "./unit"; + +declare module "./generated" { + namespace ManualDataType { + export interface IColor { + cool: never; + } + + export type TLength = CSSLength; + export type TLengthPercentage = CSSLengthPercentage; + export type TPercentage = Percent; + export type TTime = CSSTime; + } +} + +const test = color("cool")[1]; diff --git a/packages/tacky-css/src/color.ts b/packages/tacky-css/src/color.ts new file mode 100644 index 0000000..3c57c4a --- /dev/null +++ b/packages/tacky-css/src/color.ts @@ -0,0 +1,15 @@ +import { TackyVariant } from "./types"; + +export type Rgb = TackyVariant<"rgb">; +export const rgb = (red: number, green: number, blue: number): Rgb => + `rgb(${red}, ${green}, ${blue})` as Rgb; + +export type Rgba = TackyVariant<"rgba">; +export const rgba = ( + red: number, + green: number, + blue: number, + alpha: number +): Rgba => `rgba(${red}, ${green}, ${blue}, ${alpha})` as Rgba; + +export type CSSColor = Rgb | Rgba; diff --git a/packages/tacky-css/src/index.ts b/packages/tacky-css/src/index.ts deleted file mode 100644 index eee76b4..0000000 --- a/packages/tacky-css/src/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { color } from "./generated"; - -declare module "./generated" { - namespace ManualDataType { - export interface IColor { - cool: never; - } - } -} - -const test = color("cool")[1]; diff --git a/packages/tacky-css/src/types.ts b/packages/tacky-css/src/types.ts index 80b1d0e..638acd5 100644 --- a/packages/tacky-css/src/types.ts +++ b/packages/tacky-css/src/types.ts @@ -1,31 +1,9 @@ -namespace CSS { - export type Global = "initial" | "unset"; - export type Clear = "left" | "right"; -} - -namespace ValueEnum { - export enum Clear { - _ = "", - } -} - -interface Value { - clear: string & ValueEnum.Clear; -} - -export type PropertyTuple = [T, Value[T]]; - -namespace Property { - export interface Clear { - (clear: CSS.Clear): PropertyTuple<"clear">; - (global: CSS.Global): PropertyTuple<"clear">; - } -} - -namespace Property { - export interface Clear { - (example: "primary"): PropertyTuple<"clear">; - } -} - -export const clear: Property.Clear = (arg: unknown) => ["clear", arg] as any; +// Terminology taken from ReasonML - the idea is something like a variant that +// takes a single string arg, so the type system can distinguish strings from +// different sources even if their values aren't known. +// +// TODO: In TS 4.1 use +// export type TackyVariant = string & {[_ in T as `_tacky_id_${T}`]: never }; +// This is safer as users will be less likely to access the "brand" property +// (which is removed by the Babel macro) +export type TackyVariant = string & { _tacky_id: T }; diff --git a/packages/tacky-css/src/unit.ts b/packages/tacky-css/src/unit.ts new file mode 100644 index 0000000..541e2b1 --- /dev/null +++ b/packages/tacky-css/src/unit.ts @@ -0,0 +1,51 @@ +import { TackyVariant } from "./types"; + +// Length +export type Rem = TackyVariant<"rem">; +export const rem = (magnitude: number): Rem => `${magnitude}rem` as Rem; + +export type Em = TackyVariant<"em">; +export const em = (magnitude: number): Em => `${magnitude}em` as Em; + +export type Px = TackyVariant<"px">; +export const px = (magnitude: number): Px => `${magnitude}px` as Px; + +export type Percent = TackyVariant<"percent">; +export const percent = (magnitude: number): Percent => + `${magnitude}%` as Percent; + +// Time +export type Ms = TackyVariant<"ms">; +export const ms = (magnitude: number): Ms => `${magnitude}ms` as Ms; + +export type S = TackyVariant<"s">; +export const s = (magnitude: number): S => `${magnitude}s` as S; + +// Angle +export type Deg = TackyVariant<"deg">; +export const deg = (magnitude: number): Deg => `${magnitude}deg` as Deg; + +export type Rad = TackyVariant<"rad">; +export const rad = (magnitude: number): Rad => `${magnitude}rad` as Rad; + +export type Grad = TackyVariant<"grad">; +export const grad = (magnitude: number): Grad => `${magnitude}grad` as Grad; + +export type Turn = TackyVariant<"turn">; +export const turn = (magnitude: number): Turn => `${magnitude}turn` as Turn; + +// Resolution +export type Dpi = TackyVariant<"dpi">; +export const dpi = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; + +export type Dpcm = TackyVariant<"dpcm">; +export const dpcm = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; + +export type Dppx = TackyVariant<"dppx">; +export const dppx = (magnitude: number): Dpi => `${magnitude}dpi` as Dpi; + +export type CSSLength = Rem | Em | Px | 0; +export type CSSLengthPercentage = CSSLength | Percent; +export type CSSTime = Ms | S | 0; +export type CSSAngle = Deg | Rad | Grad | Turn; +export type CSSResolution = Dpi | Dpcm | Dppx; From 164ee328448820d9d11fcc184f9efd95401c90cd Mon Sep 17 00:00:00 2001 From: Tom Picton Date: Fri, 18 Sep 2020 13:42:26 -0700 Subject: [PATCH 3/3] Combine with manually defined properties --- .eslintrc.js | 1 + .gitignore | 2 +- packages/tacky-css/build.ts | 27 +- .../collections/data-types.ts | 0 .../collections/properties.ts | 0 .../collections/syntaxes.ts | 0 .../{csstype => generate}/data/patches.ts | 0 .../{csstype => generate}/data/svg.ts | 0 .../{csstype => generate}/data/urls.json | 0 .../{csstype => generate}/declarator.ts | 3 + .../{csstype => generate}/syntax/parser.ts | 134 +++++-- .../{csstype => generate}/syntax/typer.ts | 36 +- .../{csstype => generate}/typescript.ts | 58 ++- .../{csstype => generate}/utils/casing.ts | 0 .../{csstype => generate}/utils/comment.ts | 0 .../{csstype => generate}/utils/compat.ts | 0 .../{csstype => generate}/utils/logger.ts | 0 .../{csstype => generate}/utils/output.ts | 3 + .../{csstype => generate}/utils/urls.ts | 0 packages/tacky-css/src/animation.ts | 189 +++++++++ packages/tacky-css/src/augment.ts | 60 ++- packages/tacky-css/src/color.ts | 14 +- packages/tacky-css/src/function.ts | 9 + packages/tacky-css/src/image.ts | 139 +++++++ packages/tacky-css/src/index.ts | 35 ++ packages/tacky-css/src/macro.ts | 170 ++++++++ packages/tacky-css/src/media/index.ts | 51 +++ packages/tacky-css/src/media/mediaFeatures.ts | 70 ++++ packages/tacky-css/src/media/mediaTypes.ts | 43 ++ packages/tacky-css/src/media/operators.ts | 4 + packages/tacky-css/src/media/types.ts | 15 + packages/tacky-css/src/property/animations.ts | 25 ++ .../src/property/backgroundsAndBorders.ts | 370 ++++++++++++++++++ .../src/property/basicUserInterface.ts | 10 + .../tacky-css/src/property/boxAlignment.ts | 20 + packages/tacky-css/src/property/boxModel.ts | 21 + .../src/property/compositingAndBlending.ts | 26 ++ .../tacky-css/src/property/containment.ts | 15 + packages/tacky-css/src/property/display.ts | 5 + .../src/property/flexibleBoxLayout.ts | 30 ++ packages/tacky-css/src/property/fonts.ts | 18 + .../src/property/generatedContent.ts | 22 ++ packages/tacky-css/src/property/index.ts | 18 + packages/tacky-css/src/property/masking.ts | 16 + packages/tacky-css/src/property/overflow.ts | 16 + .../tacky-css/src/property/textDecoration.ts | 63 +++ .../tacky-css/src/property/transitions.ts | 41 ++ .../tacky-css/src/property/writingModes.ts | 4 + packages/tacky-css/src/shape.ts | 51 +++ packages/tacky-css/src/types.ts | 25 ++ packages/tacky-css/src/utils.ts | 24 ++ packages/tacky-css/tsconfig.json | 4 +- 52 files changed, 1808 insertions(+), 79 deletions(-) rename packages/tacky-css/{csstype => generate}/collections/data-types.ts (100%) rename packages/tacky-css/{csstype => generate}/collections/properties.ts (100%) rename packages/tacky-css/{csstype => generate}/collections/syntaxes.ts (100%) rename packages/tacky-css/{csstype => generate}/data/patches.ts (100%) rename packages/tacky-css/{csstype => generate}/data/svg.ts (100%) rename packages/tacky-css/{csstype => generate}/data/urls.json (100%) rename packages/tacky-css/{csstype => generate}/declarator.ts (98%) rename packages/tacky-css/{csstype => generate}/syntax/parser.ts (72%) rename packages/tacky-css/{csstype => generate}/syntax/typer.ts (93%) rename packages/tacky-css/{csstype => generate}/typescript.ts (86%) rename packages/tacky-css/{csstype => generate}/utils/casing.ts (100%) rename packages/tacky-css/{csstype => generate}/utils/comment.ts (100%) rename packages/tacky-css/{csstype => generate}/utils/compat.ts (100%) rename packages/tacky-css/{csstype => generate}/utils/logger.ts (100%) rename packages/tacky-css/{csstype => generate}/utils/output.ts (95%) rename packages/tacky-css/{csstype => generate}/utils/urls.ts (100%) create mode 100644 packages/tacky-css/src/animation.ts create mode 100644 packages/tacky-css/src/function.ts create mode 100644 packages/tacky-css/src/image.ts create mode 100644 packages/tacky-css/src/index.ts create mode 100644 packages/tacky-css/src/macro.ts create mode 100644 packages/tacky-css/src/media/index.ts create mode 100644 packages/tacky-css/src/media/mediaFeatures.ts create mode 100644 packages/tacky-css/src/media/mediaTypes.ts create mode 100644 packages/tacky-css/src/media/operators.ts create mode 100644 packages/tacky-css/src/media/types.ts create mode 100644 packages/tacky-css/src/property/animations.ts create mode 100644 packages/tacky-css/src/property/backgroundsAndBorders.ts create mode 100644 packages/tacky-css/src/property/basicUserInterface.ts create mode 100644 packages/tacky-css/src/property/boxAlignment.ts create mode 100644 packages/tacky-css/src/property/boxModel.ts create mode 100644 packages/tacky-css/src/property/compositingAndBlending.ts create mode 100644 packages/tacky-css/src/property/containment.ts create mode 100644 packages/tacky-css/src/property/display.ts create mode 100644 packages/tacky-css/src/property/flexibleBoxLayout.ts create mode 100644 packages/tacky-css/src/property/fonts.ts create mode 100644 packages/tacky-css/src/property/generatedContent.ts create mode 100644 packages/tacky-css/src/property/index.ts create mode 100644 packages/tacky-css/src/property/masking.ts create mode 100644 packages/tacky-css/src/property/overflow.ts create mode 100644 packages/tacky-css/src/property/textDecoration.ts create mode 100644 packages/tacky-css/src/property/transitions.ts create mode 100644 packages/tacky-css/src/property/writingModes.ts create mode 100644 packages/tacky-css/src/shape.ts create mode 100644 packages/tacky-css/src/utils.ts diff --git a/.eslintrc.js b/.eslintrc.js index a973310..0a0ff57 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,5 +50,6 @@ module.exports = { ], "arrow-parens": ["error", "as-needed"], "@typescript-eslint/no-namespace": 0, + "@typescript-eslint/no-empty-interface": 0, }, }; diff --git a/.gitignore b/.gitignore index aa9237d..c9b302c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ tags .build yarn-error.log lib -packages/tacky-css/src/generated.ts +packages/tacky-css/src/generated diff --git a/packages/tacky-css/build.ts b/packages/tacky-css/build.ts index 82a9595..0def57b 100644 --- a/packages/tacky-css/build.ts +++ b/packages/tacky-css/build.ts @@ -1,4 +1,5 @@ // import * as chokidar from 'chokidar'; +import * as fs from "fs"; import * as path from "path"; import * as prettier from "prettier"; import { writeFileAsync } from "./utils"; @@ -10,16 +11,34 @@ trigger().then(() => { }); async function trigger(): Promise { - const generatePath = path.resolve("./csstype"); + const generatePath = path.resolve("./generate"); for (const key in require.cache) { if (key.indexOf(generatePath) !== -1) { delete require.cache[key]; } } - const { default: generateTypescript } = await import("./csstype/typescript"); + + const outDir = path.resolve("./src/generated"); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir); + } + + const { default: generateTypescript } = await import("./generate/typescript"); const unformatted = await generateTypescript(); - const formatted = await format(unformatted); - await writeFileAsync("./src/generated.ts", formatted); + + const formatted = await Promise.all( + unformatted.map(async ({ name, content }) => ({ + name, + content: await format(content), + })) + ); + + await Promise.all( + formatted.map( + async ({ name, content }) => + await writeFileAsync(`./src/generated/${name}`, content) + ) + ); } // if (process.argv.includes('--start')) { diff --git a/packages/tacky-css/csstype/collections/data-types.ts b/packages/tacky-css/generate/collections/data-types.ts similarity index 100% rename from packages/tacky-css/csstype/collections/data-types.ts rename to packages/tacky-css/generate/collections/data-types.ts diff --git a/packages/tacky-css/csstype/collections/properties.ts b/packages/tacky-css/generate/collections/properties.ts similarity index 100% rename from packages/tacky-css/csstype/collections/properties.ts rename to packages/tacky-css/generate/collections/properties.ts diff --git a/packages/tacky-css/csstype/collections/syntaxes.ts b/packages/tacky-css/generate/collections/syntaxes.ts similarity index 100% rename from packages/tacky-css/csstype/collections/syntaxes.ts rename to packages/tacky-css/generate/collections/syntaxes.ts diff --git a/packages/tacky-css/csstype/data/patches.ts b/packages/tacky-css/generate/data/patches.ts similarity index 100% rename from packages/tacky-css/csstype/data/patches.ts rename to packages/tacky-css/generate/data/patches.ts diff --git a/packages/tacky-css/csstype/data/svg.ts b/packages/tacky-css/generate/data/svg.ts similarity index 100% rename from packages/tacky-css/csstype/data/svg.ts rename to packages/tacky-css/generate/data/svg.ts diff --git a/packages/tacky-css/csstype/data/urls.json b/packages/tacky-css/generate/data/urls.json similarity index 100% rename from packages/tacky-css/csstype/data/urls.json rename to packages/tacky-css/generate/data/urls.json diff --git a/packages/tacky-css/csstype/declarator.ts b/packages/tacky-css/generate/declarator.ts similarity index 98% rename from packages/tacky-css/csstype/declarator.ts rename to packages/tacky-css/generate/declarator.ts index 008b331..35589ae 100644 --- a/packages/tacky-css/csstype/declarator.ts +++ b/packages/tacky-css/generate/declarator.ts @@ -450,6 +450,9 @@ function sorter(a: MixedType, b: MixedType) { if (a.type === Type.StringLiteral && b.type === Type.StringLiteral) { return a.literal < b.literal ? -1 : a.literal > b.literal ? 1 : 0; } + if (a.type === Type.Function && b.type === Type.Function) { + return a.literal < b.literal ? -1 : a.literal > b.literal ? 1 : 0; + } if (a.type === Type.NumericLiteral && b.type === Type.NumericLiteral) { return a.literal - b.literal; } diff --git a/packages/tacky-css/csstype/syntax/parser.ts b/packages/tacky-css/generate/syntax/parser.ts similarity index 72% rename from packages/tacky-css/csstype/syntax/parser.ts rename to packages/tacky-css/generate/syntax/parser.ts index 13cbde0..d03954e 100644 --- a/packages/tacky-css/csstype/syntax/parser.ts +++ b/packages/tacky-css/generate/syntax/parser.ts @@ -1,7 +1,6 @@ export enum Entity { Component, Combinator, - Function, Unknown, } @@ -9,6 +8,7 @@ export enum Component { Keyword, DataType, Group, + Function, } // Higher number is higher precedence @@ -69,18 +69,20 @@ export interface IGroupData { entities: EntityType[]; } -export type ComponentType = INonGroupData | IGroupData; +export interface IFunction { + entity: Entity.Component; + multiplier: MultiplierType | null; + component: Component.Function; + name: string; +} + +export type ComponentType = INonGroupData | IGroupData | IFunction; export interface ICombinator { entity: Entity.Combinator; combinator: Combinator; } -export interface IFunction { - entity: Entity.Function; - multiplier: MultiplierType | null; -} - export interface IUnknown { entity: Entity.Unknown; multiplier: MultiplierType | null; @@ -91,6 +93,7 @@ export type EntityType = ComponentType | ICombinator | IFunction | IUnknown; const REGEX_ENTITY = /(?:^|\s)((?:<[^>]+>)|(?:[\w]+\([^\)]*\))|[^\s*+?#!{]+)([*+?#!]|{(\d+),(\d+)})?/g; const REGEX_DATA_TYPE = /^(<[^>]+>)/g; const REGEX_KEYWORD = /^([\w-]+)/g; +const REGEX_FUNCTION = /^([\w-]+)\((.+)\)/g; export const combinators: { [key: number]: ICombinator } = { [Combinator.Juxtaposition]: { @@ -118,26 +121,27 @@ export default function parse(syntax: string): EntityType[] { let entityMatch: RegExpExecArray | null; while ((entityMatch = REGEX_ENTITY.exec(syntax))) { const [, value, ...rawMultiplier] = entityMatch; - if (value.indexOf('(') !== -1) { - deepestLevel().push({ entity: Entity.Function, multiplier: multiplierData(rawMultiplier) }); - previousMatchWasComponent = false; - continue; - } else if (value.indexOf('&&') === 0) { + if (value.indexOf("&&") === 0) { deepestLevel().push(combinators[Combinator.DoubleAmpersand]); previousMatchWasComponent = false; continue; - } else if (value.indexOf('||') === 0) { + } else if (value.indexOf("||") === 0) { deepestLevel().push(combinators[Combinator.DoubleBar]); previousMatchWasComponent = false; continue; - } else if (value.indexOf('|') === 0) { + } else if (value.indexOf("|") === 0) { deepestLevel().push(combinators[Combinator.SingleBar]); previousMatchWasComponent = false; continue; - } else if (value.indexOf(']') === 0) { + } else if (value.indexOf("]") === 0) { const definitions = levels.pop(); if (definitions) { - deepestLevel().push(componentGroupData(groupByPrecedence(definitions), multiplierData(rawMultiplier))); + deepestLevel().push( + componentGroupData( + groupByPrecedence(definitions), + multiplierData(rawMultiplier) + ) + ); } previousMatchWasComponent = true; continue; @@ -146,7 +150,7 @@ export default function parse(syntax: string): EntityType[] { deepestLevel().push(combinators[Combinator.Juxtaposition]); } - if (value.indexOf('[') === 0) { + if (value.indexOf("[") === 0) { levels.push([]); previousMatchWasComponent = false; continue; @@ -155,17 +159,31 @@ export default function parse(syntax: string): EntityType[] { let componentMatch: RegExpMatchArray | null; if ((componentMatch = value.match(REGEX_DATA_TYPE))) { const name = componentMatch[0]; - deepestLevel().push(componentData(Component.DataType, name, multiplierData(rawMultiplier))); + deepestLevel().push( + componentData(Component.DataType, name, multiplierData(rawMultiplier)) + ); + previousMatchWasComponent = true; + continue; + } else if ((componentMatch = REGEX_FUNCTION.exec(value))) { + const name = componentMatch[1]; + deepestLevel().push( + componentFunctionData(name, multiplierData(rawMultiplier)) + ); previousMatchWasComponent = true; continue; } else if ((componentMatch = value.match(REGEX_KEYWORD))) { const name = componentMatch[0]; - deepestLevel().push(componentData(Component.Keyword, name, multiplierData(rawMultiplier))); + deepestLevel().push( + componentData(Component.Keyword, name, multiplierData(rawMultiplier)) + ); previousMatchWasComponent = true; continue; } } - deepestLevel().push({ entity: Entity.Unknown, multiplier: multiplierData(rawMultiplier) }); + deepestLevel().push({ + entity: Entity.Unknown, + multiplier: multiplierData(rawMultiplier), + }); } function deepestLevel() { @@ -183,18 +201,26 @@ export function isCombinator(entity: EntityType): entity is ICombinator { return entity.entity === Entity.Combinator; } -export function isCurlyBracetMultiplier(multiplier: MultiplierType): multiplier is IMultiplierCurlyBracet { +export function isCurlyBracetMultiplier( + multiplier: MultiplierType +): multiplier is IMultiplierCurlyBracet { return multiplier.sign === Multiplier.CurlyBracet; } export function isMandatoryMultiplied(multiplier: MultiplierType | null) { - return multiplier !== null && isCurlyBracetMultiplier(multiplier) && multiplier.min > 1; + return ( + multiplier !== null && + isCurlyBracetMultiplier(multiplier) && + multiplier.min > 1 + ); } export function isOptionallyMultiplied(multiplier: MultiplierType | null) { return ( multiplier !== null && - ((isCurlyBracetMultiplier(multiplier) && multiplier.min < multiplier.max && multiplier.max > 1) || + ((isCurlyBracetMultiplier(multiplier) && + multiplier.min < multiplier.max && + multiplier.max > 1) || multiplier.sign === Multiplier.Asterisk || multiplier.sign === Multiplier.PlusSign || multiplier.sign === Multiplier.HashMark || @@ -204,12 +230,16 @@ export function isOptionallyMultiplied(multiplier: MultiplierType | null) { export function isMandatoryEntity(entity: EntityType) { if (isCombinator(entity)) { - return entity === combinators[Combinator.DoubleAmpersand] || entity === combinators[Combinator.Juxtaposition]; + return ( + entity === combinators[Combinator.DoubleAmpersand] || + entity === combinators[Combinator.Juxtaposition] + ); } if (entity.multiplier) { return ( - (isCurlyBracetMultiplier(entity.multiplier) && entity.multiplier.min > 0) || + (isCurlyBracetMultiplier(entity.multiplier) && + entity.multiplier.min > 0) || entity.multiplier.sign === Multiplier.PlusSign || entity.multiplier.sign === Multiplier.HashMark || entity.multiplier.sign === Multiplier.ExclamationPoint @@ -222,7 +252,7 @@ export function isMandatoryEntity(entity: EntityType) { export function componentData( component: Component.Keyword | Component.DataType, value: string, - multiplier: MultiplierType | null = null, + multiplier: MultiplierType | null = null ): ComponentType { return { entity: Entity.Component, @@ -232,7 +262,10 @@ export function componentData( }; } -export function componentGroupData(entities: EntityType[], multiplier: MultiplierType | null = null): ComponentType { +export function componentGroupData( + entities: EntityType[], + multiplier: MultiplierType | null = null +): ComponentType { return { entity: Entity.Component, component: Component.Group, @@ -241,29 +274,48 @@ export function componentGroupData(entities: EntityType[], multiplier: Multiplie }; } +export function componentFunctionData( + name: string, + multiplier: MultiplierType | null = null +): ComponentType { + return { + entity: Entity.Component, + component: Component.Function, + multiplier, + name, + }; +} + function multiplierData(raw: string[]): MultiplierType | null { if (!raw[0]) { return null; } switch (raw[0].slice(0, 1)) { - case '*': + case "*": return { sign: Multiplier.Asterisk }; - case '+': + case "+": return { sign: Multiplier.PlusSign }; - case '?': + case "?": return { sign: Multiplier.QuestionMark }; - case '#': + case "#": return { sign: Multiplier.HashMark }; - case '!': + case "!": return { sign: Multiplier.ExclamationPoint }; - case '{': - return { sign: Multiplier.CurlyBracet, min: Number(raw[1]), max: Number(raw[2]) }; + case "{": + return { + sign: Multiplier.CurlyBracet, + min: Number(raw[1]), + max: Number(raw[2]), + }; default: return null; } } -function groupByPrecedence(entities: EntityType[], precedence: number = Combinator.SingleBar): EntityType[] { +function groupByPrecedence( + entities: EntityType[], + precedence: number = Combinator.SingleBar +): EntityType[] { if (precedence < 0) { // We've reached the lowest precedence possible return entities; @@ -273,7 +325,11 @@ function groupByPrecedence(entities: EntityType[], precedence: number = Combinat const combinatorIndexes: number[] = []; // Search for indexes where the combinator is used - for (let i = entities.indexOf(combinator); i > -1; i = entities.indexOf(combinator, i + 1)) { + for ( + let i = entities.indexOf(combinator); + i > -1; + i = entities.indexOf(combinator, i + 1) + ) { combinatorIndexes.push(i); } @@ -300,12 +356,14 @@ function groupByPrecedence(entities: EntityType[], precedence: number = Combinat i < combinatorIndexes.length ? combinatorIndexes[i] : // Slice to end - entities.length, + entities.length ); // Only group if there's more than one entity in between if (sectionEntities.length > 1) { - groupedEntities.push(componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence))); + groupedEntities.push( + componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence)) + ); } else { groupedEntities.push(...sectionEntities); } diff --git a/packages/tacky-css/csstype/syntax/typer.ts b/packages/tacky-css/generate/syntax/typer.ts similarity index 93% rename from packages/tacky-css/csstype/syntax/typer.ts rename to packages/tacky-css/generate/syntax/typer.ts index 7e0767f..b1c25c6 100644 --- a/packages/tacky-css/csstype/syntax/typer.ts +++ b/packages/tacky-css/generate/syntax/typer.ts @@ -25,7 +25,8 @@ export enum Type { Number, Never, LengthPercentage, - Percentage + Percentage, + Function } interface IBasic { @@ -47,17 +48,20 @@ interface INumericLiteral { literal: number; } +interface IFunctiono { + type: Type.Function; + literal: string; +} + export type DataType = IDataType; // Yet another reminder; naming is hard -export type TypeType = IBasic | IStringLiteral | INumericLiteral | TDataType; +export type TypeType = IBasic | IStringLiteral | INumericLiteral | TDataType | IFunctiono; export type ResolvedType = TypeType; let getBasicDataTypes = () => { const types = Object.keys(cssTypes).reduce<{ [name: string]: IBasic }>((dataTypes, name) => { - console.log(name); - switch (name) { case 'number': case 'integer': @@ -150,6 +154,9 @@ export default function typing(entities: EntityType[]): TypeType[] { types = addStringLiteral(types, entity.value); } break; + case Component.Function: + types = addFunction(types, entity.name); + break; case Component.DataType: { const value = valueOfDataType(entity.value); if (value in getBasicDataTypes()) { @@ -192,6 +199,8 @@ export default function typing(entities: EntityType[]): TypeType[] { return types; } + + function addLength(types: TypeType[]): TypeType[] { if (types.every(type => type.type !== Type.Length)) { return [ @@ -300,6 +309,23 @@ function addStringLiteral( return types; } +function addFunction( + types: TypeType[], + name: string +): TypeType[] { + if (types.every(type => !(type.type === Type.Function && type.literal === name))) { + return [ + ...types, + { + type: Type.Function, + literal: name, + }, + ]; + } + + return types; +} + function addNumericLiteral( types: TypeType[], literal: number, @@ -375,6 +401,8 @@ export function addType( return addDataType(types, type.name); case Type.PropertyReference: return addPropertyReference(types, type.name); + case Type.Function: + return addFunction(types, type.literal); } } diff --git a/packages/tacky-css/csstype/typescript.ts b/packages/tacky-css/generate/typescript.ts similarity index 86% rename from packages/tacky-css/csstype/typescript.ts rename to packages/tacky-css/generate/typescript.ts index 5aaf8be..cb68044 100644 --- a/packages/tacky-css/csstype/typescript.ts +++ b/packages/tacky-css/generate/typescript.ts @@ -18,7 +18,12 @@ import { stringifyGenerics, } from "./utils/output"; -export default async function typescript(): Promise { +interface FileRef { + name: string; + content: string; +} + +export default async function typescript(): Promise { const { namespaces, interfaces, @@ -79,21 +84,32 @@ export default async function typescript(): Promise { const disableAutoExport = "export {};" + EOL; const propertyTuple = - "export type PropertyTuple = readonly [T, Values[T]]" + EOL; - - return ( - disableAutoExport + - EOL + - propertyTuple + - EOL + - interfacesOutput + - EOL + - declarationsOutput + - EOL + - namespaceOutput + - EOL + - extra.join("\n\n") - ); + "export type PropertyTuple = readonly [T, Values[T]]" + + EOL; + + const types = { + name: "types.ts", + content: + disableAutoExport + + EOL + + propertyTuple + + EOL + + interfacesOutput + + EOL + + declarationsOutput + + EOL + + namespaceOutput, + }; + + const properties = { + name: "property.ts", + content: + 'import { Values, Property } from "./types";' + + EOL + + extra.join("\n\n"), + }; + + return [types, properties]; } async function outputInterface( @@ -201,10 +217,12 @@ function stringifyTypes( return namespace + type.name; }, currentNamespace); - return types - // .filter(({ type }) => ![Type.String, Type.Length, Type.Time].includes(type)) - .map(type => stringifyType(type)) - .join(" | "); + return ( + types + // .filter(({ type }) => ![Type.String, Type.Length, Type.Time].includes(type)) + .map(type => stringifyType(type)) + .join(" | ") + ); } function stringifySimpleTypes(types: SimpleType[]): string { diff --git a/packages/tacky-css/csstype/utils/casing.ts b/packages/tacky-css/generate/utils/casing.ts similarity index 100% rename from packages/tacky-css/csstype/utils/casing.ts rename to packages/tacky-css/generate/utils/casing.ts diff --git a/packages/tacky-css/csstype/utils/comment.ts b/packages/tacky-css/generate/utils/comment.ts similarity index 100% rename from packages/tacky-css/csstype/utils/comment.ts rename to packages/tacky-css/generate/utils/comment.ts diff --git a/packages/tacky-css/csstype/utils/compat.ts b/packages/tacky-css/generate/utils/compat.ts similarity index 100% rename from packages/tacky-css/csstype/utils/compat.ts rename to packages/tacky-css/generate/utils/compat.ts diff --git a/packages/tacky-css/csstype/utils/logger.ts b/packages/tacky-css/generate/utils/logger.ts similarity index 100% rename from packages/tacky-css/csstype/utils/logger.ts rename to packages/tacky-css/generate/utils/logger.ts diff --git a/packages/tacky-css/csstype/utils/output.ts b/packages/tacky-css/generate/utils/output.ts similarity index 95% rename from packages/tacky-css/csstype/utils/output.ts rename to packages/tacky-css/generate/utils/output.ts index 35d039e..cf563b5 100644 --- a/packages/tacky-css/csstype/utils/output.ts +++ b/packages/tacky-css/generate/utils/output.ts @@ -9,6 +9,7 @@ import { timeGeneric, } from '../declarator'; import { Type } from '../syntax/typer'; +import { toPascalCase } from "./casing"; export const EOL = '\n'; @@ -43,6 +44,8 @@ export function createStringifyType( } case Type.Length: return "ManualDataType." + lengthGeneric.name; + case Type.Function: + return "ManualDataType.T" + toPascalCase(type.literal); case Type.Percentage: return "ManualDataType." + "TPercentage"; case Type.LengthPercentage: diff --git a/packages/tacky-css/csstype/utils/urls.ts b/packages/tacky-css/generate/utils/urls.ts similarity index 100% rename from packages/tacky-css/csstype/utils/urls.ts rename to packages/tacky-css/generate/utils/urls.ts diff --git a/packages/tacky-css/src/animation.ts b/packages/tacky-css/src/animation.ts new file mode 100644 index 0000000..6c92578 --- /dev/null +++ b/packages/tacky-css/src/animation.ts @@ -0,0 +1,189 @@ +// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties +// TODO: Figure out a way to generate this from MDN +export type AnimatableProperties = + | "all" + | "backdropFilter" + | "background" + | "backgroundColor" + | "backgroundPosition" + | "backgroundSize" + | "blockSize" + | "border" + | "borderBlockEnd" + | "borderBlockEndColor" + | "borderBlockEndWidth" + | "borderBlockStart" + | "borderBlockStartColor" + | "borderBlockStartWidth" + | "borderBottom" + | "borderBottomColor" + | "borderBottomLeftRadius" + | "borderBottomRightRadius" + | "borderBottomWidth" + | "borderColor" + | "borderEndEndRadius" + | "borderEndStartRadius" + | "borderImageOutset" + | "borderImageSlice" + | "borderImageWidth" + | "borderInlineEnd" + | "borderInlineEndColor" + | "borderInlineEndWidth" + | "borderInlineStart" + | "borderInlineStartColor" + | "borderInlineStartWidth" + | "borderLeft" + | "borderLeftColor" + | "borderLeftWidth" + | "borderRadius" + | "borderRight" + | "borderRightColor" + | "borderRightWidth" + | "borderStartEndRadius" + | "borderStartStartRadius" + | "borderTop" + | "borderTopColor" + | "borderTopLeftRadius" + | "borderTopRightRadius" + | "borderTopWidth" + | "borderWidth" + | "bottom" + | "boxShadow" + | "caretColor" + | "clip" + | "clipPath" + | "color" + | "columnCount" + | "columnGap" + | "columnRule" + | "columnRuleColor" + | "columnRuleWidth" + | "columnWidth" + | "columns" + | "filter" + | "flex" + | "flexBasis" + | "flexGrow" + | "flexShrink" + | "font" + | "fontSize" + | "fontSizeAdjust" + | "fontStretch" + | "fontVariationSettings" + | "fontWeight" + | "gap" + | "gridColumnGap" + | "gridGap" + | "gridRowGap" + | "gridTemplateColumns" + | "gridTemplateRows" + | "height" + | "inlineSize" + | "inset" + | "insetBlock" + | "insetBlockEnd" + | "insetBlockStart" + | "insetInline" + | "insetInlineEnd" + | "insetInlineStart" + | "left" + | "letterSpacing" + | "lineClamp" + | "lineHeight" + | "margin" + | "marginBlockEnd" + | "marginBlockStart" + | "marginBottom" + | "marginInlineEnd" + | "marginInlineStart" + | "marginLeft" + | "marginRight" + | "marginTop" + | "mask" + | "maskBorder" + | "maskPosition" + | "maskSize" + | "maxBlockSize" + | "maxHeight" + | "maxInlineSize" + | "maxLines" + | "maxWidth" + | "minBlockSize" + | "minHeight" + | "minInlineSize" + | "minWidth" + | "objectPosition" + | "offset" + | "offsetAnchor" + | "offsetDistance" + | "offsetPath" + | "offsetPosition" + | "offsetRotate" + | "opacity" + | "order" + | "outline" + | "outlineColor" + | "outlineOffset" + | "outlineWidth" + | "padding" + | "paddingBlockEnd" + | "paddingBlockStart" + | "paddingBottom" + | "paddingInlineEnd" + | "paddingInlineStart" + | "paddingLeft" + | "paddingRight" + | "paddingTop" + | "perspective" + | "perspectiveOrigin" + | "right" + | "rotate" + | "rowGap" + | "scale" + | "scrollMargin" + | "scrollMarginBlock" + | "scrollMarginBlockEnd" + | "scrollMarginBlockStart" + | "scrollMarginBottom" + | "scrollMarginInline" + | "scrollMarginInlineEnd" + | "scrollMarginInlineStart" + | "scrollMarginLeft" + | "scrollMarginRight" + | "scrollMarginTop" + | "scrollPadding" + | "scrollPaddingBlock" + | "scrollPaddingBlockEnd" + | "scrollPaddingBlockStart" + | "scrollPaddingBottom" + | "scrollPaddingInline" + | "scrollPaddingInlineEnd" + | "scrollPaddingInlineStart" + | "scrollPaddingLeft" + | "scrollPaddingRight" + | "scrollPaddingTop" + | "scrollSnapCoordinate" + | "scrollSnapDestination" + | "scrollbarColor" + | "shapeImageThreshold" + | "shapeMargin" + | "shapeOutside" + | "tabSize" + | "textDecoration" + | "textDecorationColor" + | "textDecorationThickness" + | "textEmphasis" + | "textEmphasisColor" + | "textIndent" + | "textShadow" + | "textUnderlineOffset" + | "top" + | "transform" + | "transformOrigin" + | "translate" + | "verticalAlign" + | "visibility" + | "width" + | "wordSpacing" + | "zIndex" + | "zoom"; diff --git a/packages/tacky-css/src/augment.ts b/packages/tacky-css/src/augment.ts index d221663..3b437ba 100644 --- a/packages/tacky-css/src/augment.ts +++ b/packages/tacky-css/src/augment.ts @@ -1,18 +1,60 @@ -import { color, marginLeft } from "./generated"; -import { CSSColor } from "./color"; +import { FitContent } from "./function"; +import { Rgb, Rgba, Hsl, Hsla } from "./color"; import { CSSLength, CSSLengthPercentage, Percent, CSSTime } from "./unit"; -declare module "./generated" { +declare module "./generated/types" { namespace ManualDataType { - export interface IColor { - cool: never; - } - export type TLength = CSSLength; export type TLengthPercentage = CSSLengthPercentage; export type TPercentage = Percent; export type TTime = CSSTime; + export type TFitContent = FitContent; + export type TRgb = Rgb; + export type TRgba = Rgba; + export type THsl = Hsl; + export type THsla = Hsla; + + export type TTranslate = never; + export type TTranslateX = never; + export type TTranslateY = never; + export type TTranslateZ = never; + export type TTranslate3d = never; + export type TScale = never; + export type TScaleX = never; + export type TScaleY = never; + export type TScaleZ = never; + export type TScale3d = never; + export type TSkew = never; + export type TSkewX = never; + export type TSkewY = never; + export type TRotate = never; + export type TRotateX = never; + export type TRotateY = never; + export type TRotateZ = never; + export type TRotate3d = never; + export type TPerspective = never; + export type TMatrix = never; + export type TMinmax = never; + export type TRepeat = never; + export type TSteps = never; + export type TChild = never; + export type TElement = never; + export type TImage = never; + export type TSaturate = never; + export type TInvert = never; + export type TGrayscale = never; + export type TContrast = never; + export type TBlur = never; + export type TLeader = never; + export type TPath = never; + export type TInset = never; + export type TRay = never; + export type TRect = never; + export type TEllipse = never; + export type TSnapInterval = never; + export type TSwash = never; + export type TStylistic = never; + export type TStyleset = never; + export type TAnnotation = never; } } - -const test = color("cool")[1]; diff --git a/packages/tacky-css/src/color.ts b/packages/tacky-css/src/color.ts index 3c57c4a..167869e 100644 --- a/packages/tacky-css/src/color.ts +++ b/packages/tacky-css/src/color.ts @@ -12,4 +12,16 @@ export const rgba = ( alpha: number ): Rgba => `rgba(${red}, ${green}, ${blue}, ${alpha})` as Rgba; -export type CSSColor = Rgb | Rgba; +export type Hsl = TackyVariant<"hsl">; +export const hsl = (hue: number, saturation: number, lightness: number): Hsl => + `hsl(${hue}, ${saturation}, ${lightness})` as Hsl; + +export type Hsla = TackyVariant<"hsla">; +export const hsla = ( + hue: number, + saturation: number, + lightness: number, + alpha: number +): Hsla => `hsla(${hue}, ${saturation}, ${lightness}, ${alpha})` as Hsla; + +export type CSSColor = Rgb | Rgba | Hsl | Hsla; diff --git a/packages/tacky-css/src/function.ts b/packages/tacky-css/src/function.ts new file mode 100644 index 0000000..79f7d95 --- /dev/null +++ b/packages/tacky-css/src/function.ts @@ -0,0 +1,9 @@ +import { TackyVariant } from "./types"; +import { CSSLengthPercentage } from "./unit"; + +export type CSSURL = TackyVariant<"url">; +export const url = (url: URL): CSSURL => `url(${url})` as CSSURL; + +export type FitContent = TackyVariant<"fitContent">; +export const fitContent = (arg: CSSLengthPercentage): FitContent => + `fitContent(${arg})` as FitContent; diff --git a/packages/tacky-css/src/image.ts b/packages/tacky-css/src/image.ts new file mode 100644 index 0000000..320fc43 --- /dev/null +++ b/packages/tacky-css/src/image.ts @@ -0,0 +1,139 @@ +import { BackgroundPositionArgs } from "./property"; +import { CSSColor } from "./color"; +import { CSSLengthPercentage, Percent, CSSAngle } from "./unit"; +import { TackyVariant } from "./types"; +import { CSSURL } from "./function"; + +type SideOrCorner = + | "to top" + | "to top left" + | "to top right" + | "to bottom" + | "to bottom left" + | "to bottom right" + | "to left" + | "to left top" + | "to left bottom" + | "to right" + | "to right top" + | "to right bottom"; + +type InitialLinearColorStop = [ + color: CSSColor, + stopStart?: CSSLengthPercentage, + stopEnd?: CSSLengthPercentage +]; + +type LinearColorStop = InitialLinearColorStop; +type ColorHint = Percent; +type LinearColorStopOrHint = LinearColorStop | [ColorHint, ...LinearColorStop]; + +// Ideally would be expressed in a way that matches the CSS syntax +// more closely, i.e. +// +// linearGradient( +// "to top", +// ["red", percent(10), percent(40)], +// percent(50), <- +// ["blue"] +// ); +// +// The constraints we have are: +// +// 1. must be after at least one +// 2. No two neighboring arguments can both be a +// 3. Final argument must be a +// +// This is difficult because: +// - Variadic tuples lose type strictness for elements following a spread +// over a tuple of unknown length, and the "middle" arguments in a linear +// gradient can be of any length +// - Recursively spreading a tuple isn't officially supported +// https://github.com/microsoft/TypeScript/issues/40298 +// +// The current solution combines and for all +// but the 3rd argument to the nth argument. I dislike this departure from the +// CSS spec, but it does satisfy all three constaints. + +export interface LinearGradientFunction { + < + T extends [CSSAngle | SideOrCorner] | [], + V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]] + >( + ...args: [ + ...angle: T, + colorStop: InitialLinearColorStop, + ...colorStopOrHint: V + ] + ): Return; +} + +export type LinearGradient = TackyVariant<"linearGradient">; +export const linearGradient: LinearGradientFunction = ( + ...args +) => { + return `linear-gradient(${args + .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) + .join(", ")})` as LinearGradient; +}; + +export type RepeatingLinearGradient = TackyVariant<"repeatingLinearGradient">; +export const repeatingLinearGradient: LinearGradientFunction = ( + ...args +) => { + return `repeating-linear-gradient(${args + .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) + .join(", ")})` as RepeatingLinearGradient; +}; + +export type RadialGradient = TackyVariant<"RadialGradient">; +type RadialGradientShape = "circle" | "ellipse"; +type RadialGradientExtentKeyword = + | "closest-side" + | "closest-corner" + | "farthest-side" + | "farthest-corner"; +export interface RadialGradientFunction { + < + T extends + | [ + RadialGradientShape | RadialGradientExtentKeyword, + ...([] | ["at", ...BackgroundPositionArgs]) + ] + | [], + V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]] + >( + ...args: [ + ...shape: T, + colorStop: InitialLinearColorStop, + ...colorStopOrHint: V + ] + ): Return; +} +export const radialGradient: RadialGradientFunction = ( + ...args +) => { + return `radial-gradient(${args + .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) + .join(", ")})` as RadialGradient; +}; + +export type RepeatingRadialGradient = TackyVariant<"RepeatingRadialGradient">; +export const repeatingRadialGradient: RadialGradientFunction = ( + ...args +) => { + return `repeating-radial-gradient(${args + .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) + .join(", ")})` as RepeatingRadialGradient; +}; + +// TODO: conical-gradient() +export type CSSGradient = + | LinearGradient + | RepeatingLinearGradient + | RadialGradient + | RepeatingRadialGradient; + +// TODO: cross-fade() +// TODO: image-set() +export type CSSImage = CSSURL | CSSGradient; diff --git a/packages/tacky-css/src/index.ts b/packages/tacky-css/src/index.ts new file mode 100644 index 0000000..37def18 --- /dev/null +++ b/packages/tacky-css/src/index.ts @@ -0,0 +1,35 @@ +import { CSSObject } from "@emotion/react"; + +import * as color from "./color"; +import * as funktion from "./function"; +import * as image from "./image"; +import { media } from "./media"; +import * as property from "./property"; +import * as unit from "./unit"; + +export { property, unit, color, funktion }; + +import { TypedCSSArray } from "./types"; + +export const tackyArg = { + ...color, + ...funktion, + ...image, + ...property, + ...unit, + media, +} as const; + +type TackyArg = typeof tackyArg; + +export const compile = (styles: TypedCSSArray): CSSObject => + styles.reduce((acc, [key, value]) => { + // Investigate TS2590 without this cast + acc[key as string] = Array.isArray(value) ? compile(value) : value; + return acc; + }, {} as CSSObject); + +export const tacky = (callback: (_: TackyArg) => TypedCSSArray): CSSObject => + compile(callback(tackyArg)); + +export type Tacky = typeof tacky; diff --git a/packages/tacky-css/src/macro.ts b/packages/tacky-css/src/macro.ts new file mode 100644 index 0000000..488cb7a --- /dev/null +++ b/packages/tacky-css/src/macro.ts @@ -0,0 +1,170 @@ +import { createMacro } from "babel-plugin-macros"; +import { Expression } from "@babel/types"; +import generate from "@babel/generator"; +import { parseExpression } from "@babel/parser"; +import { nanoid } from "nanoid"; + +import { tackyArg as _, compile } from "./index"; +import type { Tacky } from "./index"; + +const generalError = new Error( + "Tacky has been called in a manner unsupported by its macro implementation. Please see the usage example in the README or consider using the runtime implementation instead." +); + +const macro = createMacro(({ babel, references }) => { + const t = babel.types; + const { tacky = [] } = references; + + tacky.forEach(path => { + const functionPath = path.parentPath; + if (!functionPath.isCallExpression()) { + // Tacky wasn't called + throw generalError; + } + + const args = functionPath.get("arguments"); + if (!Array.isArray(args)) { + // When does this ever happen? + throw generalError; + } + + const callback = args[0]; + if ( + !callback.isArrowFunctionExpression() && + !callback.isFunctionExpression() + ) { + // Function wasn't declared inline, or invalid call + throw generalError; + } + + const stylesArray = callback.get("body"); + if (Array.isArray(stylesArray) || !stylesArray.isArrayExpression()) { + throw generalError; + } + + const tackyParam = callback.node.params[0]; + if (tackyParam.type !== "Identifier") { + // Called in an odd way. Is there any reason to do this? + throw generalError; + } + + const tackyParamName = tackyParam.name; + const idToOldNodeMap: Record = {}; + + // First, pre-evaluate as much as possible. + stylesArray.traverse({ + CallExpression: { + exit(path) { + // Check that we're calling a Tacky helper + const callee = path.get("callee"); + if (!callee.isMemberExpression()) { + return; + } + + // Go backwards through property accesses until we reach the "root". + // This is to handle e.g. _.media.minWidth(...) + let calleeIdentifier = callee.get("object"); + while ( + !Array.isArray(calleeIdentifier) && + calleeIdentifier.isMemberExpression() + ) { + calleeIdentifier = calleeIdentifier.get("object"); + } + + if ( + Array.isArray(calleeIdentifier) || + !calleeIdentifier.isIdentifier() + ) { + return; + } + + if (calleeIdentifier.node.name !== tackyParamName) { + // Someone else's code, don't touch it + return; + } + + // For each arg, replace any non-literal expression with a string ID + path.get("arguments").forEach(argPath => { + if (argPath.isSpreadElement()) { + // not supported yet, spreading the arguments precludes + // pre-evaluation... for now + throw generalError; + } + + const node = argPath.node; + if (node.type !== "Identifier") { + return node; + } + + const id = nanoid(); + idToOldNodeMap[id] = node; + argPath.replaceWith(t.stringLiteral(id)); + }); + + // Replace Tacky param name with "this" so we can give the eval + // call the relevant context + calleeIdentifier.replaceWith(t.identifier("this")); + + // Evaluate the function and insert into AST + const code = generate(path.node).code; + + function evalInContext(): string { + return JSON.stringify(eval(code)); + } + + const evaluationResult = evalInContext.call(_); + const newNode = parseExpression(evaluationResult); + + path.replaceWith(newNode); + }, + }, + }); + + // stylesArray should no longer have runtime dependencies, so we can + // compile it to a styles object (keeping any escaped expressions). + const generatedStyles = eval(generate(stylesArray.node).code); + const compiledStyles = JSON.stringify(compile(eval(generatedStyles))); + const compiledAst = parseExpression(compiledStyles); + functionPath.replaceWith(compiledAst); + + // Go back and insert expressions back into the AST. + functionPath.traverse({ + StringLiteral(path) { + for (const [id, node] of Object.entries(idToOldNodeMap)) { + const split = path.node.value.split(id); + if (split.length === 1) { + return; + } + + if (split[0] === "" && split[1] === "") { + // No interpolation required in this case. + path.replaceWith(node); + } else { + // Otherwise, insert the expression into a template literal. + const head = t.templateElement( + { raw: split[0], cooked: split[0] }, + false + ); + const tail = t.templateElement( + { raw: split[1], cooked: split[1] }, + true + ); + + path.replaceWith(t.templateLiteral([head, tail], [node])); + } + + // Each ID should only appear once in the AST so there's no need to + // check for this one again. + delete idToOldNodeMap[id]; + + break; + } + }, + }); + }); +}); + +export const tacky: Tacky = (undefined as unknown) as Tacky; + +const macroExport: never = macro as never; +export default macroExport; diff --git a/packages/tacky-css/src/media/index.ts b/packages/tacky-css/src/media/index.ts new file mode 100644 index 0000000..54c5488 --- /dev/null +++ b/packages/tacky-css/src/media/index.ts @@ -0,0 +1,51 @@ +import { TypedCSSArray } from "../types"; +import * as mediaFeatures from "./mediaFeatures"; +import * as mediaTypes from "./mediaTypes"; +import * as operators from "./mediaTypes"; +import { + AnyMediaMember, + MediaType, + MediaTypeExpression, + MediaQuery, +} from "./types"; + +type RequiredCSSArray = [TypedCSSArray[number], ...TypedCSSArray]; + +const mediaHelpers = { + ...mediaFeatures, + ...mediaTypes, + ...operators, +} as const; + +type MediaHelpers = typeof mediaHelpers; + +export interface Media extends MediaHelpers { + ( + expressions: [AnyMediaMember, ...AnyMediaMember[]], + ...styles: RequiredCSSArray + ): [MediaQuery, TypedCSSArray]; + ( + expressions: [ + "not" | "only", + MediaType | MediaTypeExpression, + ...AnyMediaMember[] + ], + ...styles: RequiredCSSArray + ): [MediaQuery, TypedCSSArray]; +} + +export const media: Media = Object.assign( + (((expressions: string[], ...styles: unknown[]) => { + let joinedExpressions: string; + if (["not", "only"].includes(expressions[0])) { + joinedExpressions = `${expressions[0]} ${expressions + .slice(1) + .join(", ")}`; + } else { + joinedExpressions = expressions.join(", "); + } + + return [`@media ${joinedExpressions}`, styles]; + }) as unknown) as Media, + mediaHelpers +); diff --git a/packages/tacky-css/src/media/mediaFeatures.ts b/packages/tacky-css/src/media/mediaFeatures.ts new file mode 100644 index 0000000..74b19c4 --- /dev/null +++ b/packages/tacky-css/src/media/mediaFeatures.ts @@ -0,0 +1,70 @@ +// TODO: Ratio is tricky at the moment as it doesn't support decimal values and +// TS can't currently enforce integer values + +import { CSSLength, CSSResolution } from "../unit"; +import { MediaExpression } from "./types"; + +export const anyHover = (support: "none" | "hover"): MediaExpression => + `(any-hover: ${support})` as MediaExpression; + +export const anyPointer = ( + accuracy: "none" | "coarse" | "fine" +): MediaExpression => `(any-pointer: ${accuracy})` as MediaExpression; + +export const color = (bits?: number): MediaExpression => + `(color${bits ? `: ${bits}` : ""})` as MediaExpression; + +export const colorGamut = (gamut: "srgb" | "p3" | "rec2020"): MediaExpression => + `(colorGamut: ${gamut})` as MediaExpression; + +export const height = (value: CSSLength): MediaExpression => + `(height: ${value})` as MediaExpression; + +export const hover = (support: "none" | "hover"): MediaExpression => + `(hover: ${support})` as MediaExpression; + +export const maxColor = (bits: number): MediaExpression => + `(max-color: ${bits})` as MediaExpression; + +export const maxHeight = (value: CSSLength): MediaExpression => + `(max-height: ${value})` as MediaExpression; + +export const maxMonochrome = (bits: number): MediaExpression => + `(max-monochrome: ${bits})` as MediaExpression; + +export const maxResolution = (value: CSSResolution): MediaExpression => + `(max-resolution: ${value})` as MediaExpression; + +export const maxWidth = (value: CSSLength): MediaExpression => + `(max-width: ${value})` as MediaExpression; + +export const minColor = (bits: number): MediaExpression => + `(min-color: ${bits})` as MediaExpression; + +export const minHeight = (value: CSSLength): MediaExpression => + `(min-height: ${value})` as MediaExpression; + +export const minMonochrome = (bits: number): MediaExpression => + `(min-monochrome: ${bits})` as MediaExpression; + +export const minResolution = (value: CSSResolution): MediaExpression => + `(min-resolution: ${value})` as MediaExpression; + +export const minWidth = (value: CSSLength): MediaExpression => + `(min-width: ${value})` as MediaExpression; + +export const monochrome = (bits?: number): MediaExpression => + `(monochrome${bits ? `: ${bits}` : ""})` as MediaExpression; + +export const orientation = (value: "portrait" | "landscape"): MediaExpression => + `(orientation: ${value})` as MediaExpression; + +export const pointer = ( + accuracy: "none" | "coarse" | "fine" +): MediaExpression => `(pointer: ${accuracy})` as MediaExpression; + +export const resolution = (value: CSSResolution): MediaExpression => + `(resolution: ${value})` as MediaExpression; + +export const width = (value: CSSLength): MediaExpression => + `(width: ${value})` as MediaExpression; diff --git a/packages/tacky-css/src/media/mediaTypes.ts b/packages/tacky-css/src/media/mediaTypes.ts new file mode 100644 index 0000000..90149a9 --- /dev/null +++ b/packages/tacky-css/src/media/mediaTypes.ts @@ -0,0 +1,43 @@ +import { MediaType, MediaTypeExpression, MediaExpression } from "./types"; + +export function screen(): MediaType; +export function screen( + ...expressions: [MediaExpression, ...MediaExpression[]] +): MediaTypeExpression; +export function screen( + ...expressions: unknown[] +): MediaType | MediaTypeExpression { + if (expressions.length === 0) { + return "screen" as MediaType; + } + + return `screen and ${expressions.join(" and ")}` as MediaTypeExpression; +} + +export function print(): MediaType; +export function print( + ...expressions: [MediaExpression, ...MediaExpression[]] +): MediaTypeExpression; +export function print( + ...expressions: unknown[] +): MediaType | MediaTypeExpression { + if (expressions.length === 0) { + return "print" as MediaType; + } + + return `print and ${expressions.join(" and ")}` as MediaTypeExpression; +} + +export function speech(): MediaType; +export function speech( + ...expressions: [MediaExpression, ...MediaExpression[]] +): MediaTypeExpression; +export function speech( + ...expressions: unknown[] +): MediaType | MediaTypeExpression { + if (expressions.length === 0) { + return "speech" as MediaType; + } + + return `speech and ${expressions.join(" and ")}` as MediaTypeExpression; +} diff --git a/packages/tacky-css/src/media/operators.ts b/packages/tacky-css/src/media/operators.ts new file mode 100644 index 0000000..4a22596 --- /dev/null +++ b/packages/tacky-css/src/media/operators.ts @@ -0,0 +1,4 @@ +import { MediaExpression } from "./types"; + +export const and = (...expressions: MediaExpression[]): MediaExpression => + expressions.join(" and ") as MediaExpression; diff --git a/packages/tacky-css/src/media/types.ts b/packages/tacky-css/src/media/types.ts new file mode 100644 index 0000000..87b8538 --- /dev/null +++ b/packages/tacky-css/src/media/types.ts @@ -0,0 +1,15 @@ +import { TackyVariant } from "../types"; + +// e.g. (min-width: 30rem) +export type MediaExpression = TackyVariant<"media_expression">; + +// e.g. screen +export type MediaType = TackyVariant<"media_type">; + +// e.g. screen and (min-width: 30rem) +export type MediaTypeExpression = TackyVariant<"media_expression">; + +export type AnyMediaMember = MediaType | MediaExpression | MediaTypeExpression; + +// e.g. @media screen and (min-width: 30rem) +export type MediaQuery = TackyVariant<"media_query">; diff --git a/packages/tacky-css/src/property/animations.ts b/packages/tacky-css/src/property/animations.ts new file mode 100644 index 0000000..f35b95c --- /dev/null +++ b/packages/tacky-css/src/property/animations.ts @@ -0,0 +1,25 @@ +// TODO: Multiple values for each of these +import { Property, Values } from "../generated/types"; + +export const animationDelay: Property.AnimationDelay = (arg: unknown) => + ["animationDelay", arg as Values["animationDelay"]] as const; + +export const animationDirection: Property.AnimationDirection = (arg: unknown) => + ["animationDirection", arg as Values["animationDirection"]] as const; + +export const animationDuration: Property.AnimationDuration = (arg: unknown) => + ["animationDuration", arg as Values["animationDuration"]] as const; + +export const animationFillMode: Property.AnimationFillMode = (arg: unknown) => + ["animationFillMode", arg as Values["animationFillMode"]] as const; + +export const animationIterationCount: Property.AnimationIterationCount = ( + arg: unknown +) => + [ + "animationIterationCount", + arg as Values["animationIterationCount"], + ] as const; + +export const animationPlayState: Property.AnimationPlayState = (arg: unknown) => + ["animationPlayState", arg as Values["animationPlayState"]] as const; diff --git a/packages/tacky-css/src/property/backgroundsAndBorders.ts b/packages/tacky-css/src/property/backgroundsAndBorders.ts new file mode 100644 index 0000000..5db3e03 --- /dev/null +++ b/packages/tacky-css/src/property/backgroundsAndBorders.ts @@ -0,0 +1,370 @@ +// TODO: Elliptical radii +import * as CSS from "csstype"; +import { CSSColor } from "../color"; +import { CSSImage } from "../image"; +import { KnownCSSValues } from "../types"; +import { CSSLength, CSSLengthPercentage, Percent } from "../unit"; +import { FourDimensionalArgs, FourDimensionalProperty } from "../utils"; +import { Property, Values, PropertyTuple } from "../generated/types"; + +type BackgroundAttachmentKeyword = Exclude< + KnownCSSValues<"backgroundAttachment">, + CSS.Globals +>; + +export interface GenericBorder { + (style: BorderStyleValue): PropertyTuple; + (style: BorderStyleValue, color: CSSColor): PropertyTuple; + (width: CSSLengthPercentage, style: BorderStyleValue): PropertyTuple; + ( + width: CSSLengthPercentage, + style: BorderStyleValue, + color: CSSColor + ): PropertyTuple; +} + +declare module "../generated/types" { + namespace Property { + export interface BackgroundAttachment { + ( + ...attachments: [ + BackgroundAttachmentKeyword, + ...BackgroundAttachmentKeyword[] + ] + ): PropertyTuple<"backgroundAttachment">; + } + + export interface BackgroundClip { + ( + ...clip: [BackgroundClipKeyword, ...BackgroundClipKeyword[]] + ): PropertyTuple<"backgroundClip">; + } + + export interface BackgroundImage { + (...backgrounds: [CSSImage, ...CSSImage[]]): PropertyTuple< + "backgroundImage" + >; + } + + export interface BackgroundOrigin { + ( + ...origin: [BackgroundOriginKeyword, ...BackgroundOriginKeyword[]] + ): PropertyTuple<"backgroundOrigin">; + } + + export interface BackgroundPosition { + ( + ...args: [ + PartialBackgroundPositionArgs, + PartialBackgroundPositionArgs[] + ] + ): PropertyTuple<"backgroundPosition">; + } + + export interface BackgroundRepeat { + ( + x: BackgroundRepeatOptionKeyword, + y: BackgroundRepeatOptionKeyword + ): PropertyTuple<"backgroundRepeat">; + } + + export interface BackgroundSize { + ( + width: CSSLengthPercentage | "auto", + height: CSSLengthPercentage | "auto" + ): PropertyTuple<"backgroundSize">; + } + + export interface Border extends GenericBorder<"border"> {} + + export interface BorderBottom extends GenericBorder<"borderBottom"> {} + + export interface BorderColor { + (...args: FourDimensionalArgs): PropertyTuple<"borderColor">; + } + + export interface BorderImageOutset { + (...args: FourDimensionalArgs): PropertyTuple< + "borderImageOutset" + >; + } + + export interface BorderImageRepeat { + ( + topAndBottom: BorderImageRepeatKeyword, + leftAndRight: BorderImageRepeatKeyword + ): PropertyTuple<"borderImageRepeat">; + } + + // TODO: Allow "fill" keyword at any position + export interface BorderImageSlice { + (all: number | Percent, fill?: "fill"): PropertyTuple<"borderImageSlice">; + ( + vertical: number | Percent, + horizontal: number | Percent, + fill?: "fill" + ): PropertyTuple<"borderImageSlice">; + ( + top: number | Percent, + horizontal: number | Percent, + bottom: number | Percent, + fill?: "fill" + ): PropertyTuple<"borderImageSlice">; + ( + top: number | Percent, + left: number | Percent, + bottom: number | Percent, + right: number | Percent, + fill?: "fill" + ): PropertyTuple<"borderImageSlice">; + } + + export interface BorderImageWidth + extends FourDimensionalProperty< + PropertyTuple<"borderImageWidth">, + CSSLengthPercentage + > {} + + export interface BorderLeft extends GenericBorder<"borderLeft"> {} + + export interface BorderRadius { + (...args: BorderRadiusEllipticalCorners): PropertyTuple<"borderRadius">; + } + + export interface BorderRight extends GenericBorder<"borderRight"> {} + + export interface BorderStyle { + (...styles: [BorderStyleValue, ...BorderStyleValue[]]): PropertyTuple< + "borderStyle" + >; + } + + export interface BorderTop extends GenericBorder<"borderTop"> {} + + export interface BorderWidth { + (...args: FourDimensionalArgs): PropertyTuple<"borderWidth">; + } + + export interface BoxShadow { + ( + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage, + spreadLength: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + ( + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + ( + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + ( + inset: "inset", + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage, + spreadLength: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + ( + inset: "inset", + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + ( + inset: "inset", + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + color?: CSSColor + ): PropertyTuple<"boxShadow">; + } + } +} + +export const backgroundAttachment: Property.BackgroundAttachment = ( + ...args: unknown[] +) => + [ + "backgroundAttachment", + args.join(", ") as Values["backgroundAttachment"], + ] as const; + +// TODO: This is just +type BackgroundClipKeyword = Exclude< + KnownCSSValues<"backgroundClip">, + CSS.Globals +>; + +export const backgroundClip: Property.BackgroundClip = (...clip: unknown[]) => + ["backgroundClip", clip.join(", ") as Values["backgroundClip"]] as const; + +export const backgroundImage: Property.BackgroundImage = (...args: unknown[]) => + ["backgroundImage", args.join(", ") as Values["backgroundImage"]] as const; + +// TODO: This is just +type BackgroundOriginKeyword = Exclude< + KnownCSSValues<"backgroundOrigin">, + CSS.Globals +>; + +export const backgroundOrigin: Property.BackgroundOrigin = ( + ...origin: unknown[] +) => + [ + "backgroundOrigin", + origin.join(", ") as Values["backgroundOrigin"], + ] as const; + +type BackgroundPositionKeyword = "top" | "left" | "bottom" | "right" | "center"; + +type PartialBackgroundPositionArgs = + | [x: "left" | "right", y: "top" | "bottom" | CSSLengthPercentage] + | [y: "top" | "bottom", x: "left" | "right" | CSSLengthPercentage] + | [x: CSSLengthPercentage, y: CSSLengthPercentage] + | [ + x: "left" | "right", + xOffset: CSSLengthPercentage, + y: "top" | "bottom", + yOffset: CSSLengthPercentage + ]; + +export type BackgroundPositionArgs = + | [all: BackgroundPositionKeyword] + | [left: CSSLengthPercentage] + | PartialBackgroundPositionArgs; + +export const backgroundPosition: Property.BackgroundPosition = ( + ...args: unknown[] +) => + [ + "backgroundPosition", + (Array.isArray(args[0]) + ? (args as string[][]).map(position => position.join(" ")).join(", ") + : args.join(" ")) as Values["backgroundPosition"], + ] as const; + +type BackgroundRepeatOptionKeyword = "repeat" | "space" | "round" | "no-repeat"; + +export const backgroundRepeat: Property.BackgroundRepeat = ( + ...args: unknown[] +) => + [ + "backgroundRepeat", + (Array.isArray(args[0]) + ? (args as string[][]).map(repeat => repeat.join(" ")).join(", ") + : args.join(" ")) as Values["backgroundRepeat"], + ] as const; + +export const backgroundSize: Property.BackgroundSize = (...args: unknown[]) => + [ + "backgroundSize", + (Array.isArray(args[0]) + ? (args as string[][]).map(position => position.join(" ")).join(", ") + : args.join(" ")) as Values["backgroundSize"], + ] as const; + +type BorderStyleValue = Exclude, CSS.Globals>; + +export const border: Property.Border = (...args: unknown[]) => + ["border", args.join(" ") as Values["border"]] as const; + +export const borderBottom: Property.BorderBottom = (...args: unknown[]) => + ["borderBottom", args.join(" ") as Values["borderBottom"]] as const; + +export const borderBottomLeftRadius: Property.BorderBottomLeftRadius = ( + arg: unknown +) => + ["borderBottomLeftRadius", arg as Values["borderBottomLeftRadius"]] as const; + +export const borderBottomRightRadius: Property.BorderBottomRightRadius = ( + arg: unknown +) => + [ + "borderBottomRightRadius", + arg as Values["borderBottomRightRadius"], + ] as const; + +export const borderColor: Property.BorderColor = (...args: unknown[]) => + ["borderColor", args.join(" ") as Values["borderColor"]] as const; + +export const borderImageOutset: Property.BorderImageOutset = ( + ...args: unknown[] +) => + ["borderImageOutset", args.join(" ") as Values["borderImageOutset"]] as const; + +type BorderImageRepeatKeyword = "stretch" | "repeat" | "round" | "space"; + +export const borderImageRepeat: Property.BorderImageRepeat = ( + ...args: unknown[] +) => + ["borderImageRepeat", args.join(" ") as Values["borderImageRepeat"]] as const; + +export const borderImageSlice: Property.BorderImageSlice = ( + ...args: unknown[] +) => + ["borderImageSlice", args.join(" ") as Values["borderImageSlice"]] as const; + +export const borderImageWidth: Property.BorderImageWidth = ( + ...args: unknown[] +) => + ["borderImageWidth", args.join(" ") as Values["borderImageWidth"]] as const; + +export const borderLeft: Property.BorderLeft = (...args: unknown[]) => + ["borderLeft", args.join(" ") as Values["borderLeft"]] as const; + +type BorderRadiusCorners = + | [all: CSSLengthPercentage] + | [ + topLeftAndBottomRight: CSSLengthPercentage, + topRightAndBottomLeft: CSSLengthPercentage + ] + | [ + topLeft: CSSLengthPercentage, + topRightAndBottomLeft: CSSLengthPercentage, + bottomRight: CSSLengthPercentage + ] + | [ + topLeft: CSSLengthPercentage, + topRight: CSSLengthPercentage, + bottomRight: CSSLengthPercentage, + bottomLeft: CSSLengthPercentage + ]; + +export type BorderRadiusEllipticalCorners = [ + ...BorderRadiusCorners, + ...([] | ["/", ...BorderRadiusCorners]) +]; + +export const borderRadius: Property.BorderRadius = (...args: unknown[]) => + ["borderRadius", args.join(" ") as Values["borderRadius"]] as const; + +export const borderRight: Property.BorderRight = (...args: unknown[]) => + ["borderRight", args.join(" ") as Values["borderRight"]] as const; + +export const borderStyle: Property.BorderStyle = (...args: unknown[]) => + ["borderStyle", args.join(" ") as Values["borderStyle"]] as const; + +export const borderTop: Property.BorderTop = (...args: unknown[]) => + ["borderTop", args.join(" ") as Values["borderTop"]] as const; + +export const borderTopLeftRadius: Property.BorderTopLeftRadius = ( + arg: unknown +) => ["borderTopLeftRadius", arg as Values["borderTopLeftRadius"]] as const; + +export const borderTopRightRadius: Property.BorderTopRightRadius = ( + arg: unknown +) => ["borderTopRightRadius", arg as Values["borderTopRightRadius"]] as const; + +export const borderWidth: Property.BorderWidth = (...args: unknown[]) => + ["borderWidth", args.join(" ") as Values["borderWidth"]] as const; + +export const boxShadow: Property.BoxShadow = (...args: unknown[]) => + ["boxShadow", args.join(" ") as Values["boxShadow"]] as const; diff --git a/packages/tacky-css/src/property/basicUserInterface.ts b/packages/tacky-css/src/property/basicUserInterface.ts new file mode 100644 index 0000000..4e78ac4 --- /dev/null +++ b/packages/tacky-css/src/property/basicUserInterface.ts @@ -0,0 +1,10 @@ +import { Property, Values } from "../generated/types"; + +export const cursor: Property.Cursor = (arg: unknown) => + ["cursor", arg as Values["cursor"]] as const; + +export const outlineStyle: Property.OutlineStyle = (arg: unknown) => + ["outlineStyle", arg as Values["outlineStyle"]] as const; + +export const textOverflow: Property.TextOverflow = (arg: unknown) => + ["textOverflow", arg as Values["textOverflow"]] as const; diff --git a/packages/tacky-css/src/property/boxAlignment.ts b/packages/tacky-css/src/property/boxAlignment.ts new file mode 100644 index 0000000..4613526 --- /dev/null +++ b/packages/tacky-css/src/property/boxAlignment.ts @@ -0,0 +1,20 @@ +import { Values, Property } from "../generated/types"; +// TODO: these all have additional syntaxes + +export const alignContent: Property.AlignContent = (arg: unknown) => + ["alignContent", arg as Values["alignContent"]] as const; + +export const alignItems: Property.AlignItems = (arg: unknown) => + ["alignItems", arg as Values["alignItems"]] as const; + +export const alignSelf: Property.AlignSelf = (arg: unknown) => + ["alignSelf", arg as Values["alignSelf"]] as const; + +export const justifyContent: Property.JustifyContent = (arg: unknown) => + ["justifyContent", arg as Values["justifyContent"]] as const; + +export const justifyItems: Property.JustifyItems = (arg: unknown) => + ["justifyItems", arg as Values["justifyItems"]] as const; + +export const justifySelf: Property.JustifySelf = (arg: unknown) => + ["justifySelf", arg as Values["justifySelf"]] as const; diff --git a/packages/tacky-css/src/property/boxModel.ts b/packages/tacky-css/src/property/boxModel.ts new file mode 100644 index 0000000..d3c44cc --- /dev/null +++ b/packages/tacky-css/src/property/boxModel.ts @@ -0,0 +1,21 @@ +import { Values, Property } from "../generated/types"; +import { FourDimensionalArgs } from "../utils"; + +declare module "../generated/types" { + namespace Property { + export interface Margin { + (...args: FourDimensionalArgs): PropertyTuple<"margin">; + } + + export interface Padding { + (...args: FourDimensionalArgs): PropertyTuple<"padding">; + } + } +} + +// TODO: This is missing from the MDN page on Box Model module +export const margin: Property.Margin = (...args: unknown[]) => + ["margin", args.join(" ") as Values["margin"]] as const; + +export const padding: Property.Padding = (...args: unknown[]) => + ["padding", args.join(" ") as Values["padding"]] as const; diff --git a/packages/tacky-css/src/property/compositingAndBlending.ts b/packages/tacky-css/src/property/compositingAndBlending.ts new file mode 100644 index 0000000..f9f8f4b --- /dev/null +++ b/packages/tacky-css/src/property/compositingAndBlending.ts @@ -0,0 +1,26 @@ +import * as CSS from "csstype"; +import { KnownCSSValues } from "../types"; +import { Property, Values } from "../generated/types"; + +type BackgroundBlendModeValue = Exclude< + KnownCSSValues<"backgroundBlendMode">, + CSS.Globals +>; + +declare module "../generated/types" { + namespace Property { + export interface BackgroundBlendMode { + ( + ...attachment: [BackgroundBlendModeValue, ...BackgroundBlendModeValue[]] + ): PropertyTuple<"backgroundBlendMode">; + } + } +} + +export const backgroundBlendMode: Property.BackgroundBlendMode = ( + ...args: unknown[] +) => + [ + "backgroundBlendMode", + (args as string[]).join(", ") as Values["backgroundBlendMode"], + ] as const; diff --git a/packages/tacky-css/src/property/containment.ts b/packages/tacky-css/src/property/containment.ts new file mode 100644 index 0000000..83b778c --- /dev/null +++ b/packages/tacky-css/src/property/containment.ts @@ -0,0 +1,15 @@ +import { Values, Property } from "../generated/types"; + +type ContainMultipleKeyword = "size" | "layout" | "style" | "paint"; + +declare module "../generated/types" { + namespace Property { + export interface Contain { + // TODO: Prevent duplicate keyword arguments + (...keywords: ContainMultipleKeyword[]): PropertyTuple<"contain">; + } + } +} + +export const contain: Property.Contain = (...args: unknown[]) => + ["contain", args.join(" ") as Values["contain"]] as const; diff --git a/packages/tacky-css/src/property/display.ts b/packages/tacky-css/src/property/display.ts new file mode 100644 index 0000000..ec0bd13 --- /dev/null +++ b/packages/tacky-css/src/property/display.ts @@ -0,0 +1,5 @@ +import { Property, Values } from "../generated/types"; + +// TODO: This is incorrect +export const display: Property.Display = (arg: unknown) => + ["display", arg as Values["display"]] as const; diff --git a/packages/tacky-css/src/property/flexibleBoxLayout.ts b/packages/tacky-css/src/property/flexibleBoxLayout.ts new file mode 100644 index 0000000..4109904 --- /dev/null +++ b/packages/tacky-css/src/property/flexibleBoxLayout.ts @@ -0,0 +1,30 @@ +import * as CSS from "csstype"; +import { Property, Values } from "../generated/types"; + +type FlexDirectionValue = Exclude; +type FlexWrapValue = Exclude; +type FlexBasisValue = Exclude[0], CSS.Globals>; + +declare module "../generated/types" { + namespace Property { + export interface Flex { + (grow: number, shrink: number): PropertyTuple<"flex">; + (grow: number, basis: FlexBasisValue): PropertyTuple<"flex">; + (grow: number, shrink: number, basis: FlexBasisValue): PropertyTuple< + "flex" + >; + } + + export interface FlexFlow { + (direction: FlexDirectionValue, wrap: FlexWrapValue): PropertyTuple< + "flexFlow" + >; + } + } +} + +export const flex: Property.Flex = (...args: unknown[]) => + ["flex", args.join(" ") as Values["flex"]] as const; + +export const flexFlow: Property.FlexFlow = (...args: unknown[]) => + ["flexFlow", args.join(" ") as Values["flexFlow"]] as const; diff --git a/packages/tacky-css/src/property/fonts.ts b/packages/tacky-css/src/property/fonts.ts new file mode 100644 index 0000000..22ea110 --- /dev/null +++ b/packages/tacky-css/src/property/fonts.ts @@ -0,0 +1,18 @@ +import { Property, Values } from "../generated/types"; + +declare module "../generated/types" { + namespace Property { + // TODO: User-extendable interface + export interface FontFamily { + (...fontFamilies: [string, ...string[]]): PropertyTuple<"fontFamily">; + } + } +} + +export const fontFamily: Property.FontFamily = (...args: unknown[]) => + [ + "fontFamily", + (args as string[]) + .map(family => (family.includes(" ") ? `"${family}"` : family)) + .join(", ") as Values["fontFamily"], + ] as const; diff --git a/packages/tacky-css/src/property/generatedContent.ts b/packages/tacky-css/src/property/generatedContent.ts new file mode 100644 index 0000000..32da8b8 --- /dev/null +++ b/packages/tacky-css/src/property/generatedContent.ts @@ -0,0 +1,22 @@ +import { Property, Values } from "../generated/types"; + +type QuotesPair = [open: string, close: string]; + +declare module "../generated/types" { + namespace Property { + export interface Quotes { + (...quotes: QuotesPair[]): PropertyTuple<"quotes">; + } + } +} + +export const quotes: Property.Quotes = (...args: unknown[]) => + [ + "quotes", + (Array.isArray(args[0]) + ? args + .flat() + .map(c => `"${c}"`) + .join(" ") + : args[0]) as Values["quotes"], + ] as const; diff --git a/packages/tacky-css/src/property/index.ts b/packages/tacky-css/src/property/index.ts new file mode 100644 index 0000000..6248f10 --- /dev/null +++ b/packages/tacky-css/src/property/index.ts @@ -0,0 +1,18 @@ +export * from "./animations"; +export * from "./backgroundsAndBorders"; +export * from "./basicUserInterface"; +export * from "./boxAlignment"; +export * from "./boxModel"; +export * from "./compositingAndBlending"; +export * from "./containment"; +export * from "./display"; +export * from "./flexibleBoxLayout"; +export * from "./fonts"; +export * from "./generatedContent"; +export * from "./masking"; +export * from "./overflow"; +export * from "./textDecoration"; +export * from "./transitions"; +export * from "./writingModes"; + +export * from "../generated/property"; diff --git a/packages/tacky-css/src/property/masking.ts b/packages/tacky-css/src/property/masking.ts new file mode 100644 index 0000000..c2acdda --- /dev/null +++ b/packages/tacky-css/src/property/masking.ts @@ -0,0 +1,16 @@ +import { CSSURL } from "../function"; +import { Values, Property } from "../generated/types"; +import { CSSShape } from "../shape"; + +// TODO: MDN defines a value which only seems to work in Firefox +declare module "../generated/types" { + namespace Property { + export interface ClipPath { + (clipSource: CSSURL): PropertyTuple<"clipPath">; + (basicShape: CSSShape): PropertyTuple<"clipPath">; + } + } +} + +export const clipPath: Property.ClipPath = (value: unknown) => + ["clipPath", value as Values["clipPath"]] as const; diff --git a/packages/tacky-css/src/property/overflow.ts b/packages/tacky-css/src/property/overflow.ts new file mode 100644 index 0000000..ad73ad3 --- /dev/null +++ b/packages/tacky-css/src/property/overflow.ts @@ -0,0 +1,16 @@ +import * as CSS from "csstype"; +import { KnownCSSValues } from "../types"; +import { Property, Values } from "../generated/types"; + +type OverflowKeyword = Exclude, CSS.Globals>; + +declare module "../generated/types" { + namespace Property { + export interface Overflow { + (x: OverflowKeyword, y: OverflowKeyword): PropertyTuple<"overflow">; + } + } +} + +export const overflow: Property.Overflow = (...args: unknown[]) => + ["overflow", args.join(" ") as Values["overflow"]] as const; diff --git a/packages/tacky-css/src/property/textDecoration.ts b/packages/tacky-css/src/property/textDecoration.ts new file mode 100644 index 0000000..0fcdec6 --- /dev/null +++ b/packages/tacky-css/src/property/textDecoration.ts @@ -0,0 +1,63 @@ +import { CSSColor } from "../color"; +import { CSSLengthPercentage } from "../unit"; +import { Property, Values } from "../generated/types"; + +declare module "../generated/types" { + namespace Property { + export interface TextDecorationLine { + ( + // TODO: Prevent duplicate keyword arguments + ...lines: [TextDecorationLineKeyword, ...TextDecorationLineKeyword[]] + ): PropertyTuple<"textDecorationLine">; + } + + export interface TextShadow { + (...shadows: [TextShadowArgs, ...TextShadowArgs[]]): PropertyTuple< + "textShadow" + >; + } + } +} + +type TextDecorationLineKeyword = + | "blink" + | "grammar-error" + | "line-through" + | "overline" + | "spelling-error" + | "underline"; + +export const textDecorationLine: Property.TextDecorationLine = ( + ...args: unknown[] +) => + [ + "textDecorationLine", + args.join(" ") as Values["textDecorationLine"], + ] as const; + +type TextShadowArgs = + | [offsetX: CSSLengthPercentage, offsetY: CSSLengthPercentage] + | [ + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + color: CSSColor + ] + | [ + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage + ] + | [ + offsetX: CSSLengthPercentage, + offsetY: CSSLengthPercentage, + blurRadius: CSSLengthPercentage, + color: CSSColor + ]; + +export const textShadow: Property.TextShadow = (...args: unknown[]) => + [ + "textShadow", + (Array.isArray(args[0]) + ? args.map(shadow => (shadow as string[]).join(" ")).join("; ") + : args.join(" ")) as Values["textShadow"], + ] as const; diff --git a/packages/tacky-css/src/property/transitions.ts b/packages/tacky-css/src/property/transitions.ts new file mode 100644 index 0000000..6751c30 --- /dev/null +++ b/packages/tacky-css/src/property/transitions.ts @@ -0,0 +1,41 @@ +import { AnimatableProperties } from "../animation"; +import { PropertyTuple } from "../utils"; +import { CSSTime } from "../unit"; +import { Values, Property } from "../generated/types"; + +declare module "../generated/types" { + namespace Property { + export interface TransitionDelay { + (...delay: [CSSTime, ...CSSTime[]]): PropertyTuple<"transitionDelay">; + } + + export interface TransitionDuration { + (...delay: [CSSTime, ...CSSTime[]]): PropertyTuple<"transitionDuration">; + } + + export interface TransitionProperty { + ( + ...properties: [AnimatableProperties, ...AnimatableProperties[]] + ): PropertyTuple<"transitionProperty">; + } + } +} + +export const transitionDelay: Property.TransitionDelay = (...args: unknown[]) => + ["transitionDelay", args.join(", ") as Values["transitionDelay"]] as const; + +export const transitionDuration: Property.TransitionDuration = ( + ...args: unknown[] +) => + [ + "transitionDuration", + args.join(", ") as Values["transitionDuration"], + ] as const; + +export const transitionProperty = ( + ...args: unknown[] +): PropertyTuple<"transitionProperty"> => + [ + "transitionProperty", + args.join(", ") as Values["transitionProperty"], + ] as const; diff --git a/packages/tacky-css/src/property/writingModes.ts b/packages/tacky-css/src/property/writingModes.ts new file mode 100644 index 0000000..e092fd4 --- /dev/null +++ b/packages/tacky-css/src/property/writingModes.ts @@ -0,0 +1,4 @@ +import { Values, Property } from "../generated/types"; + +export const textCombineUpright: Property.TextCombineUpright = (arg: unknown) => + ["textCombineUpright", arg as Values["textCombineUpright"]] as const; diff --git a/packages/tacky-css/src/shape.ts b/packages/tacky-css/src/shape.ts new file mode 100644 index 0000000..9a610aa --- /dev/null +++ b/packages/tacky-css/src/shape.ts @@ -0,0 +1,51 @@ +// TODO: path() support +import { + BackgroundPositionArgs, + BorderRadiusEllipticalCorners, +} from "./property"; +import { TackyVariant } from "./types"; +import { CSSLengthPercentage } from "./unit"; +import { FourDimensionalArgs } from "./utils"; + +export type Inset = TackyVariant<"inset">; + +export const inset = ( + ...args: [ + ...FourDimensionalArgs, + ...([] | ["round", ...BorderRadiusEllipticalCorners]) + ] +): Inset => `inset(${args.join(" ")})` as Inset; + +type ShapeRadius = CSSLengthPercentage | "closest-side" | "farthest-side"; + +export type Circle = TackyVariant<"circle">; + +export const circle = ( + ...args: [ + ...([] | [radius: ShapeRadius]), + ...position: [] | ["at", ...BackgroundPositionArgs] + ] +): Circle => `circle(${args.join(" ")})` as Circle; + +export type Ellipse = TackyVariant<"ellipse">; + +export const ellipse = ( + ...args: [ + ...([] | [rx: ShapeRadius, ry: ShapeRadius]), + ...position: [] | ["at", ...BackgroundPositionArgs] + ] +): Ellipse => `ellipse(${args.join(" ")})` as Ellipse; + +type FillRule = "nonzero" | "evenodd"; +type Vertex = [xi: CSSLengthPercentage, yi: CSSLengthPercentage]; + +export type Polygon = TackyVariant<"polygon">; + +export const polygon = ( + ...args: [...([] | [fill: FillRule]), ...vertices: [Vertex, ...Vertex[]]] +): Polygon => + `polygon(${(args as (string | string[])[]) + .map(arg => (Array.isArray(arg) ? arg.join(" ") : arg)) + .join(", ")})` as Polygon; + +export type CSSShape = Inset | Circle | Ellipse | Polygon; diff --git a/packages/tacky-css/src/types.ts b/packages/tacky-css/src/types.ts index 638acd5..d48d7f9 100644 --- a/packages/tacky-css/src/types.ts +++ b/packages/tacky-css/src/types.ts @@ -1,3 +1,7 @@ +import * as CSS from "csstype"; +import { Values } from "./generated/types"; +import { MediaQuery } from "./media/types"; + // Terminology taken from ReasonML - the idea is something like a variant that // takes a single string arg, so the type system can distinguish strings from // different sources even if their values aren't known. @@ -7,3 +11,24 @@ // This is safer as users will be less likely to access the "brand" property // (which is removed by the Babel macro) export type TackyVariant = string & { _tacky_id: T }; + +export type KnownCSSValues< + T extends keyof CSS.Properties, + U extends CSS.Properties[T] = CSS.Properties[T] +> = U extends string | number | symbol + ? { + [K in U]: string extends K ? never : number extends K ? never : K; + } extends { [_ in U]: infer U } + ? // eslint-disable-next-line @typescript-eslint/ban-types + {} extends U + ? never + : U + : never + : never; + +export type TypedCSSArray = ( + | { + [P in keyof Values]: readonly [P, Values[P]]; + }[keyof Values] + | [MediaQuery, TypedCSSArray] +)[]; diff --git a/packages/tacky-css/src/utils.ts b/packages/tacky-css/src/utils.ts new file mode 100644 index 0000000..e5c4bd3 --- /dev/null +++ b/packages/tacky-css/src/utils.ts @@ -0,0 +1,24 @@ +import * as CSS from "csstype"; +import { PropertyTuple } from "./generated/types"; +import { CSSLengthPercentage } from "./unit"; + +export { PropertyTuple }; + +export interface FourDimensionalProperty { + (global: CSS.Globals): ReturnType; + (all: ValueType): ReturnType; + (vertical: ValueType, horizontal: ValueType): ReturnType; + (top: ValueType, right: ValueType, bottom: ValueType): ReturnType; + ( + top: ValueType, + right: ValueType, + bottom: ValueType, + left: ValueType + ): ReturnType; +} + +export type FourDimensionalArgs = + | [all: Value] + | [vertical: Value, horizontal: Value] + | [top: Value, right: Value, bottom: Value] + | [top: Value, right: Value, bottom: Value, left: Value]; diff --git a/packages/tacky-css/tsconfig.json b/packages/tacky-css/tsconfig.json index fa94449..3b592f9 100644 --- a/packages/tacky-css/tsconfig.json +++ b/packages/tacky-css/tsconfig.json @@ -1,6 +1,6 @@ { - //"include": ["./src/*", "./typings/*"], - //"exclude": ["./node_modules"], + "include": ["./src/*", "./typings/*"], + "exclude": ["./node_modules"], "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */