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]
-[](#contributors)
+[](#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