generated from iamogbz/node-js-boilerplate
-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Reflection node in iFrame to isolate styles (#780)
* chore: update package deps * chore: update package deps * feat: add Reflection node * refactor: rename useRef to useCallbackRef * refactor: comments and code * refactor: frame prop types and dom utils * fix: keep frame size synced with reflected node * refactor: split frame and dimensions into component and hooks respectively * fix: keep framed styles in sync with parent document * chore: remove unused style components * fix: include bounds in frame dimensions * test: package entry exports * test: add tests for Element component * test: add tests for Frame component * test: add tests for Mirror component * test: add tests for Reflection component * test: add test for useDimensions hook * test: add tests for useMirror hook * test: add test for useObserver hook * test: add tests for useRef hook * test: add tests for useRenderTrigger hook * test: address missing coverage * chore: update id for parent document mirror styles
- Loading branch information
Showing
40 changed files
with
1,364 additions
and
453 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export default class DOMRect { | ||
bottom = 0; | ||
left = 0; | ||
right = 0; | ||
top = 0; | ||
constructor( | ||
// eslint-disable-next-line no-unused-vars | ||
public x = 0, | ||
// eslint-disable-next-line no-unused-vars | ||
public y = 0, | ||
// eslint-disable-next-line no-unused-vars | ||
public width = 0, | ||
// eslint-disable-next-line no-unused-vars | ||
public height = 0, | ||
) { | ||
this.left = x; | ||
this.top = y; | ||
this.right = x + width; | ||
this.bottom = y + height; | ||
} | ||
} | ||
|
||
Object.assign(window, { DOMRect }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Range.prototype.getBoundingClientRect = () => new DOMRect(); | ||
/* | ||
Range.prototype.getClientRects = function () { | ||
const clientRects = [this.getBoundingClientRect()]; | ||
return { | ||
item: (i: number) => clientRects[i] ?? null, | ||
length: 1, | ||
[Symbol.iterator]: () => clientRects.values(), | ||
}; | ||
}; | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export default class ResizeObserver { | ||
observe() { | ||
// do nothing | ||
} | ||
unobserve() { | ||
// do nothing | ||
} | ||
disconnect() { | ||
// do nothing | ||
} | ||
} | ||
|
||
Object.assign(window, { ResizeObserver }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { configure } from "@testing-library/react"; | ||
import "./mocks/DOMRect"; | ||
import "./mocks/Range"; | ||
import "./mocks/ResizeObserver"; | ||
|
||
configure({ testIdAttribute: "data-test-id" }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
font-family: sans-serif; | ||
text-align: center; | ||
border: none; | ||
padding: 0; | ||
margin: 0; | ||
} | ||
|
||
.Input, .Button { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
export function addDomNode() { | ||
const domNode = document.createElement("div"); | ||
document.body.appendChild(domNode); | ||
const node0 = document.createComment("comment node"); | ||
document.appendChild(node0); | ||
const node1 = document.createElement("div"); | ||
domNode.appendChild(node1); | ||
node1.className = "class1 one"; | ||
node1.setAttribute("attr", "[value"); | ||
const node2 = document.createElement("input"); | ||
node1.appendChild(node2); | ||
node2.className = "class2 two"; | ||
node2.value = "input-value"; | ||
const node3 = document.createTextNode("text content"); | ||
domNode.appendChild(node3); | ||
return domNode; | ||
} | ||
|
||
export function addDomStyles() { | ||
const domStyle = document.createElement("style"); | ||
document.head.appendChild(domStyle); | ||
domStyle.innerHTML = ` | ||
@charset "utf-8"; | ||
@font-face { | ||
font-family: "Open Sans"; | ||
} | ||
body, .mirrorFrame:not(*) { | ||
font-family: "san-serif"; | ||
font-size: 1.2em; | ||
} | ||
:is(::after), ::before { | ||
position: absolute; | ||
} | ||
:where(::slotted(span)) { | ||
border: none; | ||
} | ||
::after { | ||
content: ''; | ||
} | ||
.mirrorFrame::before { | ||
content: 'mock text'; | ||
} | ||
.class1.one, .class2.two { | ||
height: 10px; | ||
} | ||
.class2.two { | ||
font-size: 1.3em; | ||
display: block; | ||
width: 40px; | ||
margin: 0 auto; | ||
} | ||
.class3.three::after { | ||
background: red; | ||
width: 5px; | ||
height: 5px; | ||
} | ||
.class1.one[attr^="[val"] .class2.two { | ||
width: 20px; | ||
} | ||
`; | ||
return domStyle; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
describe("Entry", () => { | ||
it("exports expected modules", async () => { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
expect(require("../")).toMatchInlineSnapshot(` | ||
Object { | ||
"Frame": [Function], | ||
"Mirror": [Function], | ||
"Reflection": [Function], | ||
"useMirror": [Function], | ||
} | ||
`); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as React from "react"; | ||
|
||
export type ElementProps<T extends string> = | ||
T extends keyof JSX.IntrinsicElements | ||
? JSX.IntrinsicElements[T] | ||
: JSX.IntrinsicElements[keyof JSX.IntrinsicElements]; | ||
|
||
type DOMElement<T extends string> = T extends keyof HTMLElementTagNameMap | ||
? HTMLElementTagNameMap[T] | ||
: never; | ||
|
||
type DOMElementProps<T extends string> = { | ||
tagName: T; | ||
domRef?: React.Ref<DOMElement<T>>; | ||
} & ElementProps<T>; | ||
|
||
export function Element<T extends string>({ | ||
children, | ||
domRef, | ||
tagName, | ||
...props | ||
}: DOMElementProps<T>) { | ||
return React.createElement( | ||
tagName.toLowerCase(), | ||
{ ...props, ref: domRef }, | ||
...(Array.isArray(children) ? children : [children]), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import * as React from "react"; | ||
import { getAllStyleRules } from "../utils/dom"; | ||
import { useObserver } from "../hooks/useObserver"; | ||
import { IFrame, IFrameProps } from "./IFrame"; | ||
import { Style } from "./Styles"; | ||
|
||
export type FrameProps = Omit<IFrameProps, "getMountNode">; | ||
|
||
/** | ||
* Used to wrap and isolate reflection from rest of document | ||
*/ | ||
export function Frame({ children, ...frameProps }: FrameProps) { | ||
return ( | ||
<IFrame {...frameProps}> | ||
<ResetStyle /> | ||
<DocumentStyle /> | ||
{children} | ||
</IFrame> | ||
); | ||
} | ||
|
||
/** | ||
* Copy of the current document style sheets to be used in framing | ||
*/ | ||
function DocumentStyle() { | ||
const [rules, setRules] = React.useState(getAllStyleRules); | ||
|
||
const onUpdate = React.useCallback( | ||
function updateRules() { | ||
const newRules = getAllStyleRules(); | ||
if (JSON.stringify(rules) === JSON.stringify(newRules)) return; | ||
setRules(newRules); | ||
}, | ||
[rules], | ||
); | ||
|
||
useObserver({ | ||
ObserverClass: MutationObserver, | ||
onUpdate, | ||
initOptions: { | ||
attributeFilter: ["class"], | ||
attributes: true, | ||
characterData: true, | ||
childList: true, | ||
subtree: true, | ||
}, | ||
target: window.document, | ||
}); | ||
|
||
return <Style id="parent-document-mirror-stylesheets" rules={rules} />; | ||
} | ||
|
||
/** | ||
* https://www.webfx.com/blog/web-design/should-you-reset-your-css | ||
*/ | ||
function ResetStyle() { | ||
return ( | ||
<link | ||
rel="stylesheet" | ||
href="https://necolas.github.io/normalize.css/8.0.1/normalize.css" | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from "react"; | ||
import { usePortal } from "../hooks/usePortal"; | ||
import { useCallbackRef } from "../hooks/useRef"; | ||
import { ElementProps } from "./Element"; | ||
|
||
export interface IFrameProps extends ElementProps<"iframe"> { | ||
/** Get the iframe content node the react children will be rendered into */ | ||
getMountNode?: (_?: Window) => Element | undefined; | ||
} | ||
|
||
export function IFrame({ | ||
children, | ||
getMountNode = (window) => window?.document?.body, | ||
...props | ||
}: IFrameProps) { | ||
const [iframe, ref] = useCallbackRef<HTMLIFrameElement>(); | ||
const mountNode = getMountNode(iframe?.contentWindow ?? undefined); | ||
const portal = usePortal({ source: children, target: mountNode }); | ||
|
||
return ( | ||
<iframe {...props} ref={ref}> | ||
{portal} | ||
</iframe> | ||
); | ||
} |
Oops, something went wrong.