Skip to content

Commit

Permalink
fix: copy mirror frame styling (#15)
Browse files Browse the repository at this point in the history
* fix: copy frame style

* test: confirm frame copies style

* fix: compare specificity for selector groups

* test: concatenated class names
  • Loading branch information
iamogbz authored Mar 23, 2020
1 parent c992f6e commit 88c8281
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 27 deletions.
2 changes: 1 addition & 1 deletion demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "./styles.css";

export default function App(): JSX.Element {
const [usingPortal, setUsingPortal] = React.useState(false);
const [ref, reflection] = useMirror();
const [ref, reflection] = useMirror({ className: "Frame" });
return (
<div className="App">
<h1>React Mirror Demo</h1>
Expand Down
2 changes: 1 addition & 1 deletion demo/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.App {
.App, .Frame {
font-family: sans-serif;
text-align: center;
}
Expand Down
24 changes: 18 additions & 6 deletions src/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,35 @@ function randomString(length: number): string {
return str;
}

function copyStyles(sourceElt: HTMLElement, targetElt: HTMLElement): void {
export function copyStyles(
sourceElt: HTMLElement,
targetElt: HTMLElement,
): void {
const styleDecls: { [key: string]: CSSStyleDeclaration } = {};
const pseudoRegex = /:(:?)[a-z-]+/g;
for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i] as CSSStyleSheet;
const rules = styleSheet.rules || styleSheet.cssRules;
for (let j = 0; j < rules.length; j++) {
const rule = rules[j] as CSSStyleRule;
// also match pseudo selectors
const selector = rule.selectorText?.replace(pseudoRegex, "");
if (sourceElt?.matches(selector)) {
styleDecls[rule.selectorText] = rule.style;
// test each selector in a group separately
// accounts for bug with specificity library where
// `.classA, .classB` fails when compared with `.classB`
// https://github.com/keeganstreet/specificity/issues/4
for (const selectorText of rule.selectorText
.split(",")
.map(s => s.trim())) {
// also match pseudo selectors
const selector = selectorText?.replace(pseudoRegex, "");
if (sourceElt?.matches(selector)) {
styleDecls[selectorText] = rule.style;
}
}
}
}
// ensures only the cloned styles are applied to element
targetElt.className = `_${randomString(7)}`;
const singleClassName = targetElt.className.replace(" ", "-");
targetElt.className = `${singleClassName}_${randomString(7)}`;
// style element used for transfering pseudo styles
const styleElt = document.createElement("style");
styleElt.type = "text/css";
Expand Down
6 changes: 5 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import * as ReactDOM from "react-dom";

import { deepCloneWithStyles } from "./clone";
import { deepCloneWithStyles, copyStyles } from "./clone";

type MirrorPropsType = {
className?: string;
Expand All @@ -25,6 +25,10 @@ export function Mirror({ className, reflect }: MirrorPropsType): JSX.Element {
} else {
frame.appendChild(reflection);
}
while (frame.firstChild !== frame.lastChild) {
frame.removeChild(frame.lastChild);
}
copyStyles(frame, frame);
}, [frame, real]);
// start of the reflection
React.useEffect(update, [update]);
Expand Down
25 changes: 17 additions & 8 deletions tests/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,55 @@ exports[`Component renders reflection with styles 1`] = `
<body>
<div>
<div
class="classOne"
class="class1 one"
>
<span
class="classTwo"
class="class2 two"
/>
</div>
<p
class="classThree"
class="class3 three"
>
Mock text node
</p>
</div>
<div>
<div>
<div
class="mirrorFrame_4fzzzxj"
style="font-family: \\"san-serif\\"; font-size: 1.2em;"
>
<div
class="_4fzzzxj"
style="pointer-events: none;"
>
<div
class="_4fzzzxj"
class="class1-one_4fzzzxj"
style="height: 10px; pointer-events: none;"
>
<span
class="_4fzzzxj"
class="class2-two_4fzzzxj"
style="font-size: 1.3em; display: block; width: 20px; margin: 0px auto; pointer-events: none;"
/>
</div>
<p
class="_4fzzzxj"
class="class3-three_4fzzzxj"
style="pointer-events: none;"
>
<style
type="text/css"
>
._4fzzzxj::after { content: ''; background: red; width: 5px; height: 5px; }
.class3-three_4fzzzxj::after { content: ''; background: red; width: 5px; height: 5px; }
</style>
Mock text node
</p>
</div>
<style
type="text/css"
>
.mirrorFrame_4fzzzxj::before { content: 'mock text'; position: absolute; }
</style>
</div>
</div>
</body>
Expand Down
25 changes: 15 additions & 10 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,30 @@ describe("Component", (): void => {
const domStyle = document.createElement("style");
document.head.appendChild(domStyle);
domStyle.innerHTML = `
body {
body, .mirrorFrame {
font-family: "san-serif";
font-size: 1.2em;
}
.classOne {
.mirrorFrame::before {
content: 'mock text';
position: absolute;
}
.class1.one, .class2.two {
height: 10px;
}
.classTwo {
.class2.two {
font-size: 1.3em;
display: block;
width: 40px;
margin: 0 auto;
}
.classThree::after {
.class3.three::after {
content: '';
background: red;
width: 5px;
height: 5px;
}
.classOne .classTwo {
.class1.one .class2.two {
width: 20px;
}
`;
Expand All @@ -68,24 +72,25 @@ describe("Component", (): void => {
document.body.appendChild(domNode);
const node1 = document.createElement("div");
domNode.appendChild(node1);
node1.className = "classOne";
node1.className = "class1 one";
const node2 = document.createElement("span");
node1.appendChild(node2);
node2.className = "classTwo";
node2.className = "class2 two";
/** render mirror into detached node */
const spyReplace = jest.spyOn(HTMLElement.prototype, "replaceChild");
const baseElement = document.createElement("div");
render(<Mirror reflect={domNode} />, { baseElement });
const renderProps = { className: "mirrorFrame", reflect: domNode };
render(<Mirror {...renderProps} />, { baseElement });
/** add more nodes and check that they are inserted */
expect(spyReplace).toHaveBeenCalledTimes(0);
const node3 = document.createElement("p");
domNode.appendChild(node3);
node3.innerHTML = "Mock text node";
node3.className = "classThree";
node3.className = "class3 three";
await new Promise(resolve => setTimeout(resolve));
expect(spyReplace).toHaveBeenCalledTimes(1);
/** mirror nodes and check results */
const result = render(<Mirror reflect={domNode} />);
const result = render(<Mirror {...renderProps} />);
expect(result.baseElement).toMatchSnapshot();
/** clean up */
domStyle.remove();
Expand Down

0 comments on commit 88c8281

Please sign in to comment.