diff --git a/.all-contributorsrc b/.all-contributorsrc index 29b198d..4bc4477 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -48,6 +48,16 @@ "code", "test" ] + }, + { + "login": "TxHawks", + "name": "Jonathan Pollak", + "avatar_url": "https://avatars2.githubusercontent.com/u/5658514?v=4", + "profile": "https://github.com/TxHawks", + "contributions": [ + "code", + "test" + ] } ] } diff --git a/README.md b/README.md index 9b5224d..77bcc6d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ RTL conversion for CSS in JS objects [![downloads][downloads-badge]][npm-stat] [![MIT License][license-badge]][LICENSE] -[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Donate][donate-badge]][donate] [![Code of Conduct][coc-badge]][coc] @@ -59,7 +59,7 @@ console.log(styles) // logs {paddingRight: 23} You can also just include a script tag in your browser and use the `rtlCSSJS` variable: ```html - + const styles = rtlCSSJS({paddingRight: 23}) console.log(styles) // logs {paddingLeft: 23} @@ -74,6 +74,17 @@ const styles = rtlCSSJS({paddingLeft: '20px /* @noflip */'}) console.log(styles) // logs {paddingLeft: '20px /* @noflip */' } ``` +### core + +`rtl-css-js` also exposes its internal helpers and utilities so you can deal +with [certain scenarios](https://github.com/kentcdodds/rtl-css-js/pull/22) +yourself. To use these you can use the `rtlCSSJSCore` global with the UMD build, +`require('rtl-css-js/core')`, or use +`import {propertyValueConverters, arrayToObject} from 'rtl-css-js/core.esm'`. + +You can import anything that's exported from `src/core`. Please see the code +comments for documentation on how to use these. + ## Caveats ### `background` @@ -96,8 +107,8 @@ I'm not aware of any, if you are please [make a pull request](http://makeapullre Thanks goes to these people ([emoji key][emojis]): -| [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=kentcdodds "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=kentcdodds "Tests") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") | [
Ahmed El Gabri](https://gabri.me)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Code") [📖](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Documentation") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Tests") | [
Maja Wichrowska](https://github.com/majapw)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=majapw "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=majapw "Tests") | [
Yaniv](https://github.com/yzimet)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=yzimet "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=yzimet "Tests") | -| :---: | :---: | :---: | :---: | +| [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=kentcdodds "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=kentcdodds "Tests") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") | [
Ahmed El Gabri](https://gabri.me)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Code") [📖](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Documentation") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=ahmedelgabri "Tests") | [
Maja Wichrowska](https://github.com/majapw)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=majapw "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=majapw "Tests") | [
Yaniv](https://github.com/yzimet)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=yzimet "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=yzimet "Tests") | [
Jonathan Pollak](https://github.com/TxHawks)
[💻](https://github.com/kentcdodds/rtl-css-js/commits?author=TxHawks "Code") [⚠️](https://github.com/kentcdodds/rtl-css-js/commits?author=TxHawks "Tests") | +| :---: | :---: | :---: | :---: | :---: | This project follows the [all-contributors][all-contributors] specification. Contributions of any kind welcome! diff --git a/core.esm.js b/core.esm.js new file mode 100644 index 0000000..38f1b44 --- /dev/null +++ b/core.esm.js @@ -0,0 +1,3 @@ +/* eslint import/no-unresolved:0 */ +// this file just makes it easier to import dist/core +export * from './dist/rtl-css-js.core.esm' diff --git a/core.js b/core.js new file mode 100644 index 0000000..fce8fd9 --- /dev/null +++ b/core.js @@ -0,0 +1,3 @@ +/* eslint import/no-unresolved:0 */ +// this file just makes it easier to require dist/core +module.exports = require('./dist/rtl-css-js.core.cjs') diff --git a/package.json b/package.json index 757b9fe..959e579 100644 --- a/package.json +++ b/package.json @@ -7,19 +7,25 @@ "module": "dist/rtl-css-js.es.js", "scripts": { "add-contributor": "kcd-scripts contributors add", - "build": "kcd-scripts build --browser --environment BUILD_NAME:rtlCSSJS", + "build": "rimraf dist && npm-run-all build:*", + "build:main": + "kcd-scripts build --bundle --environment BUILD_NAME:rtlCSSJS --no-clean", + "build:core": + "kcd-scripts build --bundle --environment BUILD_NAME:rtlCSSJSCore,BUILD_FILENAME_SUFFIX:\".core\",BUILD_INPUT:src/core/index.js --no-clean", "lint": "kcd-scripts lint", "test": "kcd-scripts test", "test:update": "npm run test -s -- --coverage --updateSnapshot", "validate": "kcd-scripts validate", "precommit": "kcd-scripts precommit" }, - "files": ["dist"], + "files": ["dist", "core", "core.esm"], "keywords": ["css-in-js", "ltr", "rtl", "cssjanus"], "author": "Kent C. Dodds (http://kentcdodds.com/)", "license": "MIT", "devDependencies": { - "kcd-scripts": "^0.6.0" + "kcd-scripts": "^0.16.0", + "npm-run-all": "^4.1.1", + "rimraf": "^2.6.2" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js" diff --git a/src/__tests__/index.js b/src/__tests__/index.js index 49b5247..af8bb16 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -15,7 +15,7 @@ import convert from '../' // use this object for bigger tests -// the key the test title +// the key is the test title // the objects each have an input (array that's spread on a call to convert) and an output which is the resulting object // if you want to run `.only` or `.skip` for one of the tests // specify `modifier: 'only'` or `modifier: 'skip'` 👍 @@ -189,8 +189,16 @@ const shortTests = [ {backgroundImage: 'repeating-linear-gradient(to left top, blue, red)'}, ], [ - [{backgroundImage: 'repeating-linear-gradient(to left, #00ff00 0%, #ff0000 100%)'}], - {backgroundImage: 'repeating-linear-gradient(to right, #00ff00 0%, #ff0000 100%)'}, + [ + { + backgroundImage: + 'repeating-linear-gradient(to left, #00ff00 0%, #ff0000 100%)', + }, + ], + { + backgroundImage: + 'repeating-linear-gradient(to right, #00ff00 0%, #ff0000 100%)', + }, ], [ [{background: '#000 linear-gradient(to left top, blue, red)'}], diff --git a/src/core/__tests__/property-value-converters.js b/src/core/__tests__/property-value-converters.js new file mode 100644 index 0000000..f8606c2 --- /dev/null +++ b/src/core/__tests__/property-value-converters.js @@ -0,0 +1,22 @@ +/** + * These are tests for core functionality that isn't used by the canonical + * implementation and therefore cannot be tested in the main test file. + */ + +import {propertyValueConverters} from '../' + +describe('Extended core functionality', () => { + describe('propertyValueConverters', () => { + it('should not calculate new background position when "isRtl" is "false"', () => { + const value = '77% 40%' + const newValue = propertyValueConverters.backgroundPosition({ + value, + valuesToConvet: {left: 'right', right: 'left'}, + isRtl: false, + bgPosDirectionRegex: new RegExp('(left)|(right)'), + }) + + expect(value).toEqual(newValue) + }) + }) +}) diff --git a/src/core/index.js b/src/core/index.js new file mode 100644 index 0000000..f08190f --- /dev/null +++ b/src/core/index.js @@ -0,0 +1,4 @@ +import propertyValueConverters from './property-value-converters' + +export * from './utils' +export {propertyValueConverters} diff --git a/src/core/property-value-converters.js b/src/core/property-value-converters.js new file mode 100644 index 0000000..1761915 --- /dev/null +++ b/src/core/property-value-converters.js @@ -0,0 +1,162 @@ +import { + includes, + isNumber, + calculateNewBackgroundPosition, + calculateNewTranslate, + handleQuartetValues, + getValuesAsList, +} from './utils' + +// some values require a little fudging, that fudging goes here. +const propertyValueConverters = { + padding({value}) { + if (isNumber(value)) { + return value + } + return handleQuartetValues(value) + }, + textShadow({value}) { + // intentionally leaving off the `g` flag here because we only want to change the first number (which is the offset-x) + return value.replace(/(-*)([.|\d]+)/, (match, negative, number) => { + if (number === '0') { + return match + } + const doubleNegative = negative === '' ? '-' : '' + return `${doubleNegative}${number}` + }) + }, + borderColor({value}) { + return handleQuartetValues(value) + }, + borderRadius({value}) { + if (isNumber(value)) { + return value + } + if (includes(value, '/')) { + const [radius1, radius2] = value.split('/') + const convertedRadius1 = propertyValueConverters.borderRadius({ + value: radius1.trim(), + }) + const convertedRadius2 = propertyValueConverters.borderRadius({ + value: radius2.trim(), + }) + return `${convertedRadius1} / ${convertedRadius2}` + } + const splitValues = getValuesAsList(value) + switch (splitValues.length) { + case 2: { + return splitValues.reverse().join(' ') + } + case 4: { + const [topLeft, topRight, bottomRight, bottomLeft] = splitValues + return [topRight, topLeft, bottomLeft, bottomRight].join(' ') + } + default: { + return value + } + } + }, + background({ + value, + valuesToConvert, + isRtl, + bgImgDirectionRegex, + bgPosDirectionRegex, + }) { + // Yeah, this is in need of a refactor 🙃... + // but this property is a tough cookie 🍪 + // get the backgroundPosition out of the string by removing everything that couldn't be the backgroundPosition value + const backgroundPositionValue = value + .replace( + /(url\(.*?\))|(rgba?\(.*?\))|(hsl\(.*?\))|(#[a-fA-F0-9]+)|((^| )(\D)+( |$))/g, + '', + ) + .trim() + // replace that backgroundPosition value with the converted version + value = value.replace( + backgroundPositionValue, + propertyValueConverters.backgroundPosition({ + value: backgroundPositionValue, + valuesToConvert, + isRtl, + bgPosDirectionRegex, + }), + ) + // do the backgroundImage value replacing on the whole value (because why not?) + return propertyValueConverters.backgroundImage({ + value, + valuesToConvert, + bgImgDirectionRegex, + }) + }, + backgroundImage({value, valuesToConvert, bgImgDirectionRegex}) { + if (!includes(value, 'url(') && !includes(value, 'linear-gradient(')) { + return value + } + return value.replace(bgImgDirectionRegex, (match, g1, group2) => { + return match.replace(group2, valuesToConvert[group2]) + }) + }, + backgroundPosition({value, valuesToConvert, isRtl, bgPosDirectionRegex}) { + return ( + value + // intentionally only grabbing the first instance of this because that represents `left` + .replace(isRtl ? /^((-|\d|\.)+%)/ : null, (match, group) => + calculateNewBackgroundPosition(group), + ) + .replace(bgPosDirectionRegex, match => valuesToConvert[match]) + ) + }, + backgroundPositionX({value, valuesToConvert, isRtl, bgPosDirectionRegex}) { + if (isNumber(value)) { + return value + } + return propertyValueConverters.backgroundPosition({ + value, + valuesToConvert, + isRtl, + bgPosDirectionRegex, + }) + }, + transform({value}) { + // This was copied and modified from CSSJanus: + // https://github.com/cssjanus/cssjanus/blob/4a40f001b1ba35567112d8b8e1d9d95eda4234c3/src/cssjanus.js#L152-L153 + const nonAsciiPattern = '[^\\u0020-\\u007e]' + const unicodePattern = '(?:(?:\\[0-9a-f]{1,6})(?:\\r\\n|\\s)?)' + const numPattern = '(?:[0-9]*\\.[0-9]+|[0-9]+)' + const unitPattern = '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)' + const escapePattern = `(?:${unicodePattern}|\\\\[^\\r\\n\\f0-9a-f])` + const nmstartPattern = `(?:[_a-z]|${nonAsciiPattern}|${escapePattern})` + const nmcharPattern = `(?:[_a-z0-9-]|${nonAsciiPattern}|${escapePattern})` + const identPattern = `-?${nmstartPattern}${nmcharPattern}*` + const quantPattern = `${numPattern}(?:\\s*${unitPattern}|${identPattern})?` + const signedQuantPattern = `((?:-?${quantPattern})|(?:inherit|auto))` + const translateXRegExp = new RegExp( + `(translateX\\s*\\(\\s*)${signedQuantPattern}(\\s*\\))`, + 'gi', + ) + const translateRegExp = new RegExp( + `(translate\\s*\\(\\s*)${signedQuantPattern}((?:\\s*,\\s*${signedQuantPattern}){0,1}\\s*\\))`, + 'gi', + ) + const translate3dRegExp = new RegExp( + `(translate3d\\s*\\(\\s*)${signedQuantPattern}((?:\\s*,\\s*${signedQuantPattern}){0,2}\\s*\\))`, + 'gi', + ) + return value + .replace(translateXRegExp, calculateNewTranslate) + .replace(translateRegExp, calculateNewTranslate) + .replace(translate3dRegExp, calculateNewTranslate) + }, +} + +propertyValueConverters.margin = propertyValueConverters.padding +propertyValueConverters.borderWidth = propertyValueConverters.padding +propertyValueConverters.boxShadow = propertyValueConverters.textShadow +propertyValueConverters.webkitBoxShadow = propertyValueConverters.textShadow +propertyValueConverters.mozBoxShadow = propertyValueConverters.textShadow +propertyValueConverters.borderStyle = propertyValueConverters.borderColor +propertyValueConverters.webkitTransform = propertyValueConverters.transform +propertyValueConverters.mozTransform = propertyValueConverters.transform + +export default propertyValueConverters diff --git a/src/core/utils.js b/src/core/utils.js new file mode 100644 index 0000000..10739cb --- /dev/null +++ b/src/core/utils.js @@ -0,0 +1,138 @@ +/** + * Takes an array of [keyValue1, keyValue2] pairs and creates an object of {keyValue1: keyValue2, keyValue2: keyValue1} + * @param {Array} array the array of pairs + * @return {Object} the {key, value} pair object + */ +function arrayToObject(array) { + return array.reduce((obj, [prop1, prop2]) => { + obj[prop1] = prop2 + obj[prop2] = prop1 + return obj + }, {}) +} + +function isNumber(val) { + return typeof val === 'number' +} + +function isNullOrUndefined(val) { + return val === null || typeof val === 'undefined' +} + +function isObject(val) { + return val && typeof val === 'object' +} + +function isString(val) { + return typeof val === 'string' +} + +function includes(inclusive, inclusee) { + return inclusive.indexOf(inclusee) !== -1 +} + +/** + * Flip the sign of a CSS value, possibly with a unit. + * + * We can't just negate the value with unary minus due to the units. + * + * @private + * @param {String} value - the original value (for example 77%) + * @return {String} the result (for example -77%) + */ +function flipSign(value) { + if (parseFloat(value) === 0) { + // Don't mangle zeroes + return value + } + + if (value[0] === '-') { + return value.slice(1) + } + + return `-${value}` +} + +function calculateNewTranslate(match, prefix, offset, suffix) { + return prefix + flipSign(offset) + suffix +} + +/** + * Takes a percentage for background position and inverts it. + * This was copied and modified from CSSJanus: + * https://github.com/cssjanus/cssjanus/blob/4245f834365f6cfb0239191a151432fb85abab23/src/cssjanus.js#L152-L175 + * @param {String} value - the original value (for example 77%) + * @return {String} the result (for example 23%) + */ +function calculateNewBackgroundPosition(value) { + const idx = value.indexOf('.') + if (idx === -1) { + value = `${100 - parseFloat(value)}%` + } else { + // Two off, one for the "%" at the end, one for the dot itself + const len = value.length - idx - 2 + value = 100 - parseFloat(value) + value = `${value.toFixed(len)}%` + } + return value +} + +/** + * This takes a list of CSS values and converts it to an array + * @param {String} value - something like `1px`, `1px 2em`, or `3pt rgb(150, 230, 550) 40px calc(100% - 5px)` + * @return {Array} the split values (for example: `['3pt', 'rgb(150, 230, 550)', '40px', 'calc(100% - 5px)']`) + */ +function getValuesAsList(value) { + return ( + value + .replace(/ +/g, ' ') // remove all extraneous spaces + .split(' ') + .map(i => i.trim()) // get rid of extra space before/after each item + .filter(Boolean) // get rid of empty strings + // join items which are within parenthese + // luckily `calc (100% - 5px)` is invalid syntax and it must be `calc(100% - 5px)`, otherwise this would be even more complex + .reduce( + ({list, state}, item) => { + const openParansCount = (item.match(/\(/g) || []).length + const closedParansCount = (item.match(/\)/g) || []).length + if (state.parensDepth > 0) { + list[list.length - 1] = `${list[list.length - 1]} ${item}` + } else { + list.push(item) + } + state.parensDepth += openParansCount - closedParansCount + return {list, state} + }, + {list: [], state: {parensDepth: 0}}, + ).list + ) +} + +/** + * This is intended for properties that are `top right bottom left` and will switch them to `top left bottom right` + * @param {String} value - `1px 2px 3px 4px` for example, but also handles cases where there are too few/too many and + * simply returns the value in those cases (which is the correct behavior) + * @return {String} the result - `1px 4px 3px 2px` for example. + */ +function handleQuartetValues(value) { + const splitValues = getValuesAsList(value) + if (splitValues.length <= 3 || splitValues.length > 4) { + return value + } + const [top, right, bottom, left] = splitValues + return [top, left, bottom, right].join(' ') +} + +export { + arrayToObject, + calculateNewBackgroundPosition, + calculateNewTranslate, + flipSign, + handleQuartetValues, + includes, + isNullOrUndefined, + isNumber, + isObject, + isString, + getValuesAsList, +} diff --git a/src/index.js b/src/index.js index 21a652f..677a0c3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,12 @@ +import { + arrayToObject, + isNumber, + isObject, + isString, + isNullOrUndefined, + propertyValueConverters, +} from './core' + // this will be an object of properties that map to their corresponding rtl property (their doppelganger) const propertiesToConvert = arrayToObject([ ['paddingLeft', 'paddingRight'], @@ -20,143 +29,16 @@ const valuesToConvert = arrayToObject([ ['nw-resize', 'ne-resize'], ]) -// some values require a little fudging, that fudging goes here. -const propertyValueConverters = { - padding(value) { - if (isNumber(value)) { - return value - } - return handleQuartetValues(value) - }, - textShadow(value) { - // intentionally leaving off the `g` flag here because we only want to change the first number (which is the offset-x) - return value.replace(/(-*)([.|\d]+)/, (match, negative, number) => { - if (number === '0') { - return match - } - const doubleNegative = negative === '' ? '-' : '' - return `${doubleNegative}${number}` - }) - }, - borderColor(value) { - return handleQuartetValues(value) - }, - borderRadius(value) { - if (isNumber(value)) { - return value - } - if (includes(value, '/')) { - const [radius1, radius2] = value.split('/') - const convertedRadius1 = propertyValueConverters.borderRadius( - radius1.trim(), - ) - const convertedRadius2 = propertyValueConverters.borderRadius( - radius2.trim(), - ) - return `${convertedRadius1} / ${convertedRadius2}` - } - const splitValues = getValuesAsList(value) - switch (splitValues.length) { - case 2: { - return splitValues.reverse().join(' ') - } - case 4: { - const [topLeft, topRight, bottomRight, bottomLeft] = splitValues - return [topRight, topLeft, bottomLeft, bottomRight].join(' ') - } - default: { - return value - } - } - }, - background(value) { - // Yeah, this is in need of a refactor 🙃... - // but this property is a tough cookie 🍪 - // get the backgroundPosition out of the string by removing everything that couldn't be the backgroundPosition value - const backgroundPositionValue = value - .replace( - /(url\(.*?\))|(rgba?\(.*?\))|(hsl\(.*?\))|(#[a-fA-F0-9]+)|((^| )(\D)+( |$))/g, - '', - ) - .trim() - // replace that backgroundPosition value with the converted version - value = value.replace( - backgroundPositionValue, - propertyValueConverters.backgroundPosition(backgroundPositionValue), - ) - // do the backgroundImage value replacing on the whole value (because why not?) - return propertyValueConverters.backgroundImage(value) - }, - backgroundImage(value) { - if (!includes(value, 'url(') && !includes(value, 'linear-gradient(')) { - return value - } - // sorry for the regex 😞, but basically this replaces _every_ instance of `ltr`, `rtl`, `right`, and `left` with - // the corresponding opposite. A situation we're accepting here: - // url('/left/right/rtl/ltr.png') will be changed to url('/right/left/ltr/rtl.png') - // Definite trade-offs here, but I think it's a good call. - return value.replace( - /(^|\W|_)((ltr)|(rtl)|(left)|(right))(\W|_|$)/g, - (match, g1, group2) => { - return match.replace(group2, valuesToConvert[group2]) - }, - ) - }, - backgroundPosition(value) { - return ( - value - // intentionally only grabbing the first instance of this because that represents `left` - .replace(/^((-|\d|\.)+%)/, (match, group) => - calculateNewBackgroundPosition(group), - ) - .replace(/(left)|(right)/, match => valuesToConvert[match]) - ) - }, - backgroundPositionX(value) { - if (isNumber(value)) { - return value - } - return propertyValueConverters.backgroundPosition(value) - }, - transform(value) { - // This was copied and modified from CSSJanus: - // https://github.com/cssjanus/cssjanus/blob/4a40f001b1ba35567112d8b8e1d9d95eda4234c3/src/cssjanus.js#L152-L153 - const nonAsciiPattern = '[^\\u0020-\\u007e]' - const unicodePattern = '(?:(?:\\[0-9a-f]{1,6})(?:\\r\\n|\\s)?)' - const numPattern = '(?:[0-9]*\\.[0-9]+|[0-9]+)' - const unitPattern = '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)' - const escapePattern = `(?:${unicodePattern}|\\\\[^\\r\\n\\f0-9a-f])` - const nmstartPattern = `(?:[_a-z]|${nonAsciiPattern}|${escapePattern})` - const nmcharPattern = `(?:[_a-z0-9-]|${nonAsciiPattern}|${escapePattern})` - const identPattern = `-?${nmstartPattern}${nmcharPattern}*` - const quantPattern = `${numPattern}(?:\\s*${unitPattern}|${identPattern})?` - const signedQuantPattern = `((?:-?${quantPattern})|(?:inherit|auto))` - const translateXRegExp = new RegExp( - `(translateX\\s*\\(\\s*)${signedQuantPattern}(\\s*\\))`, - 'gi', - ) - const translateRegExp = new RegExp( - `(translate\\s*\\(\\s*)${signedQuantPattern}((?:\\s*,\\s*${signedQuantPattern}){0,1}\\s*\\))`, - 'gi', - ) - const translate3dRegExp = new RegExp( - `(translate3d\\s*\\(\\s*)${signedQuantPattern}((?:\\s*,\\s*${signedQuantPattern}){0,2}\\s*\\))`, - 'gi', - ) - return value - .replace(translateXRegExp, calculateNewTranslate) - .replace(translateRegExp, calculateNewTranslate) - .replace(translate3dRegExp, calculateNewTranslate) - }, -} -propertyValueConverters.margin = propertyValueConverters.padding -propertyValueConverters.borderWidth = propertyValueConverters.padding -propertyValueConverters.boxShadow = propertyValueConverters.textShadow -propertyValueConverters.webkitBoxShadow = propertyValueConverters.textShadow -propertyValueConverters.mozBoxShadow = propertyValueConverters.textShadow -propertyValueConverters.borderStyle = propertyValueConverters.borderColor -propertyValueConverters.webkitTransform = propertyValueConverters.transform -propertyValueConverters.mozTransform = propertyValueConverters.transform +// Sorry for the regex 😞, but basically thisis used to replace _every_ instance of +// `ltr`, `rtl`, `right`, and `left` in `backgroundimage` with the corresponding opposite. +// A situation we're accepting here: +// url('/left/right/rtl/ltr.png') will be changed to url('/right/left/ltr/rtl.png') +// Definite trade-offs here, but I think it's a good call. +const bgImgDirectionRegex = new RegExp( + '(^|\\W|_)((ltr)|(rtl)|(left)|(right))(\\W|_|$)', + 'g', +) +const bgPosDirectionRegex = new RegExp('(left)|(right)') /** * converts properties and values in the CSS in JS object to their corresponding RTL values @@ -207,8 +89,8 @@ function getPropertyDoppelganger(property) { * @return {String|Number|Object} the converted value */ function getValueDoppelganger(key, originalValue) { - /* eslint complexity:[2, 8] */ // let's try to keep the complexity down... If we have to do this much more, let's break this up - if (originalValue === null || typeof originalValue === 'undefined') { + /* eslint complexity:[2, 7] */ // let's try to keep the complexity down... If we have to do this much more, let's break this up + if (isNullOrUndefined(originalValue)) { return originalValue } @@ -224,7 +106,13 @@ function getValueDoppelganger(key, originalValue) { const valueConverter = propertyValueConverters[key] let newValue if (valueConverter) { - newValue = valueConverter(importantlessValue) + newValue = valueConverter({ + value: importantlessValue, + valuesToConvert, + isRtl: true, + bgImgDirectionRegex, + bgPosDirectionRegex, + }) } else { newValue = valuesToConvert[importantlessValue] || importantlessValue } @@ -234,126 +122,5 @@ function getValueDoppelganger(key, originalValue) { return newValue } -/** - * This takes a list of CSS values and converts it to an array - * @param {String} value - something like `1px`, `1px 2em`, or `3pt rgb(150, 230, 550) 40px calc(100% - 5px)` - * @return {Array} the split values (for example: `['3pt', 'rgb(150, 230, 550)', '40px', 'calc(100% - 5px)']`) - */ -function getValuesAsList(value) { - return ( - value - .replace(/ +/g, ' ') // remove all extraneous spaces - .split(' ') - .map(i => i.trim()) // get rid of extra space before/after each item - .filter(Boolean) // get rid of empty strings - // join items which are within parenthese - // luckily `calc (100% - 5px)` is invalid syntax and it must be `calc(100% - 5px)`, otherwise this would be even more complex - .reduce( - ({list, state}, item) => { - const openParansCount = (item.match(/\(/g) || []).length - const closedParansCount = (item.match(/\)/g) || []).length - if (state.parensDepth > 0) { - list[list.length - 1] = `${list[list.length - 1]} ${item}` - } else { - list.push(item) - } - state.parensDepth += openParansCount - closedParansCount - return {list, state} - }, - {list: [], state: {parensDepth: 0}}, - ).list - ) -} - -/** - * This is intended for properties that are `top right bottom left` and will switch them to `top left bottom right` - * @param {String} value - `1px 2px 3px 4px` for example, but also handles cases where there are too few/too many and - * simply returns the value in those cases (which is the correct behavior) - * @return {String} the result - `1px 4px 3px 2px` for example. - */ -function handleQuartetValues(value) { - const splitValues = getValuesAsList(value) - if (splitValues.length <= 3 || splitValues.length > 4) { - return value - } - const [top, right, bottom, left] = splitValues - return [top, left, bottom, right].join(' ') -} - -/** - * Takes a percentage for background position and inverts it. - * This was copied and modified from CSSJanus: - * https://github.com/cssjanus/cssjanus/blob/4245f834365f6cfb0239191a151432fb85abab23/src/cssjanus.js#L152-L175 - * @param {String} value - the original value (for example 77%) - * @return {String} the result (for example 23%) - */ -function calculateNewBackgroundPosition(value) { - const idx = value.indexOf('.') - if (idx === -1) { - value = `${100 - parseFloat(value)}%` - } else { - // Two off, one for the "%" at the end, one for the dot itself - const len = value.length - idx - 2 - value = 100 - parseFloat(value) - value = `${value.toFixed(len)}%` - } - return value -} - -/** - * Flip the sign of a CSS value, possibly with a unit. - * - * We can't just negate the value with unary minus due to the units. - * - * @private - * @param {String} value - the original value (for example 77%) - * @return {String} the result (for example -77%) - */ -function flipSign(value) { - if (parseFloat(value) === 0) { - // Don't mangle zeroes - return value - } - - if (value[0] === '-') { - return value.slice(1) - } - - return `-${value}` -} - -function calculateNewTranslate(match, prefix, offset, suffix) { - return prefix + flipSign(offset) + suffix -} - -/** - * Takes an array of [keyValue1, keyValue2] pairs and creates an object of {keyValue1: keyValue2, keyValue2: keyValue1} - * @param {Array} array the array of pairs - * @return {Object} the {key, value} pair object - */ -function arrayToObject(array) { - return array.reduce((obj, [prop1, prop2]) => { - obj[prop1] = prop2 - obj[prop2] = prop1 - return obj - }, {}) -} - -function isNumber(val) { - return typeof val === 'number' -} - -function isObject(val) { - return val && typeof val === 'object' -} - -function isString(val) { - return typeof val === 'string' -} - -function includes(inclusive, inclusee) { - return inclusive.indexOf(inclusee) !== -1 -} - // here's our main export! 👋 export default convert