Skip to content

Commit

Permalink
feat: conditional rule precedence management
Browse files Browse the repository at this point in the history
  • Loading branch information
kripod committed Jul 23, 2020
1 parent 44e5859 commit 7ecaaa2
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 22 deletions.
28 changes: 28 additions & 0 deletions packages/example-gatsby/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ export default function IndexPage(): JSX.Element {
This is some long dummy text to demonstrate the styling capabilities
provided by the underlying library.
</p>

<div
className={css({
"@media": {
"(min-width: 2px)": { color: "green" },
},
})}
/>
<p
className={css({
"@media": {
"(min-width: 1px)": { color: "red" },
"(min-width: 2px)": { color: "green" },
},
})}
>
Conditional rule precedence management test #1, should be green
</p>
<p
className={css({
color: "red",
"@media": {
"(min-width: 2px)": { color: "green" },
},
})}
>
Conditional rule precedence management test #2, should be green
</p>
</React.StrictMode>
);
}
6 changes: 5 additions & 1 deletion packages/otion/src/createInstance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ test("number of precedence groups is correct", () => {
}),
) + 1;

const conditionalPrecedenceGroupExistenceMultiplier = 2;

expect(PRECEDENCE_GROUP_COUNT).toEqual(
pseudoClassPrecedenceGroupCount * propertyPrecedenceGroupCount,
pseudoClassPrecedenceGroupCount *
propertyPrecedenceGroupCount *
conditionalPrecedenceGroupExistenceMultiplier,
);
});
77 changes: 56 additions & 21 deletions packages/otion/src/createInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "./pseudos";

const MAX_CLASS_NAME_LENGTH = 9;
export const PRECEDENCE_GROUP_COUNT = 36;
export const PRECEDENCE_GROUP_COUNT = 72;

function toHyphenLower(match: string): string {
return `-${match.toLowerCase()}`;
Expand Down Expand Up @@ -163,6 +163,7 @@ export function createInstance(): OtionInstance {
rules: ScopedCSSRules,
cssTextHead: string,
cssTextTail: string,
maxPrecedingConditionalRuleIndexesByPrecedenceGroup: Uint16Array,
classSelectorStartIndex?: number,
): string {
let classNames = "";
Expand All @@ -181,8 +182,11 @@ export function createInstance(): OtionInstance {
const property = key.replace(/^ms|[A-Z]/g, toHyphenLower);
const declarations = serializeDeclarationList(property, value);
const className = `_${hash(cssTextHead + declarations)}`;
const isConditionalRule = cssTextTail;

if (!ruleIndexesByIdentName.has(className)) {
let ruleIndex = ruleIndexesByIdentName.get(className);

if (ruleIndex == null || isConditionalRule) {
// The property's baseline precedence is based on dash (`-`) counting
const unprefixedProperty =
property[0] !== "-"
Expand All @@ -205,34 +209,59 @@ export function createInstance(): OtionInstance {
}

// Pseudo-classes also have an impact on rule precedence
const conditionalPrecedenceGroupExistenceMultiplier = 2;
precedence *=
(classSelectorStartIndex != null &&
conditionalPrecedenceGroupExistenceMultiplier *
((classSelectorStartIndex != null &&
PRECEDENCES_BY_PSEUDO_CLASS.get(
cssTextHead.slice(
// This part uniquely identifies a pseudo selector
classSelectorStartIndex + 3,
classSelectorStartIndex + 8,
),
)) ||
PSEUDO_CLASS_PRECEDENCE_GROUP_COUNT + 1;

const scopeSelector = `.${className}`;
injector.insert(
`${
cssTextHead.slice(0, classSelectorStartIndex) +
scopeSelector +
(classSelectorStartIndex != null
? `${cssTextHead.slice(classSelectorStartIndex)}{`
: "{")
}${declarations}}${cssTextTail}`,
nextRuleIndexesByPrecedenceGroup[precedence],
);
PSEUDO_CLASS_PRECEDENCE_GROUP_COUNT + 1);

for (let i = precedence; i <= PRECEDENCE_GROUP_COUNT; ++i) {
++nextRuleIndexesByPrecedenceGroup[i];
}
// Conditional rules should take precedence over non-conditionals
precedence += +!!isConditionalRule;

if (
ruleIndex == null ||
// Re-insert conditional rule if necessary to fix CSS source order
maxPrecedingConditionalRuleIndexesByPrecedenceGroup[precedence] >
ruleIndex
) {
const scopeSelector = `.${className}`;
injector.insert(
`${
cssTextHead.slice(0, classSelectorStartIndex) +
scopeSelector +
(classSelectorStartIndex != null
? `${cssTextHead.slice(classSelectorStartIndex)}{`
: "{")
}${declarations}}${cssTextTail}`,
nextRuleIndexesByPrecedenceGroup[precedence],
);

for (let i = precedence; i <= PRECEDENCE_GROUP_COUNT; ++i) {
++nextRuleIndexesByPrecedenceGroup[i];
}

ruleIndexesByIdentName.set(className, ruleIndexesByIdentName.size);
ruleIndex = ruleIndexesByIdentName.size;
ruleIndexesByIdentName.set(className, ruleIndex);

if (isConditionalRule) {
// eslint-disable-next-line no-param-reassign
maxPrecedingConditionalRuleIndexesByPrecedenceGroup[
precedence
] = Math.max(
maxPrecedingConditionalRuleIndexesByPrecedenceGroup[
precedence
],
ruleIndex,
);
}
}
}

classNames += ` ${className}`;
Expand Down Expand Up @@ -268,6 +297,7 @@ export function createInstance(): OtionInstance {
value as ScopedCSSRules,
cssTextHead + parentRuleHead,
parentRuleTail + cssTextTail,
maxPrecedingConditionalRuleIndexesByPrecedenceGroup,
scopeClassSelectorStartIndex,
);
},
Expand Down Expand Up @@ -328,7 +358,12 @@ export function createInstance(): OtionInstance {
if (isDev) checkSetup();

// The leading white space character gets removed
return decomposeToClassNames(rules, "", "").slice(1);
return decomposeToClassNames(
rules,
"",
"",
new Uint16Array(PRECEDENCE_GROUP_COUNT),
).slice(1);
},

keyframes(rules): { toString(): string } {
Expand Down

0 comments on commit 7ecaaa2

Please sign in to comment.