Unverified Commit 3311ee9b authored by Tasso Evangelista's avatar Tasso Evangelista Committed by GitHub

feat: Update and expose logical properties plugin for Stylis (#333)

parent 28c621ec
......@@ -2,9 +2,11 @@
"version": "0.1.0",
"license": "MIT",
"private": true,
"workspaces": [
"packages/*"
],
"workspaces": {
"packages": [
"packages/*"
]
},
"devDependencies": {
"cross-env": "^7.0.2",
"fs-extra": "^9.0.0",
......
......@@ -110,7 +110,7 @@ Transpiles CSS Modules content to CSS rules.
- `selector` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `content` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `options` **TranspileOptions?**
- `middleware` **Middleware** (optional, default `defaultMiddleware`)
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
......
{
"name": "@rocket.chat/css-in-js/logicalProperties",
"private": true,
"main": "../dist/logicalProperties.js",
"module": "../dist/logicalProperties.module.js"
}
......@@ -25,11 +25,12 @@
"unpkg": "dist/index.umd.js",
"types": "dist/index.d.ts",
"files": [
"dist"
"dist",
"logicalProperties"
],
"scripts": {
"start": "microbundle watch",
"build": "microbundle",
"start": "rollup -c -w",
"build": "rollup -c",
"test": "jest",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
"test:ci": "jest --runInBand",
......@@ -39,7 +40,12 @@
},
"devDependencies": {
"@rocket.chat/eslint-config": "^0.4.0",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-typescript": "^6.1.0",
"@types/jest": "^26.0.0",
"@types/stylis": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"documentation": "^13.0.2",
......@@ -49,8 +55,9 @@
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.1.0",
"lint-staged": "^10.0.8",
"microbundle": "^0.12.2",
"prettier": "2.1.2",
"rollup": "^2.33.3",
"rollup-plugin-terser": "^7.0.2",
"ts-jest": "^26.1.1",
"typescript": "^4.0.3"
},
......@@ -59,6 +66,6 @@
},
"dependencies": {
"@emotion/hash": "^0.8.0",
"@emotion/stylis": "^0.8.5"
"stylis": "^4.0.3"
}
}
import path from 'path';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import nodeResolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import pkg from './package.json';
const plugins = [
terser({
compress: true,
mangle: true,
module: true,
output: {
comments: false,
},
}),
json(),
nodeResolve(),
commonjs(),
typescript(),
];
export default [
{
input: 'src/index.ts',
output: [
{
dir: path.dirname(pkg.main),
entryFileNames: path.basename(pkg.main),
format: 'cjs',
sourcemap: true,
},
{
dir: path.dirname(pkg.module),
entryFileNames: path.basename(pkg.module),
format: 'es',
sourcemap: true,
},
{
dir: path.dirname(pkg.unpkg),
entryFileNames: path.basename(pkg.unpkg),
format: 'umd',
name: 'cssInJs',
sourcemap: true,
globals: {
'@emotion/hash': 'hash',
},
},
],
plugins,
},
{
input: 'src/logicalProperties.ts',
output: [
{
dir: path.dirname(pkg.main),
entryFileNames: 'logicalProperties.js',
format: 'cjs',
sourcemap: true,
},
{
dir: path.dirname(pkg.module),
entryFileNames: 'logicalProperties.module.js',
format: 'es',
sourcemap: true,
},
{
dir: path.dirname(pkg.unpkg),
entryFileNames: 'logicalProperties.umd.js',
format: 'umd',
name: 'cssInJs',
sourcemap: true,
},
],
plugins,
},
];
import { transpile, createTranspileMiddleware } from './transpile';
it('transpiles simple properties', () => {
expect(transpile('div', 'color: inherit;')).toMatch('div{color:inherit;}');
});
it('transpiles with vendor prefixing', () => {
expect(transpile('div', 'display: flex;')).toMatch(
'div{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}'
);
});
const property = (
property: string,
...characteristics: ((property: string) => void)[]
): void =>
describe(property, () => {
for (const characteristic of characteristics) {
characteristic(property);
}
});
const isSupported = (vendorPrefixedProperty?: string) => (property: string) =>
it(`supports ${property}`, () => {
expect(
transpile(
'div',
`${property}: inherit;`,
createTranspileMiddleware({
isPropertySupported: (p: string) => p === property,
})
)
).toMatch(
vendorPrefixedProperty
? `div{${vendorPrefixedProperty}:inherit;${property}:inherit;}`
: `div{${property}:inherit;}`
);
});
const fallbacksTo = (...properties: string[]) => (property: string) =>
it(`fallbacks ${property} to ${properties.join(', ')}`, () => {
expect(
transpile(
`div`,
`${property}: inherit;`,
createTranspileMiddleware({
isPropertySupported: (p) => properties.includes(p),
})
)
).toMatch(
`div{${properties.map((property) => `${property}:inherit;`).join('')}}`
);
});
const fallbacksWithDirectionTo = (
ltrProperty: string,
rtlProperty: string,
...blockProperties: string[]
) => (property: string) => {
const properties = [ltrProperty, rtlProperty, ...blockProperties];
const expected =
blockProperties.length > 0
? `html:not([dir=rtl]) div{${ltrProperty}:inherit;${rtlProperty}:inherit;}` +
`[dir=rtl] div{${rtlProperty}:inherit;${ltrProperty}:inherit;}` +
`div{${blockProperties
.map((property) => `${property}:inherit;`)
.join('')}}`
: `html:not([dir=rtl]) div{${ltrProperty}:inherit;}[dir=rtl] div{${rtlProperty}:inherit;}`;
it(`fallbacks ${property} to ${properties.join(', ')}`, () => {
expect(
transpile(
`div`,
`${property}: inherit;`,
createTranspileMiddleware({
isPropertySupported: (p) => properties.includes(p),
})
)
).toMatch(expected);
});
};
const supportsLogicalValues = () => (property: string) =>
it.each([['start'], ['inline-start'], ['end'], ['inline-end']])(
'supports %s value',
(logicalValue: string) => {
expect(
transpile(
'div',
`${property}: ${logicalValue};`,
createTranspileMiddleware({
isPropertyValueSupported: (p, v) =>
p === property && v === logicalValue,
})
)
).toMatch(`div{${property}:${logicalValue};}`);
}
);
const fallbacksLogicalValues = () => (property: string) =>
it.each([
['start', 'left', 'right'],
['inline-start', 'left', 'right'],
['end', 'right', 'left'],
['inline-end', 'right', 'left'],
])(
'fallbacks %s value to %s and %s',
(logicalValue: string, ltrValue: string, rtlValue: string) => {
expect(transpile('div', `${property}: ${logicalValue};`)).toMatch(
`[dir=rtl] div{${property}:${rtlValue};}div{${property}:${ltrValue};}`
);
}
);
property(
'border-start-start-radius',
isSupported(),
fallbacksWithDirectionTo('border-top-left-radius', 'border-top-right-radius')
);
property(
'border-start-end-radius',
isSupported(),
fallbacksWithDirectionTo('border-top-right-radius', 'border-top-left-radius')
);
property(
'border-end-start-radius',
isSupported(),
fallbacksWithDirectionTo(
'border-bottom-left-radius',
'border-bottom-right-radius'
)
);
property(
'border-end-end-radius',
isSupported(),
fallbacksWithDirectionTo(
'border-bottom-right-radius',
'border-bottom-left-radius'
)
);
property(
'inset-inline-start',
isSupported(),
fallbacksWithDirectionTo('left', 'right')
);
property(
'inset-inline-end',
isSupported(),
fallbacksWithDirectionTo('right', 'left')
);
property(
'border-inline-start',
isSupported(),
fallbacksWithDirectionTo('border-left', 'border-right')
);
property(
'border-inline-end',
isSupported(),
fallbacksWithDirectionTo('border-right', 'border-left')
);
property(
'border-inline-start-width',
isSupported(),
fallbacksWithDirectionTo('border-left-width', 'border-right-width')
);
property(
'border-inline-end-width',
isSupported(),
fallbacksWithDirectionTo('border-right-width', 'border-left-width')
);
property(
'border-inline-start-style',
isSupported(),
fallbacksWithDirectionTo('border-left-style', 'border-right-style')
);
property(
'border-inline-end-style',
isSupported(),
fallbacksWithDirectionTo('border-right-style', 'border-left-style')
);
property(
'border-inline-start-color',
isSupported(),
fallbacksWithDirectionTo('border-left-color', 'border-right-color')
);
property(
'border-inline-end-color',
isSupported(),
fallbacksWithDirectionTo('border-right-color', 'border-left-color')
);
property(
'margin-inline-start',
isSupported('-webkit-margin-start'),
fallbacksWithDirectionTo('margin-left', 'margin-right')
);
property(
'margin-inline-end',
isSupported('-webkit-margin-end'),
fallbacksWithDirectionTo('margin-right', 'margin-left')
);
property(
'padding-inline-start',
isSupported('-webkit-padding-start'),
fallbacksWithDirectionTo('padding-left', 'padding-right')
);
property(
'padding-inline-end',
isSupported('-webkit-padding-end'),
fallbacksWithDirectionTo('padding-right', 'padding-left')
);
property(
'border-inline',
isSupported(),
fallbacksTo('border-inline-start', 'border-inline-end'),
fallbacksTo('border-left', 'border-right')
);
property(
'border-inline-width',
isSupported(),
fallbacksTo('border-inline-start-width', 'border-inline-end-width'),
fallbacksTo('border-left-width', 'border-right-width')
);
property(
'border-inline-style',
isSupported(),
fallbacksTo('border-inline-start-style', 'border-inline-end-style'),
fallbacksTo('border-left-style', 'border-right-style')
);
property(
'border-inline-color',
isSupported(),
fallbacksTo('border-inline-start-color', 'border-inline-end-color'),
fallbacksTo('border-left-color', 'border-right-color')
);
property(
'inset-inline',
isSupported(),
fallbacksTo('inset-inline-start', 'inset-inline-end'),
fallbacksTo('left', 'right')
);
property(
'margin-inline',
isSupported(),
fallbacksTo(
'-webkit-margin-start',
'margin-inline-start',
'-webkit-margin-end',
'margin-inline-end'
),
fallbacksTo('margin-left', 'margin-right')
);
property(
'padding-inline',
isSupported(),
fallbacksTo(
'-webkit-padding-start',
'padding-inline-start',
'-webkit-padding-end',
'padding-inline-end'
),
fallbacksTo('padding-left', 'padding-right')
);
property('border-block-start', isSupported(), fallbacksTo('border-top'));
property('border-block-end', isSupported(), fallbacksTo('border-bottom'));
property(
'border-block-start-width',
isSupported(),
fallbacksTo('border-top-width')
);
property(
'border-block-end-width',
isSupported(),
fallbacksTo('border-bottom-width')
);
property(
'border-block-start-style',
isSupported(),
fallbacksTo('border-top-style')
);
property(
'border-block-end-style',
isSupported(),
fallbacksTo('border-bottom-style')
);
property(
'border-block-start-color',
isSupported(),
fallbacksTo('border-top-color')
);
property(
'border-block-end-color',
isSupported(),
fallbacksTo('border-bottom-color')
);
property('inset-block-start', isSupported(), fallbacksTo('top'));
property('inset-block-end', isSupported(), fallbacksTo('bottom'));
property('margin-block-start', isSupported(), fallbacksTo('margin-top'));
property('margin-block-end', isSupported(), fallbacksTo('margin-bottom'));
property('padding-block-start', isSupported(), fallbacksTo('padding-top'));
property('padding-block-end', isSupported(), fallbacksTo('padding-bottom'));
property(
'border-block',
isSupported(),
fallbacksTo('border-block-start', 'border-block-end'),
fallbacksTo('border-top', 'border-bottom')
);
property(
'border-block-width',
isSupported(),
fallbacksTo('border-block-start-width', 'border-block-end-width'),
fallbacksTo('border-top-width', 'border-bottom-width')
);
property(
'border-block-style',
isSupported(),
fallbacksTo('border-block-start-style', 'border-block-end-style'),
fallbacksTo('border-top-style', 'border-bottom-style')
);
property(
'border-block-color',
isSupported(),
fallbacksTo('border-block-start-color', 'border-block-end-color'),
fallbacksTo('border-top-color', 'border-bottom-color')
);
property(
'inset-block',
isSupported(),
fallbacksTo('inset-block-start', 'inset-block-end'),
fallbacksTo('top', 'bottom')
);
property(
'margin-block',
isSupported(),
fallbacksTo('margin-block-start', 'margin-block-end'),
fallbacksTo('margin-top', 'margin-bottom')
);
property(
'padding-block',
isSupported(),
fallbacksTo('padding-block-start', 'padding-block-end'),
fallbacksTo('padding-top', 'padding-bottom')
);
property(
'inset',
isSupported(),
fallbacksTo('inset-inline', 'inset-block'),
fallbacksTo(
'inset-inline-start',
'inset-inline-end',
'inset-block-start',
'inset-block-end'
),
fallbacksWithDirectionTo('left', 'right', 'top', 'bottom')
);
property('inline-size', isSupported(), fallbacksTo('width'));
property('min-inline-size', isSupported(), fallbacksTo('min-width'));
property('max-inline-size', isSupported(), fallbacksTo('max-width'));
property('block-size', isSupported(), fallbacksTo('height'));
property('min-block-size', isSupported(), fallbacksTo('min-height'));
property('max-block-size', isSupported(), fallbacksTo('max-height'));
property('float', supportsLogicalValues(), fallbacksLogicalValues());
property('clear', supportsLogicalValues(), fallbacksLogicalValues());
property('text-align', supportsLogicalValues(), fallbacksLogicalValues());
import {
DECLARATION,
Element,
Middleware,
node,
RULESET,
serialize,
} from 'stylis';
import { cssSupports } from './cssSupports';
type RuleSet = Element & {
type: typeof RULESET;
children: Element[];
props: string[];
};
type Declaration = Element & {
type: typeof DECLARATION;
props: string;
children: string;
};
type Operation = (
value: Declaration['children'],
ruleSet: Readonly<RuleSet>,
ltrRuleSet: Readonly<RuleSet>,
rtlRuleSet: Readonly<RuleSet>
) => void;
const isRuleSet = (element: Element): element is RuleSet =>
element.type === RULESET;
const isDeclaration = (element: Element): element is Declaration =>
element.type === DECLARATION;
const attachDeclaration = (
property: string,
value: string,
ruleSet: RuleSet
): void => {
const declaration = node(
`${property}:${value};`,
ruleSet,
ruleSet,
DECLARATION,
(property as unknown) as string[],
(value as unknown) as Element[],
property.length
);
ruleSet.children.push(declaration);
};
const compileOperations = ({
isPropertySupported,
isPropertyValueSupported,
}: {
isPropertySupported: (property: string) => boolean;
isPropertyValueSupported: (property: string, value: string) => boolean;
}): Map<string, Operation> => {
const ops = new Map<string, Operation>();
const withLogicalValues = (property: string): void => {
const logicalValues = new Map<string, boolean>(
['start', 'inline-start', 'end', 'inline-end'].map((logicalValue) => [
logicalValue,
isPropertyValueSupported(property, logicalValue),
])