diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4867b9..7fda9f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.2.0] - 2020-04-16 + +- Introduced a new parameter (bothPrefix) to manage styles that can be overridden due to specificity + ## [1.1.2] - 2020-04-14 - Avoid duplicating the same declarations in the flipped rules diff --git a/README.md b/README.md index fa6fa6a0..17233131 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ All the options are optional, and a default value will be used, if any of them i | mode | `Mode (string)` | `Mode.combined` | Mode of generating the final CSS rules | | ltrPrefix | `string` or `string[]` | `[dir="ltr"]` | Prefix to use in the left-to-right CSS rules | | rtlPrefix | `string` or `string[]` | `[dir="rtl"]` | Prefix to use in the right-to-left CSS rules | +| bothPrefix | `string` or `string[]` | `[dir]` | Prefix to use for styles in both directions when the specificity of the ltr or rtl styles will override them | | source | `Source (string)` | `Source.ltr` | The direction from which the final CSS will be generated | | processUrls | `boolean` | `false` | Change the strings using the string map also in URLs | | processKeyFrames | `boolean` | `false` | Flip keyframe animations | @@ -418,6 +419,68 @@ const options = { --- +#### bothPrefix + +
Expand +

+ +This prefix will be used in some specific cases in which a ltr or rtl rule will override styles located in the main rule due to [specificty](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity). Consider the next example using the option `processUrls` as `true`: + +```css +.test1 { + background: url('icons/ltr/arrow.png'); + background-size: 10px 20px; + width: 10px; +} +``` + +The generated CSS would be: + +```css +.test1 { + background-size: 10px 20px; + width: 10px; +} + +[dir="ltr"] .test1 { + background: url('icons/ltr/arrow.png'); +} + +[dir="rtl"] .test1 { + background: url('icons/rtl/arrow.png'); +} +``` + +In the previous case, the `background-size` property has been overridden by the `background` one. Even if we change the order of the rules, the last ones have a higher specificty, so they will rule over the first one. + +To solve this, another rule will be created at the end using the `bothPrefix` parameter: + +```css +.test1 { + width: 10px; +} + +[dir="ltr"] .test1 { + background: url('icons/ltr/arrow.png'); +} + +[dir="rtl"] .test1 { + background: url('icons/rtl/arrow.png'); +} + +[dir] { + background-size: 10px 20px; +} +``` + +And no matter the direction, the `background-size` property is respected. + +

+ +
+ +--- + #### source
Expand diff --git a/src/@types/index.ts b/src/@types/index.ts index c5da86b8..67be19bb 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -41,6 +41,7 @@ export interface PluginOptions { mode?: ModeValues; ltrPrefix?: strings; rtlPrefix?: strings; + bothPrefix?: strings; source?: SourceValues; processUrls?: boolean; processKeyFrames?: boolean; @@ -56,6 +57,7 @@ export interface RulesObject { rule: Rule; ruleLTR: Rule | null; ruleRTL: Rule | null; + ruleBoth: Rule | null; } export interface AtRulesObject { @@ -78,4 +80,11 @@ export interface KeyFramesData { keyframes: AtRulesObject[]; stringMap: AtRulesStringMap; regExp: RegExp; +} + +export interface ShortHandsData { + [key: string]: { + overridden: string | null; + overrides: string[]; + }; } \ No newline at end of file diff --git a/src/data/shorthands.json b/src/data/shorthands.json new file mode 100644 index 00000000..87cba5f3 --- /dev/null +++ b/src/data/shorthands.json @@ -0,0 +1,250 @@ +{ + "animation": { + "overridden": null, + "overrides": [ + "animation-direction", + "animation-duration", + "animation-delay", + "animation-name", + "animation-fill-mode", + "animation-iteration-count", + "animation-play-state", + "animation-timing-function" + ] + }, + "background": { + "overridden": null, + "overrides": [ + "background-attachment", + "background-color", + "background-image", + "background-position", + "background-repeat" + ] + }, + "border": { + "overridden": null, + "overrides": [ + "border-color", + "border-style", + "border-width" + ] + }, + "border-bottom": { + "overridden": "border", + "overrides": [ + "border-bottom-color", + "border-bottom-style", + "border-bottom-width" + ] + }, + "border-left": { + "overridden": "border", + "overrides": [ + "border-left-color", + "border-left-style", + "border-left-width" + ] + }, + "border-right": { + "overridden": "border", + "overrides": [ + "border-right-color", + "border-right-style", + "border-right-width" + ] + }, + "border-top": { + "overridden": "border", + "overrides": [ + "border-top-color", + "border-top-style", + "border-top-width" + ] + }, + "border-radius": { + "overridden": null, + "overrides": [ + "border-bottom-left-radius", + "border-bottom-right-radius", + "border-top-left-radius", + "border-top-right-radius" + ] + }, + "columns": { + "overridden": null, + "overrides": [ + "column-count", + "column-width" + ] + }, + "column-rule": { + "overridden": null, + "overrides": [ + "column-rule-color", + "column-rule-style", + "column-rule-width" + ] + }, + "font": { + "overridden": null, + "overrides": [ + "font-family", + "font-style", + "font-size", + "font-stretch", + "font-variant", + "font-weight", + "line-height" + ] + }, + "flex": { + "overridden": null, + "overrides": [ + "flex-basis", + "flex-grow", + "flex-shrink" + ] + }, + "flex-flow": { + "overridden": null, + "overrides": [ + "flex-direction", + "flex-wrap" + ] + }, + "grid": { + "overridden": null, + "overrides": [ + "grid-auto-columns", + "grid-auto-flow", + "grid-auto-rows", + "grid-column-gap", + "grid-row-gap", + "grid-template-areas", + "grid-template-columns", + "grid-template-rows" + ] + }, + "grid-area": { + "overridden": null, + "overrides": [ + "grid-column-end", + "grid-column-start", + "grid-row-end", + "grid-row-start" + ] + }, + "grid-column": { + "overridden": "grid-area", + "overrides": [ + "grid-column-end", + "grid-column-start" + ] + }, + "grid-row": { + "overridden": "grid-area", + "overrides": [ + "grid-row-end", + "grid-row-start" + ] + }, + "grid-template": { + "overridden": "grid", + "overrides": [ + "grid-template-areas", + "grid-template-columns", + "grid-template-rows" + ] + }, + "list-style": { + "overridden": null, + "overrides": [ + "list-style-image", + "list-style-position", + "list-style-type" + ] + }, + "margin": { + "overridden": null, + "overrides": [ + "margin-bottom", + "margin-left", + "margin-top", + "margin-right" + ] + }, + "offset": { + "overridden": null, + "overrides": [ + "offset-anchor", + "offset-distance", + "offset-path", + "offset-position", + "offset-rotate" + ] + }, + "outline": { + "overridden": null, + "overrides": [ + "outline-color", + "outline-style", + "outline-width" + ] + }, + "overflow": { + "overridden": null, + "overrides": [ + "overflow-x", + "overflow-y" + ] + }, + "padding": { + "overridden": null, + "overrides": [ + "padding-bottom", + "padding-left", + "padding-top", + "padding-right" + ] + }, + "place-content": { + "overridden": null, + "overrides": [ + "align-content", + "justify-content" + ] + }, + "place-items": { + "overridden": null, + "overrides": [ + "align-items", + "justify-items" + ] + }, + "place-self": { + "overridden": null, + "overrides": [ + "align-self", + "justify-self" + ] + }, + "text-decoration": { + "overridden": null, + "overrides": [ + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-decoration-thickness" + ] + }, + "transition": { + "overridden": null, + "overrides": [ + "transition-delay", + "transition-duration", + "transition-property", + "transition-timing-function" + ] + } +} \ No newline at end of file diff --git a/src/parsers/declarations.ts b/src/parsers/declarations.ts index ff1060f4..fc2f80e8 100644 --- a/src/parsers/declarations.ts +++ b/src/parsers/declarations.ts @@ -3,6 +3,7 @@ import rtlcss from 'rtlcss'; import { RulesObject, KeyFramesData, PluginOptionsNormalized, Source, Mode, ObjectWithProps } from '@types'; import { DECLARATION_TYPE, FLIP_PROPERTY_REGEXP, ANIMATION_PROP, ANIMATION_NAME_PROP } from '@constants'; import { addSelectorPrefixes } from '@utilities/selectors'; +import { shorthands } from '@utilities/shorthands'; import { walkContainer } from '@utilities/containers'; export const parseDeclarations = ( @@ -12,15 +13,13 @@ export const parseDeclarations = ( options: PluginOptionsNormalized ): void => { - const { mode, ltrPrefix, rtlPrefix, source, processUrls, useCalc, stringMap } = options; + const { mode, ltrPrefix, rtlPrefix, bothPrefix, source, processUrls, useCalc, stringMap } = options; const deleteDeclarations: Declaration[] = []; - const ruleFlipped = rule.clone(); - const ruleFlippedSecond = rule.clone(); - - ruleFlipped.removeAll(); - ruleFlippedSecond.removeAll(); + const ruleFlipped = rule.clone().removeAll(); + const ruleFlippedSecond = ruleFlipped.clone(); + const ruleBoth = ruleFlipped.clone(); const declarationHashMap = Array.prototype.reduce.call(rule.nodes, (obj: ObjectWithProps, node: Node): object => { if (node.type === DECLARATION_TYPE) { @@ -30,6 +29,8 @@ export const parseDeclarations = ( return obj; }, {}); + const declarationsProps: string[] = []; + walkContainer(rule, [ DECLARATION_TYPE ], true, (node: Node): void => { const decl = node as Declaration; @@ -46,8 +47,11 @@ export const parseDeclarations = ( const isAnimation = declPropUnprefixed === ANIMATION_PROP || declPropUnprefixed === ANIMATION_NAME_PROP; const declFlippedProp = declFlipped.prop.trim(); const declFlippedValue = declFlipped.value.trim(); - + const overridenBy = shorthands[declPropUnprefixed] || ''; + const hasBeenOverriden = declarationsProps.includes(overridenBy); + if ( + !hasBeenOverriden && declProp === declFlippedProp && declValue === declFlippedValue && ( @@ -93,9 +97,19 @@ export const parseDeclarations = ( declCloneFlipped.value = declValueFlipped; ruleFlipped.append(declCloneFlipped); } + } else { - if (declarationHashMap[declFlipped.prop] === declFlippedValue) { + if ( + hasBeenOverriden && + declProp === declFlippedProp && + declValue === declFlippedValue + ) { + const declClone = decl.clone(); + ruleBoth.append(declClone); + deleteDeclarations.push(decl); + return; + } else if (declarationHashMap[declFlipped.prop] === declFlippedValue) { return; } @@ -113,6 +127,8 @@ export const parseDeclarations = ( ruleFlipped.append(declFlipped); } + declarationsProps.push(declPropUnprefixed); + } }); @@ -131,10 +147,13 @@ export const parseDeclarations = ( addSelectorPrefixes(ruleFlippedSecond, source === Source.rtl ? rtlPrefix : ltrPrefix); } + addSelectorPrefixes(ruleBoth, bothPrefix); + rules.push({ rule, ruleLTR: ruleFlipped, - ruleRTL: ruleFlippedSecond + ruleRTL: ruleFlippedSecond, + ruleBoth }); } diff --git a/src/utilities/options.ts b/src/utilities/options.ts index 236e0370..4d6f6f82 100644 --- a/src/utilities/options.ts +++ b/src/utilities/options.ts @@ -68,6 +68,7 @@ const defaultOptions: PluginOptionsNormalized = { mode: Mode.combined, ltrPrefix: '[dir="ltr"]', rtlPrefix: '[dir="rtl"]', + bothPrefix: '[dir]', source: Source.ltr, processUrls: false, processKeyFrames: false, @@ -89,6 +90,9 @@ export const normalizeOptions = (options: PluginOptions): PluginOptionsNormalize if (!isNotAcceptedPrefix(options.rtlPrefix)) { returnOptions.rtlPrefix = options.rtlPrefix; } + if (!isNotAcceptedPrefix(options.bothPrefix)) { + returnOptions.bothPrefix = options.bothPrefix; + } if (typeof options.processUrls === BOOLEAN_TYPE) { returnOptions.processUrls = options.processUrls; } diff --git a/src/utilities/rules.ts b/src/utilities/rules.ts index 4b6ffd83..d562d2f9 100644 --- a/src/utilities/rules.ts +++ b/src/utilities/rules.ts @@ -28,13 +28,14 @@ export const cleanRules = (...rules: (Rule | AtRule | undefined | null)[]): void }; export const appendRules = (rules: RulesObject[]): void => { - rules.forEach(({rule, ruleLTR, ruleRTL}): void => { + rules.forEach(({rule, ruleLTR, ruleRTL, ruleBoth}): void => { + ruleBoth && ruleBoth.nodes.length && rule.after(ruleBoth); ruleRTL && ruleRTL.nodes.length && rule.after(ruleRTL); - ruleLTR && ruleLTR.nodes.length && rule.after(ruleLTR); + ruleLTR && ruleLTR.nodes.length && rule.after(ruleLTR); if (rule.nodes.length === 0) { rule.remove(); } - cleanRules(rule, ruleLTR, ruleRTL); + cleanRules(rule, ruleLTR, ruleRTL, ruleBoth); }); }; diff --git a/src/utilities/shorthands.ts b/src/utilities/shorthands.ts new file mode 100644 index 00000000..67a8b85b --- /dev/null +++ b/src/utilities/shorthands.ts @@ -0,0 +1,19 @@ +import { ObjectWithProps, ShortHandsData } from '@types'; +import shorthandsJson from '@data/shorthands.json'; + +const shorthandsData: ShortHandsData = shorthandsJson; +const shorthands: ObjectWithProps = {}; + +Object.keys(shorthandsData).forEach((prop: string): void => { + + shorthands[prop] = shorthandsData[prop].overridden; + + shorthandsData[prop].overrides.forEach((oprop: string): void => { + + shorthands[oprop] = prop; + + }); + +}); + +export { shorthands }; \ No newline at end of file diff --git a/tests/__snapshots__/combined.test.ts.snap b/tests/__snapshots__/combined.test.ts.snap index f5bc8b15..971b6dfe 100644 --- a/tests/__snapshots__/combined.test.ts.snap +++ b/tests/__snapshots__/combined.test.ts.snap @@ -2,9 +2,9 @@ exports[`Combined Tests Combined {processKeyFrames: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -279,14 +279,12 @@ exports[`Combined Tests Combined {processKeyFrames: true} 1`] = ` exports[`Combined Tests Combined {processUrls: true} 1`] = ` ".test1, .test2 { - background-color: #FFF; - background-position: 10px 20px; color: #666; width: 100%; } [dir=\\"ltr\\"] .test1, [dir=\\"ltr\\"] .test2 { - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; padding-right: 20px; text-align: left; @@ -294,13 +292,18 @@ exports[`Combined Tests Combined {processUrls: true} 1`] = ` } [dir=\\"rtl\\"] .test1, [dir=\\"rtl\\"] .test2 { - background-image: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); border-radius: 2px 0 8px 0; padding-left: 20px; text-align: right; transform: translate(50%, 50%); } +[dir] .test1, [dir] .test2 { + background-color: #FFF; + background-position: 10px 20px; +} + /* Comment not related to rtl */ .test3 { margin: 1px 2px 3px; @@ -530,9 +533,9 @@ exports[`Combined Tests Combined {processUrls: true} 1`] = ` exports[`Combined Tests Combined {source: rtl, processKeyFrames: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -807,9 +810,9 @@ exports[`Combined Tests Combined {source: rtl, processKeyFrames: true} 1`] = ` exports[`Combined Tests Combined {source: rtl} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -1057,8 +1060,8 @@ exports[`Combined Tests Combined {source: rtl} 1`] = ` exports[`Combined Tests Combined {useCalc: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -1309,9 +1312,9 @@ exports[`Combined Tests Combined {useCalc: true} 1`] = ` exports[`Combined Tests Combined Basic 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -1557,11 +1560,11 @@ exports[`Combined Tests Combined Basic 1`] = ` }" `; -exports[`Combined Tests Combined custom ltrPrefix and rtlPrefix properties 1`] = ` +exports[`Combined Tests Combined custom ltrPrefix and rtlPrefix 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -1809,9 +1812,9 @@ exports[`Combined Tests Combined custom ltrPrefix and rtlPrefix properties 1`] = exports[`Combined Tests Combined custom ltrPrefix and rtlPrefix properties as arrays 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); color: #666; width: 100%; } @@ -2067,16 +2070,278 @@ exports[`Combined Tests Combined custom ltrPrefix and rtlPrefix properties as ar }" `; -exports[`Combined Tests Combined custom string map 1`] = ` +exports[`Combined Tests Combined custom ltrPrefix, rtlPrefix, and bothPrefix properties as arrays and processUrls: true 1`] = ` ".test1, .test2 { + color: #666; + width: 100%; +} + +.ltr .test1, .left-to-right .test1, .ltr .test2, .left-to-right .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); + border-radius: 0 2px 0 8px; + padding-right: 20px; + text-align: left; + transform: translate(-50%, 50%); +} + +.rtl .test1, .right-to-left .test1, .rtl .test2, .right-to-left .test2 { + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); + border-radius: 2px 0 8px 0; + padding-left: 20px; + text-align: right; + transform: translate(50%, 50%); +} + +.ltr .test1, .left-to-right .test1, .rtl .test1, .right-to-left .test1, .ltr .test2, .left-to-right .test2, .rtl .test2, .right-to-left .test2 { background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); +} + +/* Comment not related to rtl */ +.test3 { + margin: 1px 2px 3px; + padding: 10px 20px; + /* Property comment not related to rtl */ + text-align: center; +} + +.ltr .test3, .left-to-right .test3 { + direction: ltr; +} + +.rtl .test3, .right-to-left .test3 { + direction: rtl; +} + +.test4 { + font-size: 10px; + border-color: red; + transform-origin: 10px 20px; + transform: scale(0.5, 0.5); +} + +.ltr .test4, .left-to-right .test4 { + border-radius: 2px 4px 8px 16px; + text-shadow: red 99px -1px 1px, blue 99px 2px 1px; +} + +.rtl .test4, .right-to-left .test4 { + border-radius: 4px 2px 16px 8px; + text-shadow: red -99px -1px 1px, blue -99px 2px 1px; +} + +.test5, +.test6, +.test7 { + border: 1px solid 2px; + box-sizing: border-box; + position: absolute; +} + +.ltr .test5, +.left-to-right .test5, +.ltr .test6, +.left-to-right .test6, +.ltr .test7, +.left-to-right .test7 { + background: linear-gradient(0.25turn, #3F87A6, #EBF8E1, #F69D3C); + border-color: #666 #777 #888 #999; + border-width: 1px 2px 3px 4px; + left: 100px; + transform: translateX(5px); +} + +.rtl .test5, +.right-to-left .test5, +.rtl .test6, +.right-to-left .test6, +.rtl .test7, +.right-to-left .test7 { + background: linear-gradient(-0.25turn, #3F87A6, #EBF8E1, #F69D3C); + border-color: #666 #999 #888 #777; + border-width: 1px 4px 3px 2px; + right: 100px; + transform: translateX(-5px); +} + +/*rtl:novalid:ignore*/ +.test8 { + display: flex; + padding-left: 10%; +} + +.ltr .test8, .left-to-right .test8 { + background: linear-gradient(to left, #333, #333 50%, #EEE 75%, #333 75%); +} + +.rtl .test8, .right-to-left .test8 { + background: linear-gradient(to right, #333, #333 50%, #EEE 75%, #333 75%); +} + +.test9, .test10 { + padding: 0px 2px 3px 4px; +} + +.ltr .test9, .left-to-right .test9, .ltr .test10, .left-to-right .test10 { + background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), + linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), + linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); + left: 5px; +} + +.rtl .test9, .right-to-left .test9, .rtl .test10, .right-to-left .test10 { + background: linear-gradient(-217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), + linear-gradient(-127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), + linear-gradient(-336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); + right: 5px; +} + +.ltr .test11:hover, +.left-to-right .test11:hover, +.ltr .test11:active, +.left-to-right .test11:active { + font-family: Arial, Helvetica; + font-size: 20px; + color: '#FFF'; + transform: translateY(10px); + padding: 10px; +} + +.rtl .test11:hover, +.right-to-left .test11:hover, +.rtl .test11:active, +.right-to-left .test11:active { + font-family: \\"Droid Arabic Kufi\\", Arial, Helvetica; + font-size: 30px; + color: #000; + transform: translateY(10px) scaleX(-1); + padding: 10px 20px; +} + +#test12, #test13 { + left: 10px; + position: relative; + text-align: right; +} + +.test14 .test15 span { + border-left-color: #777; + margin: 10px 20px 30px 40px; + transform: translate(100%, 10%); +} + +.test16 { + padding-right: 10px; +} + +.test17 { + cursor: pointer; + padding: 10px 20px 40px 10px; + text-align: right; +} + +@media only screen and (min-device-width: 320px) { + .test17 { + cursor: wait; + } +} + +.test18 { + animation: 5s flip 1s ease-in-out, + 3s my-animation 6s ease-in-out; + font-size: 10px; +} + +.ltr .test18, .left-to-right .test18 { + padding: 10px 20px 40px 10px; +} + +.rtl .test18, .right-to-left .test18 { + padding: 10px 10px 40px 20px; +} + +@keyframes flip { + from { + transform: translateX(100px); + } + to { + transform: translateX(0); + } +} + +@media only screen and (min-device-width: 320px) { + .test18 { + width: 100%; + } + + .ltr .test18, .left-to-right .test18 { + padding: 1px 2px 3px 4px; + } + + .rtl .test18, .right-to-left .test18 { + padding: 1px 4px 3px 2px; + } +} + +.test19 { + animation-delay: 1s; + animation-duration: 3s; + animation-name: my-animation; + animation-timing-function: ease-in-out; +} + +@keyframes my-animation { + from { + left: 0%; + } + to { + left: 100%; + } +} + +.test20 { + animation-delay: 1s; + animation-duration: 3s; + animation-name: no-flip; + animation-timing-function: ease-in-out; + width: 100%; +} + +@keyframes no-flip { + from { + color: #000; + } + to { + color: #FFF; + } +} + +/* Do not add reset values in override mode */ +.ltr .test21, .left-to-right .test21 { + left: 5px; + right: 10px; +} + +.rtl .test21, .right-to-left .test21 { + right: 5px; + left: 10px; +} + +/* Do not create the RTL version if the result is the same */ +.test22 { + left: 10px; + right: 10px; +}" +`; + +exports[`Combined Tests Combined custom string map and processUrls: true 1`] = ` +".test1, .test2 { color: #666; width: 100%; } [dir=\\"ltr\\"] .test1, [dir=\\"ltr\\"] .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; padding-right: 20px; text-align: left; @@ -2084,12 +2349,18 @@ exports[`Combined Tests Combined custom string map 1`] = ` } [dir=\\"rtl\\"] .test1, [dir=\\"rtl\\"] .test2 { + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); border-radius: 2px 0 8px 0; padding-left: 20px; text-align: right; transform: translate(50%, 50%); } +[dir] .test1, [dir] .test2 { + background-color: #FFF; + background-position: 10px 20px; +} + /* Comment not related to rtl */ .test3 { margin: 1px 2px 3px; diff --git a/tests/__snapshots__/override.test.ts.snap b/tests/__snapshots__/override.test.ts.snap index 1f426ede..30fc7958 100644 --- a/tests/__snapshots__/override.test.ts.snap +++ b/tests/__snapshots__/override.test.ts.snap @@ -2,9 +2,9 @@ exports[`Override Tests Override {processKeyFrames: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -253,9 +253,7 @@ exports[`Override Tests Override {processKeyFrames: true} 1`] = ` exports[`Override Tests Override {processUrls: true} 1`] = ` ".test1, .test2 { - background-color: #FFF; - background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -265,7 +263,7 @@ exports[`Override Tests Override {processUrls: true} 1`] = ` } [dir=\\"rtl\\"] .test1, [dir=\\"rtl\\"] .test2 { - background-image: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); border-radius: 2px 0 8px 0; padding-right: unset; padding-left: 20px; @@ -273,6 +271,11 @@ exports[`Override Tests Override {processUrls: true} 1`] = ` transform: translate(50%, 50%); } +[dir] .test1, [dir] .test2 { + background-color: #FFF; + background-position: 10px 20px; +} + /* Comment not related to rtl */ .test3 { direction: ltr; @@ -481,9 +484,9 @@ exports[`Override Tests Override {processUrls: true} 1`] = ` exports[`Override Tests Override {source: rtl, processKeyFrames: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -732,9 +735,9 @@ exports[`Override Tests Override {source: rtl, processKeyFrames: true} 1`] = ` exports[`Override Tests Override {source: rtl} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -959,9 +962,9 @@ exports[`Override Tests Override {source: rtl} 1`] = ` exports[`Override Tests Override {useCalc: true} 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -1188,9 +1191,9 @@ exports[`Override Tests Override {useCalc: true} 1`] = ` exports[`Override Tests Override Basic 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -1415,9 +1418,9 @@ exports[`Override Tests Override Basic 1`] = ` exports[`Override Tests Override custom ltrPrefix and rtlPrefix properties 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -1642,9 +1645,9 @@ exports[`Override Tests Override custom ltrPrefix and rtlPrefix properties 1`] = exports[`Override Tests Override custom ltrPrefix and rtlPrefix properties as arrays 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -1872,11 +1875,245 @@ exports[`Override Tests Override custom ltrPrefix and rtlPrefix properties as ar }" `; -exports[`Override Tests Override custom string map 1`] = ` +exports[`Override Tests Override custom ltrPrefix, rtlPrefix, and bothPrefix properties as arrays and processUrls: true 1`] = ` ".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); + border-radius: 0 2px 0 8px; + color: #666; + padding-right: 20px; + text-align: left; + transform: translate(-50%, 50%); + width: 100%; +} + +.rtl .test1, .right-to-left .test1, .rtl .test2, .right-to-left .test2 { + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); + border-radius: 2px 0 8px 0; + padding-right: unset; + padding-left: 20px; + text-align: right; + transform: translate(50%, 50%); +} + +.ltr .test1, .left-to-right .test1, .rtl .test1, .right-to-left .test1, .ltr .test2, .left-to-right .test2, .rtl .test2, .right-to-left .test2 { background-color: #FFF; background-position: 10px 20px; - background-image: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); +} + +/* Comment not related to rtl */ +.test3 { + direction: ltr; + margin: 1px 2px 3px; + padding: 10px 20px; + /* Property comment not related to rtl */ + text-align: center; +} + +.rtl .test3, .right-to-left .test3 { + direction: rtl; +} + +.test4 { + font-size: 10px; + border-color: red; + border-radius: 2px 4px 8px 16px; + text-shadow: red 99px -1px 1px, blue 99px 2px 1px; + transform-origin: 10px 20px; + transform: scale(0.5, 0.5); +} + +.rtl .test4, .right-to-left .test4 { + border-radius: 4px 2px 16px 8px; + text-shadow: red -99px -1px 1px, blue -99px 2px 1px; +} + +.test5, +.test6, +.test7 { + background: linear-gradient(0.25turn, #3F87A6, #EBF8E1, #F69D3C); + border: 1px solid 2px; + border-color: #666 #777 #888 #999; + box-sizing: border-box; + border-width: 1px 2px 3px 4px; + position: absolute; + left: 100px; + transform: translateX(5px); +} + +.rtl .test5, +.right-to-left .test5, +.rtl .test6, +.right-to-left .test6, +.rtl .test7, +.right-to-left .test7 { + background: linear-gradient(-0.25turn, #3F87A6, #EBF8E1, #F69D3C); + border-color: #666 #999 #888 #777; + border-width: 1px 4px 3px 2px; + left: unset; + right: 100px; + transform: translateX(-5px); +} + +/*rtl:novalid:ignore*/ +.test8 { + background: linear-gradient(to left, #333, #333 50%, #EEE 75%, #333 75%); + display: flex; + padding-left: 10%; +} + +.rtl .test8, .right-to-left .test8 { + background: linear-gradient(to right, #333, #333 50%, #EEE 75%, #333 75%); +} + +.test9, .test10 { + background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), + linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), + linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); + left: 5px; + padding: 0px 2px 3px 4px; +} + +.rtl .test9, .right-to-left .test9, .rtl .test10, .right-to-left .test10 { + background: linear-gradient(-217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), + linear-gradient(-127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), + linear-gradient(-336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); + left: unset; + right: 5px; +} + +.test11:hover, +.test11:active { + font-family: Arial, Helvetica; + font-size: 20px; + color: '#FFF'; + transform: translateY(10px); + padding: 10px; +} + +.rtl .test11:hover, +.right-to-left .test11:hover, +.rtl .test11:active, +.right-to-left .test11:active { + font-family: \\"Droid Arabic Kufi\\", Arial, Helvetica; + font-size: 30px; + color: #000; + transform: translateY(10px) scaleX(-1); + padding: 10px 20px; +} + +#test12, #test13 { + left: 10px; + position: relative; + text-align: right; +} + +.test14 .test15 span { + border-left-color: #777; + margin: 10px 20px 30px 40px; + transform: translate(100%, 10%); +} + +.test16 { + padding-right: 10px; +} + +.test17 { + cursor: pointer; + padding: 10px 20px 40px 10px; + text-align: right; +} + +@media only screen and (min-device-width: 320px) { + .test17 { + cursor: wait; + } +} + +.test18 { + animation: 5s flip 1s ease-in-out, + 3s my-animation 6s ease-in-out; + font-size: 10px; + padding: 10px 20px 40px 10px; +} + +.rtl .test18, .right-to-left .test18 { + padding: 10px 10px 40px 20px; +} + +@keyframes flip { + from { + transform: translateX(100px); + } + to { + transform: translateX(0); + } +} + +@media only screen and (min-device-width: 320px) { + .test18 { + padding: 1px 2px 3px 4px; + width: 100%; + } + + .rtl .test18, .right-to-left .test18 { + padding: 1px 4px 3px 2px; + } +} + +.test19 { + animation-delay: 1s; + animation-duration: 3s; + animation-name: my-animation; + animation-timing-function: ease-in-out; +} + +@keyframes my-animation { + from { + left: 0%; + } + to { + left: 100%; + } +} + +.test20 { + animation-delay: 1s; + animation-duration: 3s; + animation-name: no-flip; + animation-timing-function: ease-in-out; + width: 100%; +} + +@keyframes no-flip { + from { + color: #000; + } + to { + color: #FFF; + } +} + +/* Do not add reset values in override mode */ +.test21 { + left: 5px; + right: 10px; +} + +.rtl .test21, .right-to-left .test21 { + right: 5px; + left: 10px; +} + +/* Do not create the RTL version if the result is the same */ +.test22 { + left: 10px; + right: 10px; +}" +`; + +exports[`Override Tests Override custom string map and processUrls: true 1`] = ` +".test1, .test2 { + background: url(\\"/folder/subfolder/icons/ltr/chevron-left.png\\"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; @@ -1886,6 +2123,7 @@ exports[`Override Tests Override custom string map 1`] = ` } [dir=\\"rtl\\"] .test1, [dir=\\"rtl\\"] .test2 { + background: url(\\"/folder/subfolder/icons/rtl/chevron-right.png\\"); border-radius: 2px 0 8px 0; padding-right: unset; padding-left: 20px; @@ -1893,6 +2131,11 @@ exports[`Override Tests Override custom string map 1`] = ` transform: translate(50%, 50%); } +[dir] .test1, [dir] .test2 { + background-color: #FFF; + background-position: 10px 20px; +} + /* Comment not related to rtl */ .test3 { direction: ltr; diff --git a/tests/combined.test.ts b/tests/combined.test.ts index 94dfe4fb..29a87e79 100644 --- a/tests/combined.test.ts +++ b/tests/combined.test.ts @@ -53,7 +53,7 @@ describe('Combined Tests', (): void => { expect(output.warnings()).toHaveLength(0); }); - it('Combined custom ltrPrefix and rtlPrefix properties', (): void => { + it('Combined custom ltrPrefix and rtlPrefix', (): void => { const options: PluginOptions = { ...baseOptions, ltrPrefix: '.ltr', rtlPrefix: '.rtl' }; const output = postcss([postcssRTLCSS(options)]).process(input); expect(output.css).toMatchSnapshot(); @@ -67,12 +67,18 @@ describe('Combined Tests', (): void => { expect(output.warnings()).toHaveLength(0); }); - it('Combined custom string map', (): void => { + it('Combined custom ltrPrefix, rtlPrefix, and bothPrefix properties as arrays and processUrls: true', (): void => { + const options: PluginOptions = { ...baseOptions, ltrPrefix: ['.ltr', '.left-to-right'], rtlPrefix: ['.rtl', '.right-to-left'], bothPrefix: ['.ltr', '.left-to-right', '.rtl', '.right-to-left'], processUrls: true }; + const output = postcss([postcssRTLCSS(options)]).process(input); + expect(output.css).toMatchSnapshot(); + expect(output.warnings()).toHaveLength(0); + }); + + it('Combined custom string map and processUrls: true', (): void => { const stringMap: PluginOptions['stringMap'] = [ - {search: 'left', replace: 'right'}, - {search: 'ltr', replace: 'rtl'} + {search: 'left', replace: 'right'} ]; - const options: PluginOptions = { ...baseOptions, stringMap }; + const options: PluginOptions = { ...baseOptions, processUrls: true, stringMap }; const output = postcss([postcssRTLCSS(options)]).process(input); expect(output.css).toMatchSnapshot(); expect(output.warnings()).toHaveLength(0); diff --git a/tests/css/input.css b/tests/css/input.css index 8ab262c8..aa7e9324 100644 --- a/tests/css/input.css +++ b/tests/css/input.css @@ -1,7 +1,7 @@ .test1, .test2 { + background: url("/folder/subfolder/icons/ltr/chevron-left.png"); background-color: #FFF; background-position: 10px 20px; - background-image: url("/folder/subfolder/icons/ltr/chevron-left.png"); border-radius: 0 2px 0 8px; color: #666; padding-right: 20px; diff --git a/tests/override.test.ts b/tests/override.test.ts index 915c1231..47015b7d 100644 --- a/tests/override.test.ts +++ b/tests/override.test.ts @@ -61,19 +61,25 @@ describe('Override Tests', (): void => { expect(output.warnings()).toHaveLength(0); }); - it('Override custom string map', (): void => { - const stringMap: PluginOptions['stringMap'] = [ - {search: 'left', replace: 'right'}, - {search: 'ltr', replace: 'rtl'} - ]; - const options: PluginOptions = { ...baseOptions, stringMap }; + it('Override custom ltrPrefix and rtlPrefix properties as arrays', (): void => { + const options: PluginOptions = { ...baseOptions, ltrPrefix: ['.ltr', '.left-to-right'], rtlPrefix: ['.rtl', '.right-to-left'] }; const output = postcss([postcssRTLCSS(options)]).process(input); expect(output.css).toMatchSnapshot(); expect(output.warnings()).toHaveLength(0); }); - it('Override custom ltrPrefix and rtlPrefix properties as arrays', (): void => { - const options: PluginOptions = { ...baseOptions, ltrPrefix: ['.ltr', '.left-to-right'], rtlPrefix: ['.rtl', '.right-to-left'] }; + it('Override custom ltrPrefix, rtlPrefix, and bothPrefix properties as arrays and processUrls: true', (): void => { + const options: PluginOptions = { ...baseOptions, ltrPrefix: ['.ltr', '.left-to-right'], rtlPrefix: ['.rtl', '.right-to-left'], bothPrefix: ['.ltr', '.left-to-right', '.rtl', '.right-to-left'], processUrls: true }; + const output = postcss([postcssRTLCSS(options)]).process(input); + expect(output.css).toMatchSnapshot(); + expect(output.warnings()).toHaveLength(0); + }); + + it('Override custom string map and processUrls: true', (): void => { + const stringMap: PluginOptions['stringMap'] = [ + {search: 'left', replace: 'right'} + ]; + const options: PluginOptions = { ...baseOptions, processUrls: true, stringMap }; const output = postcss([postcssRTLCSS(options)]).process(input); expect(output.css).toMatchSnapshot(); expect(output.warnings()).toHaveLength(0); diff --git a/tsconfig.json b/tsconfig.json index b9568818..4b999598 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "target": "ES5", "moduleResolution": "node", "esModuleInterop": true, + "resolveJsonModule": true, "declaration": true, "noImplicitAny": true, "removeComments": true, @@ -13,10 +14,11 @@ "@types": ["@types"], "@constants": ["constants"], "@parsers/*": ["parsers/*"], - "@utilities/*": ["utilities/*"] + "@utilities/*": ["utilities/*"], + "@data/*": ["data/*"] } }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "src/**/*.json"], "exclude": ["node_modules"] } \ No newline at end of file