Skip to content

Commit

Permalink
feat: implement auto-frame-component
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Feb 27, 2024
1 parent acede7f commit 06b9e67
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 8 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
# React lib
# auto-frame-component

An iframe component that automatically syncs styles from the host.

An implementation of [react-frame-component](https://github.com/ryanseddon/react-frame-component).

## Quick start

```sh
npm i @measured/auto-frame-component
```

```jsx
import AutoFrame from "@measured/auto-frame-component";

export function Page() {
return (
<AutoFrame>
{/* Hero class exists on parent */}
<div className="Hero">Hello, world</div>
</AutoFrame>
);
}
```

## API

Shares an API with [react-frame-component](https://github.com/ryanseddon/react-frame-component).

## License

MIT © [Measured Corporation Ltd](https://measured.co)
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./src/AutoFrameComponent";
5 changes: 0 additions & 5 deletions index.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"eslint": "^7.32.0",
"object-hash": "^3.0.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "5.2.6",
"tsup": "^6.7.0",
"typescript": "^4.5.2"
},
Expand Down
129 changes: 129 additions & 0 deletions src/AutoFrameComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { ReactNode, useLayoutEffect } from "react";
import Frame, { FrameComponentProps, useFrame } from "react-frame-component";
import hash from "object-hash";

const styleSelector = 'style, link[as="style"], link[rel="stylesheet"]';

const collectStyles = (doc: Document) => {
const collected: Node[] = [];

doc.head.querySelectorAll(styleSelector).forEach((style) => {
collected.push(style);
});

return collected;
};

const CopyHostStyles = ({ children }: { children: ReactNode }) => {
const { document: doc, window: win } = useFrame();

useLayoutEffect(() => {
if (!win) {
return () => {};
}

const add = (el: HTMLElement, contentHash: string = hash(el.outerHTML)) => {
if (doc?.head.querySelector(`[data-content-hash="${contentHash}"]`)) {
console.log(
`Style tag with same content (${contentHash}) already exists, skpping...`
);

return;
}

console.log(
`Added style node with content hash ${contentHash} ${el.innerHTML}`
);

const frameStyles = el.cloneNode(true);

(frameStyles as HTMLElement).setAttribute(
"data-content-hash",
contentHash
);

doc?.head.append(frameStyles);
};

const remove = (el: HTMLElement) => {
const contentHash = hash(el.textContent);
const frameStyles = el.cloneNode(true);

(frameStyles as HTMLElement).setAttribute(
"data-content-hash",
contentHash
);

console.log(
`Removing node with content hash ${contentHash} as no longer present in parent`
);

doc?.head.querySelector(`[data-content-hash="${contentHash}"]`)?.remove();
};

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
mutation.addedNodes.forEach((node) => {
if (
node.nodeType === Node.TEXT_NODE ||
node.nodeType === Node.ELEMENT_NODE
) {
const el =
node.nodeType === Node.TEXT_NODE
? node.parentElement
: (node as HTMLElement);

if (el && el.matches(styleSelector)) {
add(el);
}
}
});

mutation.removedNodes.forEach((node) => {
if (
node.nodeType === Node.TEXT_NODE ||
node.nodeType === Node.ELEMENT_NODE
) {
const el =
node.nodeType === Node.TEXT_NODE
? node.parentElement
: (node as HTMLElement);

if (el && el.matches(styleSelector)) {
remove(el);
}
}
});
}
});
});

const parentDocument = win!.parent.document;

observer.observe(parentDocument.head, { childList: true, subtree: true });

const collectedStyles = collectStyles(parentDocument);

// Add new style tags
collectedStyles.forEach((styleNode) => {
add(styleNode as HTMLElement);
});

return () => {
observer.disconnect();
};
}, []);

return <>{children}</>;
};

export default React.forwardRef<HTMLIFrameElement, FrameComponentProps>(
function ({ children, ...props }: FrameComponentProps, ref) {
return (
<Frame {...props} ref={ref}>
<CopyHostStyles>{children}</CopyHostStyles>
</Frame>
);
}
);
28 changes: 26 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==

loose-envify@^1.1.0:
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
Expand Down Expand Up @@ -1114,11 +1114,16 @@ npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"

object-assign@^4.0.1:
object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==

object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==

once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
Expand Down Expand Up @@ -1203,6 +1208,15 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==

prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"

punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
Expand All @@ -1221,6 +1235,16 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"

[email protected]:
version "5.2.6"
resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-5.2.6.tgz#0d9991d251ff1f7177479d8f370deea06b824b79"
integrity sha512-CwkEM5VSt6nFwZ1Op8hi3JB5rPseZlmnp5CGiismVTauE6S4Jsc4TNMlT0O7Cts4WgIC3ZBAQ2p1Mm9XgLbj+w==

react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==

react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Expand Down

0 comments on commit 06b9e67

Please sign in to comment.