postcss-logical-properties-polyfill
Version:
PostCSS plugin that polyfill W3C's CSS proposal to support logical properties and values
369 lines (368 loc) • 15.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformToNonLogical = exports.isSupportedProp = void 0;
const postcss_1 = __importStar(require("postcss"));
function getDirectionParams(decl, options) {
if (decl.prop.includes('block')) {
return ['block', options.blockStart, options.blockEnd];
}
else {
return ['inline', options.inlineStart, options.inlineEnd];
}
}
const replaceStartEndValue = (decl, { valueStart, valueEnd }) => {
const value = decl.value.toLowerCase();
if (value === 'start' || value === 'inline-start') {
return decl.clone({ value: valueStart });
}
if (value === 'end' || value === 'inline-end') {
return decl.clone({ value: valueEnd });
}
};
const replaceResize = (decl, { resizeBlock, resizeInline }) => {
const value = decl.value.toLowerCase();
if (value === 'block') {
return decl.clone({ value: resizeBlock });
}
if (value === 'inline') {
return decl.clone({ value: resizeInline });
}
};
const replaceDimension = (decl, { blockSize, inlineSize }) => {
if (decl.prop.includes('block')) {
return decl.clone({ prop: decl.prop.replace('block-size', blockSize) });
}
else {
return decl.clone({ prop: decl.prop.replace('inline-size', inlineSize) });
}
};
const replaceDirectionalBoxShorthand = (decl, options) => {
var _a;
const [direction, start, end] = getDirectionParams(decl, options);
const parts = postcss_1.list.space(decl.value);
return [
decl.clone({ prop: decl.prop.replace(direction, start), value: parts[0] }),
decl.clone({ prop: decl.prop.replace(direction, end), value: (_a = parts[1]) !== null && _a !== void 0 ? _a : parts[0] }),
];
};
const replaceBox = (decl, options) => {
const [direction, start, end] = getDirectionParams(decl, options);
if (decl.prop.toLowerCase().includes('start')) {
return decl.clone({ prop: decl.prop.replace(`${direction}-start`, start) });
}
else {
return decl.clone({ prop: decl.prop.replace(`${direction}-end`, end) });
}
};
const replaceSimpleRL = (decl, options) => {
const [direction, start, end] = getDirectionParams(decl, options);
return [
decl.clone({ prop: decl.prop.replace(direction, start) }),
decl.clone({ prop: decl.prop.replace(direction, end) }),
];
};
const replaceInlinePositioning = (decl, { inlineStart, inlineEnd }) => {
if (decl.prop.toLowerCase().includes('start')) {
return decl.clone({ prop: inlineStart });
}
else {
return decl.clone({ prop: inlineEnd });
}
};
const replaceBoxShorthand = (decl, options) => {
var _a, _b, _c, _d;
const parts = postcss_1.list.space(decl.value);
if (parts[0] !== 'logical') {
return;
}
return [
decl.clone({ prop: `${decl.prop}-${options.blockStart}`, value: parts[1] }),
decl.clone({ prop: `${decl.prop}-${options.inlineEnd}`, value: (_a = parts[2]) !== null && _a !== void 0 ? _a : parts[1] }),
decl.clone({ prop: `${decl.prop}-${options.blockEnd}`, value: (_b = parts[3]) !== null && _b !== void 0 ? _b : parts[1] }),
decl.clone({ prop: `${decl.prop}-${options.inlineStart}`, value: (_d = (_c = parts[4]) !== null && _c !== void 0 ? _c : parts[2]) !== null && _d !== void 0 ? _d : parts[1] }),
];
};
const replaceInsetShorthand = (decl, options) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const parts = postcss_1.list.space(decl.value);
if (parts[0] === 'logical') {
return [
decl.clone({ prop: options.blockStart, value: parts[1] }),
decl.clone({ prop: options.inlineStart, value: (_a = parts[2]) !== null && _a !== void 0 ? _a : parts[1] }),
decl.clone({ prop: options.blockEnd, value: (_b = parts[3]) !== null && _b !== void 0 ? _b : parts[1] }),
decl.clone({ prop: options.inlineEnd, value: (_d = (_c = parts[4]) !== null && _c !== void 0 ? _c : parts[2]) !== null && _d !== void 0 ? _d : parts[1] }),
];
}
return [
decl.clone({ prop: 'top', value: parts[0] }),
decl.clone({ prop: 'left', value: (_e = parts[1]) !== null && _e !== void 0 ? _e : parts[0] }),
decl.clone({ prop: 'bottom', value: (_f = parts[2]) !== null && _f !== void 0 ? _f : parts[0] }),
decl.clone({ prop: 'right', value: (_h = (_g = parts[3]) !== null && _g !== void 0 ? _g : parts[1]) !== null && _h !== void 0 ? _h : parts[0] }),
];
};
const replaceBorderShorthand = (decl, options) => {
var _a, _b, _c, _d;
const parts = postcss_1.list.space(decl.value);
if (parts[0] !== 'logical') {
return;
}
const nameParts = decl.prop.split('-');
return [
decl.clone({ prop: `${nameParts[0]}-${options.blockStart}-${nameParts[1]}`, value: parts[1] }),
decl.clone({ prop: `${nameParts[0]}-${options.inlineStart}-${nameParts[1]}`, value: (_a = parts[2]) !== null && _a !== void 0 ? _a : parts[1] }),
decl.clone({ prop: `${nameParts[0]}-${options.blockEnd}-${nameParts[1]}`, value: (_b = parts[3]) !== null && _b !== void 0 ? _b : parts[1] }),
decl.clone({
prop: `${nameParts[0]}-${options.inlineEnd}-${nameParts[1]}`,
value: (_d = (_c = parts[4]) !== null && _c !== void 0 ? _c : parts[2]) !== null && _d !== void 0 ? _d : parts[1],
}),
];
};
const replacePositioningShorthand = (decl, options) => {
var _a;
const [, start, end] = getDirectionParams(decl, options);
const parts = postcss_1.list.space(decl.value);
return [decl.clone({ prop: start, value: parts[0] }), decl.clone({ prop: end, value: (_a = parts[1]) !== null && _a !== void 0 ? _a : parts[0] })];
};
const replaceBorderRadius = (decl, options) => {
return decl.clone({
prop: decl.prop
.replace('start-start', options.borderStartStart)
.replace('start-end', options.borderStartEnd)
.replace('end-start', options.borderEndStart)
.replace('end-end', options.borderEndEnd),
});
};
const replaceTransition = (decl, options) => {
const rawItems = postcss_1.list.comma(decl.value);
// There might be "transition-property" or shorthand "transition"
// In any case, property name is always comes first, so each element at [0] will contain a property name
const parsedItems = rawItems.map(postcss_1.list.space);
const indexesToModify = parsedItems.reduce((indexes, [prop], i) => {
if (isSupportedProp(prop)) {
indexes.push(i);
}
return indexes;
}, []);
if (indexesToModify.length === 0) {
return;
}
let hasChangedProps = false;
let countOfNewProps = 0;
indexesToModify.forEach((i) => {
const [prop, ...restParams] = parsedItems[i + countOfNewProps];
const tempDecl = postcss_1.default.decl({
prop,
value: 'initial',
});
const newDecls = shouldFindTransformer(prop)(tempDecl, options);
if (!newDecls) {
return;
}
(Array.isArray(newDecls) ? newDecls : [newDecls]).forEach((newDecl, declarationIndex) => {
hasChangedProps = true;
if (declarationIndex === 0) {
parsedItems[i + countOfNewProps][0] = newDecl.prop;
}
else {
parsedItems.splice(i + countOfNewProps, 0, [newDecl.prop, ...restParams]);
countOfNewProps++;
}
});
});
if (!hasChangedProps) {
return;
}
return decl.clone({
value: parsedItems.map((item) => item.join(' ')).join(', '),
});
};
const transformationMap = {
// https://www.w3.org/TR/css-logical-1/#float-clear
float: replaceStartEndValue,
clear: replaceStartEndValue,
// https://www.w3.org/TR/css-logical-1/#text-align
'text-align': replaceStartEndValue,
// https://www.w3.org/TR/css-logical-1/#resize
resize: replaceResize,
// https://www.w3.org/TR/css-logical-1/#dimension-properties
'block-size': replaceDimension,
'inline-size': replaceDimension,
'min-block-size': replaceDimension,
'min-inline-size': replaceDimension,
'max-block-size': replaceDimension,
'max-inline-size': replaceDimension,
// https://www.w3.org/TR/css-logical-1/#margin-properties
margin: replaceBoxShorthand,
'margin-block': replaceDirectionalBoxShorthand,
'margin-block-start': replaceBox,
'margin-block-end': replaceBox,
'margin-inline': replaceDirectionalBoxShorthand,
'margin-inline-start': replaceBox,
'margin-inline-end': replaceBox,
// https://www.w3.org/TR/css-logical-1/#inset-properties
inset: replaceInsetShorthand,
'inset-block': replacePositioningShorthand,
'inset-block-start': replaceInlinePositioning,
'inset-block-end': replaceInlinePositioning,
'inset-inline': replacePositioningShorthand,
'inset-inline-start': replaceInlinePositioning,
'inset-inline-end': replaceInlinePositioning,
// https://www.w3.org/TR/css-logical-1/#padding-properties
padding: replaceBoxShorthand,
'padding-inline': replaceDirectionalBoxShorthand,
'padding-inline-start': replaceBox,
'padding-inline-end': replaceBox,
'padding-block': replaceDirectionalBoxShorthand,
'padding-block-start': replaceBox,
'padding-block-end': replaceBox,
// https://www.w3.org/TR/css-logical-1/#border-width
'border-width': replaceBorderShorthand,
'border-block-width': replaceDirectionalBoxShorthand,
'border-block-start-width': replaceBox,
'border-block-end-width': replaceBox,
'border-inline-width': replaceDirectionalBoxShorthand,
'border-inline-start-width': replaceBox,
'border-inline-end-width': replaceBox,
// https://www.w3.org/TR/css-logical-1/#border-style
'border-style': replaceBorderShorthand,
'border-block-style': replaceDirectionalBoxShorthand,
'border-block-start-style': replaceBox,
'border-block-end-style': replaceBox,
'border-inline-style': replaceDirectionalBoxShorthand,
'border-inline-start-style': replaceBox,
'border-inline-end-style': replaceBox,
// https://www.w3.org/TR/css-logical-1/#border-color
'border-color': replaceBorderShorthand,
'border-block-color': replaceDirectionalBoxShorthand,
'border-block-start-color': replaceBox,
'border-block-end-color': replaceBox,
'border-inline-color': replaceDirectionalBoxShorthand,
'border-inline-start-color': replaceBox,
'border-inline-end-color': replaceBox,
// https://www.w3.org/TR/css-logical-1/#border-shorthands
'border-block': replaceSimpleRL,
'border-block-start': replaceBox,
'border-block-end': replaceBox,
'border-inline': replaceSimpleRL,
'border-inline-start': replaceBox,
'border-inline-end': replaceBox,
// https://www.w3.org/TR/css-logical-1/#border-radius-shorthands
'border-start-start-radius': replaceBorderRadius,
'border-start-end-radius': replaceBorderRadius,
'border-end-start-radius': replaceBorderRadius,
'border-end-end-radius': replaceBorderRadius,
transition: replaceTransition,
'transition-property': replaceTransition,
};
function shouldFindTransformer(prop) {
const transformer = transformationMap[prop.toLowerCase()];
if (!transformer) {
throw new Error(`Unknown declaration property received: "${prop}"`);
}
return transformer;
}
function shouldGetTransformerOptions(writingMode, direction) {
let options;
switch (writingMode) {
case 'horizontal-tb':
options = {
blockSize: 'height',
inlineSize: 'width',
inlineStart: 'left',
inlineEnd: 'right',
blockStart: 'top',
blockEnd: 'bottom',
borderStartStart: 'top-left',
borderStartEnd: 'top-right',
borderEndStart: 'bottom-left',
borderEndEnd: 'bottom-right',
};
break;
case 'vertical-rl':
case 'sideways-rl':
options = {
blockSize: 'width',
inlineSize: 'height',
inlineStart: 'top',
inlineEnd: 'bottom',
blockStart: 'right',
blockEnd: 'left',
borderStartStart: 'top-right',
borderStartEnd: 'bottom-right',
borderEndStart: 'top-left',
borderEndEnd: 'bottom-left',
};
break;
case 'vertical-lr':
options = {
blockSize: 'width',
inlineSize: 'height',
inlineStart: 'top',
inlineEnd: 'bottom',
blockStart: 'left',
blockEnd: 'right',
borderStartStart: 'top-left',
borderStartEnd: 'bottom-left',
borderEndStart: 'top-right',
borderEndEnd: 'bottom-right',
};
break;
case 'sideways-lr':
options = {
blockSize: 'width',
inlineSize: 'height',
inlineStart: 'bottom',
inlineEnd: 'top',
blockStart: 'left',
blockEnd: 'right',
borderStartStart: 'bottom-left',
borderStartEnd: 'top-left',
borderEndStart: 'bottom-right',
borderEndEnd: 'top-right',
};
break;
default:
throw new Error(`Unknown writing-mode received: "${writingMode}"`);
}
if (direction === 'rtl') {
[options.inlineStart, options.inlineEnd] = [options.inlineEnd, options.inlineStart];
[options.borderStartStart, options.borderStartEnd] = [options.borderStartEnd, options.borderStartStart];
[options.borderEndStart, options.borderEndEnd] = [options.borderEndEnd, options.borderEndStart];
}
return {
valueStart: direction === 'ltr' ? 'left' : 'right',
valueEnd: direction === 'ltr' ? 'right' : 'left',
resizeBlock: writingMode === 'horizontal-tb' ? 'vertical' : 'horizontal',
resizeInline: writingMode === 'horizontal-tb' ? 'horizontal' : 'vertical',
...options,
};
}
function isSupportedProp(prop) {
return prop.toLowerCase() in transformationMap;
}
exports.isSupportedProp = isSupportedProp;
function transformToNonLogical(decl, writingMode, direction) {
const transformer = shouldFindTransformer(decl.prop);
const options = shouldGetTransformerOptions(writingMode, direction);
return transformer(decl, options);
}
exports.transformToNonLogical = transformToNonLogical;