From d73454695f55b0c5949d419c04d80430e0ae7244 Mon Sep 17 00:00:00 2001 From: Maja Wichrowska Date: Mon, 5 Feb 2018 16:41:55 -0800 Subject: [PATCH] Separate out LTR/RTL create and resolve methods --- package.json | 4 +- src/aphroditeInterfaceFactory.js | 28 +- src/aphroditeInterfaceWithRTLFactory.js | 25 -- src/utils/generateDirectionalStyles.js | 68 --- src/utils/{resolve.js => resolveLTR.js} | 12 +- src/utils/resolveRTL.js | 29 ++ src/utils/resolveWithRTL.js | 66 --- src/utils/separateStyles.js | 10 +- src/utils/withRTLExtension.js | 36 -- src/with-rtl.js | 5 - test/aphroditeInterfaceFactory_test.js | 64 ++- test/aphroditeInterfaceWithRTLFactory_test.js | 57 --- test/aphroditeInterface_test.js | 5 +- test/no-important_test.js | 5 +- test/utils/generateDirectionalStyles_test.js | 184 -------- .../{resolve_test.js => resolveLTR_test.js} | 41 +- ...olveWithRTL_test.js => resolveRTL_test.js} | 55 +-- test/utils/withRTLExtension_test.js | 405 ------------------ test/with-rtl_test.js | 33 -- with-rtl.js | 1 - 20 files changed, 148 insertions(+), 985 deletions(-) delete mode 100644 src/aphroditeInterfaceWithRTLFactory.js delete mode 100644 src/utils/generateDirectionalStyles.js rename src/utils/{resolve.js => resolveLTR.js} (70%) create mode 100644 src/utils/resolveRTL.js delete mode 100644 src/utils/resolveWithRTL.js delete mode 100644 src/utils/withRTLExtension.js delete mode 100644 src/with-rtl.js delete mode 100644 test/aphroditeInterfaceWithRTLFactory_test.js delete mode 100644 test/utils/generateDirectionalStyles_test.js rename test/utils/{resolve_test.js => resolveLTR_test.js} (71%) rename test/utils/{resolveWithRTL_test.js => resolveRTL_test.js} (62%) delete mode 100644 test/utils/withRTLExtension_test.js delete mode 100644 test/with-rtl_test.js delete mode 100644 with-rtl.js diff --git a/package.json b/package.json index 300011c..270a8f2 100644 --- a/package.json +++ b/package.json @@ -63,12 +63,14 @@ "sinon-sandbox": "^1.0.2" }, "peerDependencies": { - "aphrodite": ">=0.5.0" + "aphrodite": ">=0.5.0", + "react-with-styles": "^3.0.0" }, "dependencies": { "array-flatten": "^2.1.1", "has": "^1.0.1", "object.assign": "^4.1.0", + "object.entries": "^1.0.4", "rtl-css-js": "^1.8.0" } } diff --git a/src/aphroditeInterfaceFactory.js b/src/aphroditeInterfaceFactory.js index 09d5e27..464b565 100644 --- a/src/aphroditeInterfaceFactory.js +++ b/src/aphroditeInterfaceFactory.js @@ -1,18 +1,38 @@ import { flushToStyleTag } from 'aphrodite/lib/inject'; +import rtlCSSJS from 'rtl-css-js'; +import entries from 'object.entries'; -import resolve from './utils/resolve'; +import resolveLTR from './utils/resolveLTR'; +import resolveRTL from './utils/resolveRTL'; export default ({ StyleSheet, css }/* aphrodite */) => ({ create(styleHash) { + return this.createLTR(styleHash); + }, + + createLTR(styleHash) { return StyleSheet.create(styleHash); }, + createRTL(styleHash) { + const styleHashRTL = {}; + entries(styleHash).forEach(([styleKey, styleDef]) => { + styleHashRTL[styleKey] = rtlCSSJS(styleDef); + }); + + return StyleSheet.create(styleHashRTL); + }, + resolve(styles) { - return resolve(css, styles); + return this.resolveLTR(styles); + }, + + resolveLTR(styles) { + return resolveLTR(css, styles); }, - resolveNoRTL(styles) { - return resolve(css, styles); + resolveRTL(styles) { + return resolveRTL(css, styles); }, // Flushes all buffered styles to a style tag. Required for components diff --git a/src/aphroditeInterfaceWithRTLFactory.js b/src/aphroditeInterfaceWithRTLFactory.js deleted file mode 100644 index a835aca..0000000 --- a/src/aphroditeInterfaceWithRTLFactory.js +++ /dev/null @@ -1,25 +0,0 @@ -import { flushToStyleTag } from 'aphrodite/lib/inject'; - -import resolveWithRTL from './utils/resolveWithRTL'; -import resolve from './utils/resolve'; - -export default ({ StyleSheet, css }/* aphrodite */) => ({ - create(styleHash) { - return StyleSheet.create(styleHash); - }, - - resolve(styles) { - return resolveWithRTL(css, styles); - }, - - resolveNoRTL(styles) { - return resolve(css, styles); - }, - - // Flushes all buffered styles to a style tag. Required for components - // that depend upon previous styles in the component tree (i.e. - // for calculating container width, including padding/margin). - flush() { - flushToStyleTag(); - }, -}); diff --git a/src/utils/generateDirectionalStyles.js b/src/utils/generateDirectionalStyles.js deleted file mode 100644 index b297b72..0000000 --- a/src/utils/generateDirectionalStyles.js +++ /dev/null @@ -1,68 +0,0 @@ -import rtlCSSJS from 'rtl-css-js'; - -import { LTR_SELECTOR, RTL_SELECTOR } from './withRTLExtension'; - -function isEmpty(obj) { - return obj && Object.keys(obj).length === 0; -} - -function separateDirectionalStyles(originalStyles, autoRTLStyles) { - const sharedStyles = {}; - const ltrStyles = { ...originalStyles }; - const rtlStyles = {}; - - let hasRTLStyles = false; - Object.entries(autoRTLStyles) - .forEach(([key, value]) => { - if (value === originalStyles[key]) { - delete ltrStyles[key]; - sharedStyles[key] = value; - return; - } - - if (value && typeof value === 'object') { - delete ltrStyles[key]; - // In some cases (pseudoselectors, matchmedia queries, etc.), the style - // value may be an object, and we need to recurse. - const recursiveStyles = separateDirectionalStyles(originalStyles[key], value); - - if (recursiveStyles != null) { - hasRTLStyles = true; - const { - sharedStyles: recursiveSharedStyles, - ltrStyles: recursiveLtrStyles, - rtlStyles: recursiveRtlStyles, - } = recursiveStyles; - - if (recursiveSharedStyles) sharedStyles[key] = recursiveSharedStyles; - if (recursiveLtrStyles) ltrStyles[key] = recursiveLtrStyles; - if (recursiveRtlStyles) rtlStyles[key] = recursiveRtlStyles; - } else { - sharedStyles[key] = value; - } - } else if (value != null) { - hasRTLStyles = true; - rtlStyles[key] = value; - } - }); - - if (!hasRTLStyles) return null; - - return { - ...!isEmpty(sharedStyles) && { sharedStyles }, - ...!isEmpty(ltrStyles) && { ltrStyles }, - ...!isEmpty(rtlStyles) && { rtlStyles }, - }; -} - -export default function generateDirectionalStyles(originalStyles) { - const directionalStyles = separateDirectionalStyles(originalStyles, rtlCSSJS(originalStyles)); - if (!directionalStyles) return null; - - const { sharedStyles, ltrStyles, rtlStyles } = directionalStyles; - return { - ...sharedStyles, - [LTR_SELECTOR]: ltrStyles, - [RTL_SELECTOR]: rtlStyles, - }; -} diff --git a/src/utils/resolve.js b/src/utils/resolveLTR.js similarity index 70% rename from src/utils/resolve.js rename to src/utils/resolveLTR.js index 9ed2bee..6e617db 100644 --- a/src/utils/resolve.js +++ b/src/utils/resolveLTR.js @@ -1,27 +1,19 @@ -/* eslint-disable no-param-reassign */ import { from as flatten } from 'array-flatten'; import separateStyles from './separateStyles'; -function resetStyleDefinition(stylesObj) { - if (stylesObj.noRTL) { - stylesObj._definition = stylesObj.noRTL; - } - return stylesObj; -} - // Styles is an array of properties returned by `create()`, a POJO, or an // array thereof. POJOs are treated as inline styles. This version of the // resolve function explicitly does no work to flip styles for an RTL context. // This function returns an object to be spread onto an element. -export default function resolve(css, styles) { +export default function resolveLTR(css, styles) { const flattenedStyles = flatten(styles); const { aphroditeStyles, hasInlineStyles, inlineStyles, - } = separateStyles(flattenedStyles, resetStyleDefinition); + } = separateStyles(flattenedStyles); const result = {}; if (aphroditeStyles.length > 0) { diff --git a/src/utils/resolveRTL.js b/src/utils/resolveRTL.js new file mode 100644 index 0000000..5f7b328 --- /dev/null +++ b/src/utils/resolveRTL.js @@ -0,0 +1,29 @@ +import { from as flatten } from 'array-flatten'; +import rtlCSSJS from 'rtl-css-js'; + +import separateStyles from './separateStyles'; + +// Styles is an array of properties returned by `create()`, a POJO, or an +// array thereof. POJOs are treated as inline styles. This version of the +// resolve function explicitly flips inline styles for an RTL context. +// This function returns an object to be spread onto an element. +export default function resolveRTL(css, styles) { + const flattenedStyles = flatten(styles); + + const { + aphroditeStyles, + hasInlineStyles, + inlineStyles, + } = separateStyles(flattenedStyles); + + const result = {}; + if (aphroditeStyles.length > 0) { + result.className = css(...aphroditeStyles); + } + + if (hasInlineStyles) { + result.style = rtlCSSJS(inlineStyles); + } + + return result; +} diff --git a/src/utils/resolveWithRTL.js b/src/utils/resolveWithRTL.js deleted file mode 100644 index eb5f015..0000000 --- a/src/utils/resolveWithRTL.js +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable no-underscore-dangle, no-param-reassign */ -import { from as flatten } from 'array-flatten'; -import { hashObject } from 'aphrodite/lib/util'; - -import separateStyles from './separateStyles'; -import generateDirectionalStyles from './generateDirectionalStyles'; - -function setStyleDefinitionWithRTL(stylesObj) { - // Since we are mutating the StyleSheet object directly, we want to cache the withRTL/noRTL - // results and then set the _definition key to point to the appropriate version when necessary. - if (!stylesObj.withRTL || !stylesObj.noRTL) { - // The _definition key is an implementation detail of aphrodite. If aphrodite - // changes it in the future, this code will need to be updated. - const definition = stylesObj._definition; - stylesObj.noRTL = definition; - - const directionalStyles = generateDirectionalStyles(definition); - stylesObj.withRTL = directionalStyles || definition; - } - stylesObj._definition = stylesObj.withRTL; - return stylesObj; -} - -// Styles is an array of properties returned by `create()`, a POJO, or an -// array thereof. POJOs are treated as inline styles. The default resolve -// method makes an effort to flip CSS styles for an RTL context. -// This function returns an object to be spread onto an element. -export default function resolveWithRTL(css, styles) { - const flattenedStyles = flatten(styles); - - const { - aphroditeStyles, - hasInlineStyles, - inlineStyles, - } = separateStyles(flattenedStyles, setStyleDefinitionWithRTL); - - const result = {}; - if (hasInlineStyles) { - const inlineRTLStyles = generateDirectionalStyles(inlineStyles); - - if (inlineRTLStyles) { - // Because we know nothing about the current directional context, there - // is no way to determine whether or not the inline styles should be - // flipped. As a result, if there are inline styles to be flipped, we - // do so by converting them to classnames and providing styles in both - // an RTL and an LTR context. - // This may not work for all situations! For instance, when using inline - // styles to animate a component with react-motion or animated.js, - // converting to classes is likely to be way too slow. For those and - // other edge-cases, consumers should rely on the resolveNoRTL method - // instead. - aphroditeStyles.push({ - _name: `inlineStyles_${hashObject(inlineRTLStyles)}`, - _definition: inlineRTLStyles, - }); - } else { - result.style = inlineStyles; - } - } - - if (aphroditeStyles.length > 0) { - result.className = css(...aphroditeStyles); - } - - return result; -} diff --git a/src/utils/separateStyles.js b/src/utils/separateStyles.js index a02c982..6ff6deb 100644 --- a/src/utils/separateStyles.js +++ b/src/utils/separateStyles.js @@ -2,7 +2,7 @@ import has from 'has'; // This function takes the array of styles and separates them into styles that // are handled by Aphrodite and inline styles. -export default function separateStyles(stylesArray, aphroditeStyleTransform) { +export default function separateStyles(stylesArray) { const aphroditeStyles = []; // Since determining if an Object is empty requires collecting all of its @@ -15,7 +15,7 @@ export default function separateStyles(stylesArray, aphroditeStyleTransform) { // performance is critical. Normally we would prefer using `forEach`, but // old-fashioned for loops are faster so that's what we have chosen here. for (let i = 0; i < stylesArray.length; i += 1) { - let style = stylesArray[i]; + const style = stylesArray[i]; // If this style is falsey, we just want to disregard it. This allows for // syntax like: @@ -26,12 +26,6 @@ export default function separateStyles(stylesArray, aphroditeStyleTransform) { has(style, '_name') && has(style, '_definition') ) { - // This looks like a reference to an Aphrodite style object, so that's - // where it goes. - if (aphroditeStyleTransform) { - style = aphroditeStyleTransform(style); - } - aphroditeStyles.push(style); } else { Object.assign(inlineStyles, style); diff --git a/src/utils/withRTLExtension.js b/src/utils/withRTLExtension.js deleted file mode 100644 index 824237f..0000000 --- a/src/utils/withRTLExtension.js +++ /dev/null @@ -1,36 +0,0 @@ -export const LTR_SELECTOR = '_ltr'; -export const RTL_SELECTOR = '_rtl'; - -const styleDefRegex = /(\.[^{}]+\{[^{}]+\})/g; - -/* - * When automatically flipping CSS styles in our interface, instead of determining RTL/LTR context - * at the time of the create or resolve call (which is hard to do without either setting the - * direction in the global cache or ignoring inline styles entirely), we simply add a new style - * definition for the generated class name that is only applied in a [dir="rtl"] context. This - * handler adds that extra style definition to the