├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
├── bundle.js
└── index.html
├── package-lock.json
├── package.json
├── src
├── docs
│ ├── demos
│ │ ├── Basic.js
│ │ ├── BasicFunctional.js
│ │ ├── Constructable.js
│ │ ├── Context.js
│ │ ├── DelegatesFocus.js
│ │ ├── Dynamic.js
│ │ ├── Select.js
│ │ └── Slots.js
│ ├── index.html
│ ├── index.js
│ ├── pages
│ │ ├── API.js
│ │ ├── Basic.js
│ │ ├── Constructable.js
│ │ ├── Declarative.js
│ │ ├── DelegatesFocus.js
│ │ ├── DynamicStyles.js
│ │ ├── Introduction.js
│ │ └── Slots.js
│ ├── styles.css
│ └── utils
│ │ ├── A.js
│ │ ├── CodeBlock.js
│ │ └── NotSupported.js
└── lib
│ ├── ReactShadowRoot.js
│ └── index.js
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-object-rest-spread",
5 | "@babel/plugin-proposal-class-properties"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 | /lib
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs
2 | src
3 | examples
4 | .babelrc
5 | webpack.config.js
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 apearce
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-shadow-root
2 | Lets you add a shadow root to React components allowing you to use the shadow DOM. This provides scoped CSS and includes support for [constructable](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) [stylesheets](https://wicg.github.io/construct-stylesheets).
3 |
4 | ## Installation
5 | `npm install --save react-shadow-root`
6 |
7 | ## Examples
8 | https://apearce.github.io/react-shadow-root/
9 |
10 | ## ReactShadowRoot
11 | ### Usage
12 | ```jsx
13 | import React from 'react';
14 | import ReactShadowRoot from 'react-shadow-root';
15 |
16 | class ShadowCounter extends React.Component {
17 | state = { cnt: 0 };
18 |
19 | increment = () => {
20 | this.setState({
21 | cnt: this.state.cnt + 1
22 | });
23 | }
24 |
25 | render() {
26 | const style = `span {
27 | background-color: #333;
28 | border-radius: 3px;
29 | color: #fff;
30 | padding: 1px 5px;
31 | }
32 | button {
33 | background-color: #fff;
34 | border: 1px solid currentColor;
35 | border-radius: 3px;
36 | color: #333;
37 | cursor: pointer;
38 | outline: 0;
39 | }
40 | button:active {
41 | background-color: #333;
42 | color: #fff;
43 | }`;
44 |
45 | return (
46 |
{/* The shadow root will be attached to this DIV */}
47 |
48 |
49 | {this.state.cnt} Click Me
50 |
51 |
52 | );
53 | }
54 | }
55 | ```
56 | When the shadow root is created on its parent element, all children are copied into the shadow DOM. Styles in the shadow DOM are automatically scoped. You can inspect the element to confirm. [Slots](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) work as expected; just be sure to add `{this.props.children}` _after_ the closing `ReactShadowRoot` tag.
57 |
58 | ### Static Properties
59 | | Name | Description |
60 | |------|-------------|
61 | | `constructableStylesheetsSupported` | A boolean telling you if constructable stylesheets are supported by the browser. |
62 | | `constructibleStylesheetsSupported` | An alias of `constructableStylesheetsSupported` using the ['correct' spelling](https://github.com/WICG/construct-stylesheets/issues/90). |
63 | | `shadowRootSupported` | A boolean telling you if attaching a shadow root is supported by the _browser_, not the element. |
64 |
65 | ### Props
66 | | Prop | Type | Values | Default | Description |
67 | |------|------|--------|---------|-------------|
68 | | `declarative` | `Boolean` | `true` or `false` | `false` | Creates a [Declarative Shadow Root](https://apearce.github.io/react-shadow-root/#declarative) |
69 | | `delegatesFocus` | `Boolean` | `true` or `false` | `false` | Expands the focus behavior of elements within the shadow DOM. Click [here](https://apearce.github.io/react-shadow-root/#delegates-focus) for more information. |
70 | | `mode` | `String` | `open` or `closed` | `open` | Sets the [mode](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode) of the shadow root. |
71 | | `stylesheets` | `Array` | `arrayOf(CSSStyleSheet)` | optional | Takes an array of [CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet) objects for constructable stylesheets. |
72 |
73 | ## Notes
74 | - A minimum of React 16 is required.
75 | - TypeScript definitions included and should not require configuration
76 | - Works in all modern browsers except non-Chromium Edge. Click [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#Browser_compatibility) for current browser support.
77 | - Not all HTML elements allow you to attach a shadow root. Click [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#Elements_you_can_attach_a_shadow_to) for more information.
78 | - It has been tested with the Context API introduced in React 16.3.0 and it worked fine. It has not been tested with the previous API.
79 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React Shadow Root Examples
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-shadow-root",
3 | "version": "6.2.0",
4 | "description": "Adds a shadow root to React components",
5 | "main": "./lib/index.js",
6 | "types": "./lib/index.d.ts",
7 | "license": "MIT",
8 | "scripts": {
9 | "dev": "concurrently \"npm run build:watch\" \"npm run docs\"",
10 | "build": "tsc && babel src/lib -d lib --copy-files",
11 | "build:watch": "babel src/lib -w -d lib --copy-files",
12 | "docs": "webpack-dev-server --mode development",
13 | "docs:prod": "webpack --mode production"
14 | },
15 | "peerDependencies": {
16 | "prop-types": ">=15.6.0",
17 | "react": ">=16.0.0",
18 | "react-dom": ">=16.0.0"
19 | },
20 | "devDependencies": {
21 | "@babel/cli": "^7.8.4",
22 | "@babel/core": "^7.9.0",
23 | "@babel/plugin-proposal-class-properties": "^7.8.3",
24 | "@babel/plugin-proposal-object-rest-spread": "^7.9.0",
25 | "@babel/preset-env": "^7.9.0",
26 | "@babel/preset-react": "^7.9.4",
27 | "@types/prop-types": "^15.7.4",
28 | "@types/react": "^17.0.19",
29 | "@types/react-dom": "^17.0.9",
30 | "babel-loader": "^8.1.0",
31 | "concurrently": "^3.5.1",
32 | "css-loader": "^3.4.2",
33 | "html-webpack-plugin": "^3.2.0",
34 | "path": "^0.12.7",
35 | "prop-types": "^15.6.0",
36 | "react": "^16.13.1",
37 | "react-dom": "^16.13.1",
38 | "react-syntax-highlighter": "^11.0.2",
39 | "style-loader": "^0.23.0",
40 | "typescript": "^4.4.2",
41 | "webpack": "^4.42.1",
42 | "webpack-cli": "^3.3.11",
43 | "webpack-dev-server": "^3.11.0"
44 | },
45 | "repository": {
46 | "type": "git",
47 | "url": "git+https://github.com/apearce/react-shadow-root.git"
48 | },
49 | "keywords": [
50 | "React",
51 | "Shadow DOM",
52 | "Shadow Root"
53 | ],
54 | "author": "Alan Pearce ",
55 | "bugs": {
56 | "url": "https://github.com/apearce/react-shadow-root/issues"
57 | },
58 | "homepage": "https://github.com/apearce/react-shadow-root#readme",
59 | "dependencies": {}
60 | }
61 |
--------------------------------------------------------------------------------
/src/docs/demos/Basic.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const styles = `:host {
5 | display: inline-flex;
6 | }
7 | span {
8 | background-color: #333;
9 | border-radius: 3px;
10 | color: #fff;
11 | padding: 1px 5px;
12 | }
13 | button {
14 | background-color: #fff;
15 | border: 1px solid currentColor;
16 | border-radius: 3px;
17 | cursor: pointer;
18 | outline: 0;
19 | }
20 | button:active {
21 | background-color: #333;
22 | color: #fff;
23 | }
24 | button,
25 | span {
26 | margin: 0 2px;
27 | }`;
28 |
29 | export default class extends React.Component {
30 | state = { cnt: 0 };
31 |
32 | increment = () => {
33 | this.setState({
34 | cnt: this.state.cnt + 1
35 | });
36 | }
37 |
38 | render() {
39 | return (
40 | {/* The shadow root will be attached to this element */}
41 |
42 |
43 | {this.state.cnt}
44 | Click Me
45 |
46 |
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/docs/demos/BasicFunctional.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const styles = `:host {
5 | display: inline-flex;
6 | }
7 | span {
8 | background-color: #333;
9 | border-radius: 3px;
10 | color: #fff;
11 | padding: 1px 5px;
12 | }
13 | button {
14 | background-color: #fff;
15 | border: 1px solid currentColor;
16 | border-radius: 3px;
17 | cursor: pointer;
18 | outline: 0;
19 | }
20 | button:active {
21 | background-color: #333;
22 | color: #fff;
23 | }
24 | button,
25 | span {
26 | margin: 0 2px;
27 | }`;
28 |
29 | export default function() {
30 | const [cnt, setCount] = useState(0);
31 |
32 | return (
33 | {/* The shadow root will be attached to this element */}
34 |
35 |
36 | {cnt}
37 | setCount(cnt + 1)}>Click Me
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/docs/demos/Constructable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const { constructableStylesheetsSupported } = ReactShadowRoot;
5 | const styles = `:host {
6 | display: inline-flex;
7 | flex-wrap: wrap;
8 | }
9 | span {
10 | background-color: #333;
11 | border-radius: 3px;
12 | color: #fff;
13 | padding: 1px 5px;
14 | }
15 | button {
16 | background-color: #fff;
17 | border: 1px solid currentColor;
18 | border-radius: 3px;
19 | cursor: pointer;
20 | outline: 0;
21 | }
22 | button:active {
23 | background-color: #333;
24 | color: #fff;
25 | }
26 | button,
27 | span {
28 | margin: 0 2px;
29 | }
30 | .fallback-message {
31 | background-color: transparent;
32 | color: #c00;
33 | }`;
34 |
35 | let sheet;
36 | let styleSheets;
37 |
38 | if (constructableStylesheetsSupported) {
39 | sheet = new CSSStyleSheet();
40 | sheet.replaceSync(styles);
41 | styleSheets = [sheet];
42 | }
43 |
44 | export default class extends React.Component {
45 | state = { cnt: 0 };
46 |
47 | increment = () => {
48 | this.setState({
49 | cnt: this.state.cnt + 1
50 | });
51 | }
52 |
53 | render() {
54 | return (
55 |
56 |
57 | {this.state.cnt}
58 | Click Me
59 | {!constructableStylesheetsSupported &&
60 | <>
61 |
62 | Your browser does not support constructable stylesheets. Using fallback.
63 |
64 |
65 | >
66 | }
67 |
68 |
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/docs/demos/Context.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const MyContext = React.createContext('Alan');
5 |
6 | class Bar extends React.Component {
7 | state = {cnt: 0};
8 | clicker = () => {
9 | this.setState({
10 | cnt: this.state.cnt + 1
11 | })
12 | }
13 | render() {
14 | return (
15 | {foo => {
16 | return {foo} {this.state.cnt} ;
17 | }}
18 | );
19 | }
20 | }
21 |
22 | class Foo extends React.Component {
23 | render() {
24 | return (
);
25 | }
26 | }
27 |
28 | class ContextTest extends React.Component {
29 | render() {
30 | return (
31 |
32 | );
33 | }
34 | }
35 |
36 | export default ContextTest;
37 |
--------------------------------------------------------------------------------
/src/docs/demos/DelegatesFocus.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | export default class extends React.Component {
5 | render() {
6 | const { delegatesFocus } = this.props;
7 | const style = `:host {
8 | background-color: #fff;
9 | border: 1px dotted black;
10 | display: flex;
11 | padding: 16px;
12 | }
13 | :focus {
14 | outline: 2px solid blue;
15 | }
16 | input {
17 | margin-left: 5px;
18 | width: 150px;
19 | }`;
20 |
21 | return (
22 |
23 |
24 |
25 | Clickable Shadow DOM text
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/docs/demos/Dynamic.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const { constructableStylesheetsSupported } = ReactShadowRoot;
5 | const colors = [
6 | 'black', 'red', 'rebeccapurple', 'blue', 'brown',
7 | 'lime', 'magenta', 'green', 'orange', 'teal'
8 | ];
9 | const styles = `:host {
10 | display: inline-flex;
11 | }
12 | span {
13 | background-color: var(--color);
14 | border-radius: 3px;
15 | color: #fff;
16 | padding: 1px 5px;
17 | }
18 | button {
19 | background-color: #fff;
20 | border: 1px solid var(--color);
21 | border-radius: 3px;
22 | color: var(--color);
23 | cursor: pointer;
24 | outline: 0;
25 | }
26 | button:active {
27 | background-color: var(--color);
28 | color: #fff;
29 | }
30 | button,
31 | span {
32 | margin: 0 2px;
33 | }`;
34 |
35 | let sheet;
36 | let styleSheets;
37 |
38 | if (constructableStylesheetsSupported) {
39 | sheet = new CSSStyleSheet();
40 | sheet.replaceSync(styles);
41 | styleSheets = [sheet];
42 | }
43 |
44 | export default class extends React.Component {
45 | state = { cnt: 0 };
46 |
47 | increment = () => {
48 | this.setState({
49 | cnt: this.state.cnt + 1
50 | });
51 | }
52 |
53 | render() {
54 | const { reverse } = this.props;
55 | const { cnt } = this.state;
56 | const dynamicStyles = `
57 | :host {
58 | flex-direction: ${reverse ? 'row-reverse' : 'row'};
59 | --color: ${colors[cnt % 10]};
60 | }
61 | `;
62 |
63 | return (
64 |
65 |
66 |
67 | {cnt}
68 | Click Me
69 | {!constructableStylesheetsSupported &&
70 |
71 | }
72 |
73 |
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/docs/demos/Select.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const styles = `
5 | :host {
6 | display: inline-block;
7 | height: 25px;
8 | position: relative;
9 | width: 150px;
10 | }
11 | div, ul {
12 | background-color: #fff;
13 | border: 1px solid #333;
14 | border-radius: 3px;
15 | box-sizing: border-box;
16 | }
17 | div {
18 | align-items: center;
19 | display: flex;
20 | height: 100%;
21 | }
22 | output {
23 | flex-grow: 1;
24 | overflow: hidden;
25 | padding: 0 5px;
26 | text-overflow: ellipsis;
27 | white-space: nowrap;
28 | }
29 | output.placeholder {
30 | opacity: .6;
31 | }
32 | button {
33 | background-color: #fff;
34 | border: 0;
35 | border-left: 1px solid #333;
36 | border-radius: 0 3px 3px 0;
37 | cursor: pointer;
38 | height: 100%;
39 | margin-left: auto;
40 | margin-right: 0;
41 | outline: 0;
42 | }
43 | button:hover {
44 | background-color: #333;
45 | color: #fff;
46 | }
47 | ul {
48 | list-style-type: none;
49 | margin: 0;
50 | margin-top: 1px;
51 | max-height: 135px;
52 | min-width: 100%;
53 | overflow-y: auto;
54 | padding: 0;
55 | position: absolute;
56 | }
57 | li {
58 | cursor: pointer;
59 | padding: 5px 10px;
60 | white-space: nowrap;
61 | }
62 | li:hover {
63 | background-color: #333;
64 | color: #fff;
65 | }
66 | `;
67 |
68 | function Option(props) {
69 | const {
70 | value,
71 | label,
72 | ...rest
73 | } = props;
74 |
75 | return ({label} );
76 | }
77 |
78 | class Select extends React.Component {
79 | state = {
80 | selected: {},
81 | visible: false
82 | };
83 |
84 | itemSelected = (e) => {
85 | const selected = {
86 | label: e.target.textContent,
87 | value: e.target.dataset.value
88 | };
89 |
90 | this.props.onClick(selected);
91 |
92 | this.setState({
93 | selected,
94 | visible: false
95 | });
96 | }
97 |
98 | toggle = () => {
99 | this.setState({
100 | visible: !this.state.visible
101 | });
102 | }
103 |
104 | render() {
105 | const { selected, visible } = this.state;
106 | const arrow = visible ? 9650 : 9660;
107 | const outputClass = !selected.label && {
108 | className: 'placeholder'
109 | };
110 | return (
111 |
112 |
113 |
114 | {selected.label || 'Select Something'}
115 | {String.fromCharCode(arrow)}
116 |
117 | {visible &&
118 | {this.props.children}
119 | }
120 |
121 |
122 |
123 | );
124 | }
125 | }
126 |
127 | export default class extends React.PureComponent {
128 | optionClicked(option) {
129 | console.log('Option Clicked', option);
130 | }
131 |
132 | render () {
133 | return (
134 |
135 |
136 |
137 |
138 |
139 | );
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/docs/demos/Slots.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactShadowRoot from "../../../lib";
3 |
4 | const { constructableStylesheetsSupported } = ReactShadowRoot;
5 | const styles = `
6 | :host { background-color: #fff; display:block; }
7 | details { font-family: "Open Sans Light",Helvetica,Arial; }
8 | .name { font-weight: bold; color: #217ac0; font-size: 120%; margin-right: 5px; }
9 | h4 { margin: 10px 0 -8px 0; }
10 | h4 span { background: #217ac0; padding: 2px 6px 2px 6px; }
11 | h4 span { border: 1px solid #cee9f9; border-radius: 4px; }
12 | h4 span { color: white; }
13 | .attributes { margin-left: 22px; font-size: 90%; }
14 | .attributes p { margin-left: 16px; font-style: italic; }
15 | `;
16 |
17 | let sheet;
18 | let styleSheets;
19 |
20 | if (constructableStylesheetsSupported) {
21 | sheet = new CSSStyleSheet();
22 | sheet.replaceSync(styles);
23 | styleSheets = [sheet];
24 | }
25 |
26 | class ElementDetails extends React.Component {
27 | render() {
28 | return (
29 |
30 |
31 |
32 |
33 | <NEED NAME >
34 | NEED DESCRIPTION
35 |
36 |
37 |
38 |
Attributes
39 |
None
40 |
41 |
42 |
43 | {!constructableStylesheetsSupported && }
44 |
45 | {this.props.children}
46 | );
47 | }
48 | }
49 |
50 | export default function SlotsDemo() {
51 | return (
52 | <>
53 |
54 | slot
55 | A placeholder inside a web
56 | component that users can fill with their own markup,
57 | with the effect of composing different DOM trees
58 | together.
59 |
60 | name
61 | The name of the slot.
62 |
63 |
64 |
65 | template
66 | A mechanism for holding client-
67 | side content that is not to be rendered when a page is
68 | loaded but may subsequently be instantiated during
69 | runtime using JavaScript.
70 |
71 | >
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React Shadow Root Examples
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/docs/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import APIPage from "./pages/API";
4 | import BasicPage from "./pages/Basic";
5 | import ConstructablePage from "./pages/Constructable";
6 | import DeclarativePage from "./pages/Declarative";
7 | import DelegatesFocusPage from "./pages/DelegatesFocus";
8 | import DynamicStylesPage from "./pages/DynamicStyles";
9 | import IntroductionPage from "./pages/Introduction";
10 | import SlotsPage from "./pages/Slots";
11 | import ReactShadowRoot from "../lib/ReactShadowRoot";
12 |
13 | import "./styles.css";
14 |
15 | const shadowRootSupported = ReactShadowRoot.shadowRootSupported;
16 |
17 | const subheaders = [
18 | "Styles go in and they don't come out",
19 | "You got your Web components in my React components!",
20 | "Put your style where your substance is"
21 | ];
22 |
23 | function Header() {
24 | const subheader = subheaders[Math.floor(Math.random() * subheaders.length)];
25 |
26 | return (<>
27 | react-shadow-root
28 | {subheader}
29 | >);
30 | }
31 |
32 | function Main() {
33 | return (<>
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | >);
43 | }
44 |
45 | class Nav extends React.PureComponent {
46 | componentDidMount() {
47 | const headings = document.querySelectorAll('article > h2[id]');
48 | const menu = document.createElement('ul');
49 |
50 | headings.forEach(h => {
51 | const menuItem = document.createElement('li');
52 | const menuLink = document.createElement('a');
53 | menuLink.textContent = h.textContent;
54 | menuLink.href = '#' + h.id;
55 | menuItem.appendChild(menuLink);
56 | menu.appendChild(menuItem);
57 | });
58 |
59 | document.querySelector('nav').prepend(menu);
60 | }
61 |
62 | render() {
63 | return {String.fromCharCode(9650)} ;
64 | }
65 | }
66 |
67 | ReactDOM.render(, document.querySelector("body > header"));
68 | ReactDOM.render( , document.querySelector("main > section"));
69 | ReactDOM.render( , document.querySelector("main > nav"));
70 |
--------------------------------------------------------------------------------
/src/docs/pages/API.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import A from "../utils/A";
3 |
4 | export default function(props) {
5 | return (
6 | API
7 |
8 | Static Properties
9 |
10 |
11 |
12 | Name
13 | Description
14 |
15 |
16 |
17 |
18 | constructableStylesheetsSupported
19 | A boolean telling you if constructable stylesheets are supported by the browser.
20 |
21 |
22 | constructibleStylesheetsSupported
23 | An alias of constructableStylesheetsSupported
using the 'correct' spelling .
24 |
25 |
26 | shadowRootSupported
27 | A boolean telling you if attaching a shadow root is supported by the browser , not the element.
28 |
29 |
30 |
31 |
32 |
33 | Props
34 |
35 |
36 |
37 | Prop
38 | Type
39 | Values
40 | Default
41 | Description
42 |
43 |
44 |
45 |
46 | delegatesFocus
47 | Boolean
48 | true
or false
49 | false
50 | Expands the focus behavior of elements within the shadow DOM. Click here for more information.
51 |
52 |
53 | mode
54 | String
55 | open
or closed
56 | open
57 | Sets the mode of the shadow root.
58 |
59 |
60 | stylesheets
61 | Array
62 | arrayOf(CSSStyleSheet)
63 | optional
64 | Takes an array of CSSStyleSheet objects for constructable stylesheets.
65 |
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/src/docs/pages/Basic.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Basic from "../demos/Basic";
3 | import BasicFunctional from "../demos/BasicFunctional";
4 | import A from "../utils/A";
5 | import { JSXBlock } from "../utils/CodeBlock";
6 | import NotSupported from "../utils/NotSupported";
7 |
8 | export default function(props) {
9 | const Component = props.shadowRootSupported ? Basic : NotSupported;
10 | const ComponentFunctional = props.shadowRootSupported ? BasicFunctional : NotSupported;
11 | const code = `
12 | import React from 'react';
13 | import ReactShadowRoot from 'react-shadow-root';
14 |
15 | const styles = \`:host {
16 | display: inline-flex;
17 | }
18 | span {
19 | background-color: #333;
20 | border-radius: 3px;
21 | color: #fff;
22 | padding: 1px 5px;
23 | }
24 | button {
25 | background-color: #fff;
26 | border: 1px solid currentColor;
27 | border-radius: 3px;
28 | cursor: pointer;
29 | outline: 0;
30 | }
31 | button:active {
32 | background-color: #333;
33 | color: #fff;
34 | }
35 | button,
36 | span {
37 | margin: 0 2px;
38 | }\`;
39 |
40 | export default class extends React.Component {
41 | state = { cnt: 0 };
42 |
43 | increment = () => {
44 | this.setState({
45 | cnt: this.state.cnt + 1
46 | });
47 | }
48 |
49 | render() {
50 | return (
51 | {/* The shadow root will be attached to this element */}
52 |
53 |
54 | {this.state.cnt}
55 | Click Me
56 |
57 |
58 | );
59 | }
60 | }
61 | `;
62 | const functionalCode = `
63 | import React, { useState } from 'react';
64 | ...
65 | export default function() {
66 | const [cnt, setCount] = useState(0);
67 |
68 | return (
69 | {/* The shadow root will be attached to this element */}
70 |
71 |
72 | {cnt}
73 | setCount(cnt + 1)}>Click Me
74 |
75 |
76 | );
77 | }
78 | `;
79 | const linkCode = `
80 |
81 |
82 | ...
83 |
84 | `;
85 |
86 | return (
87 | A Basic Example
88 | Here is a basic example. Be sure to look at the result of the example in your browser console to see that the content is actually in the shadow DOM.
89 |
90 | This is the output:
91 |
92 |
93 |
94 | It also works fine with functional components:
95 |
96 |
97 |
98 |
99 |
100 | You can also load an external stylesheet using a link tag the the styles will be loaded and scoped appropriately.
101 |
102 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/src/docs/pages/Constructable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Demo from "../demos/Constructable";
3 | import A from "../utils/A";
4 | import { JSXBlock } from "../utils/CodeBlock";
5 | import NotSupported from "../utils/NotSupported";
6 |
7 | export default function(props) {
8 | const Component = props.shadowRootSupported ? Demo : NotSupported;
9 | const code = `
10 | import React from 'react';
11 | import ReactShadowRoot from 'react-shadow-root';
12 |
13 | const { constructableStylesheetsSupported } = ReactShadowRoot;
14 | const styles = \`:host {
15 | display: inline-flex;
16 | flex-wrap: wrap;
17 | }
18 | span {
19 | background-color: #333;
20 | border-radius: 3px;
21 | color: #fff;
22 | padding: 1px 5px;
23 | }
24 | button {
25 | background-color: #fff;
26 | border: 1px solid currentColor;
27 | border-radius: 3px;
28 | cursor: pointer;
29 | outline: 0;
30 | }
31 | button:active {
32 | background-color: #333;
33 | color: #fff;
34 | }
35 | button,
36 | span {
37 | margin: 0 2px;
38 | }
39 | .fallback-message {
40 | background-color: transparent;
41 | color: #c00;
42 | }\`;
43 |
44 | let sheet;
45 | let styleSheets;
46 |
47 | if (constructableStylesheetsSupported) {
48 | sheet = new CSSStyleSheet();
49 | sheet.replaceSync(styles);
50 | styleSheets = [sheet];
51 | }
52 |
53 | export default class extends React.Component {
54 | state = { cnt: 0 };
55 |
56 | increment = () => {
57 | this.setState({
58 | cnt: this.state.cnt + 1
59 | });
60 | }
61 |
62 | render() {
63 | return (
64 |
65 |
66 | {this.state.cnt}
67 | Click Me
68 | {!constructableStylesheetsSupported &&
69 | <>
70 |
71 | Your browser does not support constructable stylesheets. Using fallback.
72 |
73 |
74 | >
75 | }
76 |
77 |
78 | );
79 | }
80 | }
81 | `;
82 |
83 | return (
84 | Constructable Stylesheets
85 |
86 |
87 | Constructable
88 |
89 | stylesheets
90 | allow you to create a stylesheet object that will be shared by any shadow root that adopts it.
91 | The styles do not appear in the shadow DOM but are still applied to the content.
92 |
93 |
94 | In the example below the stylesheet is created outside of the component definition as it's intended
95 | to be used by all instances of the component. Any change to the style rules will be applied
96 | to all components using the stylesheet.
97 |
98 |
99 |
100 |
101 |
102 |
103 | If you look at the result of the above example in your console in a supported browser,
104 | you will not see a style
tag but can still inspect the applied styles.
105 | The example also contains a fallback for browsers that don't support constructable stylesheets.
106 |
107 |
108 |
109 | If you do provide such a fallback, it is important to put it after any other styles in the shadow DOM,
110 | including any mechanism for letting the user inject their own. This is because constructable stylesheets
111 | are added after any styles already in the shadow DOM.
112 |
113 |
114 | );
115 | }
116 |
--------------------------------------------------------------------------------
/src/docs/pages/Declarative.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import A from "../utils/A";
3 | import { JSXBlock } from "../utils/CodeBlock";
4 | import NotSupported from "../utils/NotSupported";
5 |
6 | export default function(props) {
7 |
8 | return (
9 | Declarative Shadow Root
10 |
11 | Setting the declarative
prop to true
will result in
12 | a Declarative Shadow Root . The code will render
13 | a template
tag with a shadowroot
prop.
14 | This is useful for server side rendering because in supported browsers the
15 | shadow root will automatically be applied to the parent node of the component even without JavaScript enabled or when JavaScript may be slow to load.
16 | Obviously any functionality which requires JavaScript will not work when JavaScript is disabled,
17 | including constructable stylesheets and delegates focus ,
18 | but at least the component will still render the HTML and CSS. Browsers which do not support
19 | Declarative Shadow Root will still work as expected as long as JavaScript is enabled.
20 |
21 |
22 |
23 | When using this with JavaScript enabled, you may see a warning in the browser console saying something like
24 | Warning: Expected server HTML to contain a matching <template> in <div>.
25 | I think this happens because the browser removes the template
tag before
26 | hydration. While it seems to be harmless, you can suppress this by adding the suppressHydrationWarning
prop set
27 | to true
to the parent node of your shadow root. I'm open to suggestions for a better
28 | way to fix this.
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/docs/pages/DelegatesFocus.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Demo from "../demos/DelegatesFocus";
3 | import A from "../utils/A";
4 | import { CSSBlock, JSXBlock } from "../utils/CodeBlock";
5 | import NotSupported from "../utils/NotSupported";
6 |
7 | export default function(props) {
8 | const Component = props.shadowRootSupported ? Demo : NotSupported;
9 | const code = `
10 | export default class extends React.Component {
11 | render() {
12 | const { delegatesFocus } = this.props;
13 | const style = \`:host {
14 | background-color: #fff;
15 | border: 1px dotted black;
16 | display: flex;
17 | padding: 16px;
18 | }
19 | :focus {
20 | outline: 2px solid blue;
21 | }
22 | input {
23 | margin-left: 5px;
24 | width: 150px;
25 | }\`;
26 |
27 | return (
28 |
29 |
30 |
31 | Clickable Shadow DOM text
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 | `;
39 | const styleCode = `
40 | focus-demo:focus {
41 | outline: 2px solid red;
42 | }
43 | `;
44 |
45 | return (
46 | Delegates Focus
47 | This is the description for delegatesFocus
from MDN .
48 |
49 | A boolean that, when set to true, specifies behavior that mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus
styling.
50 |
51 |
52 | Click here for browser support and here for more info on delegatesFocus
.
53 | Below are live demos of the examples from that article which of course will only work in supported browsers.
54 |
55 |
56 | Note that I am returning a custom element as my shadow host. In my main stylesheet I have the following rule which targets the custom element when it has focus:
57 |
58 | Set to true:
59 |
60 |
61 |
62 | Not set:
63 |
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/docs/pages/DynamicStyles.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Demo from "../demos/Dynamic";
3 | import A from "../utils/A";
4 | import { CSSBlock, JSXBlock } from "../utils/CodeBlock";
5 | import NotSupported from "../utils/NotSupported";
6 |
7 | export default function(props) {
8 | const Component = props.shadowRootSupported ? Demo : NotSupported;
9 | const code = `
10 | import React from 'react';
11 | import ReactShadowRoot from 'react-shadow-root';
12 |
13 | const { constructableStylesheetsSupported } = ReactShadowRoot;
14 | const colors = [
15 | 'black', 'red', 'rebeccapurple', 'blue', 'brown',
16 | 'lime', 'magenta', 'green', 'orange', 'teal'
17 | ];
18 | const styles = \`:host {
19 | display: inline-flex;
20 | }
21 | span {
22 | background-color: var(--color);
23 | border-radius: 3px;
24 | color: #fff;
25 | padding: 1px 5px;
26 | }
27 | button {
28 | background-color: #fff;
29 | border: 1px solid var(--color);
30 | border-radius: 3px;
31 | color: var(--color);
32 | cursor: pointer;
33 | outline: 0;
34 | }
35 | button:active {
36 | background-color: var(--color);
37 | color: #fff;
38 | }
39 | button,
40 | span {
41 | margin: 0 2px;
42 | }\`;
43 |
44 | let sheet;
45 | let styleSheets;
46 |
47 | if (constructableStylesheetsSupported) {
48 | sheet = new CSSStyleSheet();
49 | sheet.replaceSync(styles);
50 | styleSheets = [sheet];
51 | }
52 |
53 | export default class extends React.Component {
54 | state = { cnt: 0 };
55 |
56 | increment = () => {
57 | this.setState({
58 | cnt: this.state.cnt + 1
59 | });
60 | }
61 |
62 | render() {
63 | const { reverse } = this.props;
64 | const { cnt } = this.state;
65 | const dynamicStyles = \`
66 | :host {
67 | flex-direction: $\{reverse ? 'row-reverse' : 'row'};
68 | --color: $\{colors[cnt % 10]};
69 | }
70 | \`;
71 |
72 | return (
73 |
74 |
75 |
76 | {cnt}
77 | Click Me
78 | {!constructableStylesheetsSupported &&
79 |
80 | }
81 |
82 |
83 | );
84 | }
85 | }
86 | `;
87 | const hostCSS = `
88 | :host([reverse]) {
89 | flex-direction: row-reverse;
90 | }
91 | `;
92 | const removeCSS = `
93 | flex-direction: $\{reverse ? 'row-reverse' : 'row'};
94 | `;
95 | const reverseAttrCode = `
96 |
97 | `;
98 | return (
99 | Dynamic Styling
100 |
101 | The previous examples created the styles outside of the component definition.
102 | Since the styes don't need to change based on props or state, this makes sense.
103 | You can however make style changes based on props and/or state by updating the styles
104 | in render
. You could also do this using an instance
105 | specific constructable stylesheet.
106 |
107 |
108 | The following shows the above example modified to change the way it displays based on a prop.
109 | Passing a reverse
prop will make the button appear before the output.
110 | It also changes color based on state; contrived, I know. For the color changes
111 | I am using CSS custom properties to
112 | keep the dynamic changes to a minimum.
113 |
114 |
115 | Default:
116 |
117 |
118 |
119 | With the reverse
prop set:
120 |
121 |
122 |
123 |
124 | You could also use :host()
in
125 | the component's main stylesheet to reverse the order. To do so, add the following rule to the stylesheet:
126 |
127 |
128 | Remove the following from the dynamic styles:
129 |
130 | And add reverse
as an attribute of the returned element:
131 |
132 |
133 | React will only add the reverse
attribute when
134 | the reverse
prop is true. When the attribute is
135 | present, the :host()
rule will be applied by the browser.
136 |
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/src/docs/pages/Introduction.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import A from "../utils/A";
3 |
4 | export default function(props) {
5 | return (
6 | Introduction
7 |
8 | React shadow root allows you to use the shadow DOM in
9 | your React components. The biggest advantage of this is that you can include
10 | your CSS with your component and it will be scoped to the shadow DOM. The styles don't leak out
11 | of the shadow DOM and only inheritable styles can't get in. You can use a preprocessor or whatever you like to
12 | create your CSS as long as it is valid CSS when you use it in your component.
13 |
14 |
15 | In the examples I am using valid custom element names for
16 | the shadow host instead of a more generic div
or span
.
17 | This is not required , but I find it can make debugging easier and it allows you to easily target your components
18 | from your main CSS file if needed. Since the behavior is defined with React, you should not need
19 | to register the custom element.
20 |
21 | Installation
22 | npm i -S react-shadow-root
23 | or
24 | yarn add react-shadow-root
25 | Notes
26 |
27 | A minimum of React 16 is required.
28 | Works in all modern browsers except non-Chromium Edge. Click here for current browser support.
29 | Not all HTML elements allow you to attach a shadow root. Click here for more information.
30 | It has been tested with the Context API introduced in React 16.3.0 and it worked fine. It has not been tested with the previous API.
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/docs/pages/Slots.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Demo from "../demos/Slots";
3 | import A from "../utils/A";
4 | import { JSXBlock } from "../utils/CodeBlock";
5 | import NotSupported from "../utils/NotSupported";
6 |
7 | export default function(props) {
8 | const Component = props.shadowRootSupported ? Demo : NotSupported;
9 | const componentCode = `
10 | class ElementDetails extends React.Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 | <NEED NAME >
18 | NEED DESCRIPTION
19 |
20 |
21 |
22 |
Attributes
23 |
None
24 |
25 |
26 |
27 | {!constructableStylesheetsSupported && }
28 |
29 | {this.props.children}
30 | );
31 | }
32 | }
33 | `;
34 | const useageCode = `
35 | export default function SlotsDemo() {
36 | return (
37 | <>
38 |
39 | slot
40 | A placeholder inside a web
41 | component that users can fill with their own markup,
42 | with the effect of composing different DOM trees
43 | together.
44 |
45 | name
46 | The name of the slot.
47 |
48 |
49 |
50 | template
51 | A mechanism for holding client-
52 | side content that is not to be rendered when a page is
53 | loaded but may subsequently be instantiated during
54 | runtime using JavaScript.
55 |
56 | >
57 | );
58 | }
59 | `;
60 |
61 | return (
62 | Working With Slots
63 |
64 | Slots let you create placeholders in the shadow DOM for users to pass content to.
65 | Of course you can achieve something silmilar with props, but with slots the user can control the markup and style of what is passed in, which may or may not be a good thing.
66 |
67 |
68 | Let's steal an example from MDN .
69 | We'll modify it to be a React component instead of a web component. Be sure to include {'{this.props.children}'}
after the
70 | closing ReactShadowRoot
tag. Below is the React equivalent of the template.
71 |
72 |
73 | And how you might use it:
74 |
75 | This is the end result:
76 |
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/docs/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | scroll-behavior: smooth;
3 | }
4 |
5 | body {
6 | color: #333;
7 | font-family: Arial, sans-serif;
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | header {
13 | align-items: center;
14 | background-color: #eee;
15 | border-bottom: 1px solid #666;
16 | display: flex;
17 | flex-direction: column;
18 | height: 150px;
19 | justify-content: center;
20 | margin: 0;
21 | text-align: center;
22 | width: 100%;
23 | }
24 |
25 | header h1 {
26 | font-family: monospace;
27 | margin: 0;
28 | }
29 |
30 | header h2 {
31 | color: #666;
32 | font-size: 1em;
33 | margin: 0;
34 | }
35 |
36 | main {
37 | display: flex;
38 | margin: 0 5%;
39 | }
40 |
41 | main > section {
42 | overflow-y: hidden;
43 | }
44 |
45 | #api ~ section {
46 | overflow-x: auto;
47 | }
48 |
49 | #api ~ section:first-of-type {
50 | margin-bottom: 5px;
51 | }
52 |
53 | #api ~ section h3 {
54 | left: 0;
55 | margin-right: -100%; /* Hack for Safari. Grrrrr */
56 | margin-top: 0;
57 | position: -webkit-sticky; /* Hack for Safari. Grrrrr */
58 | position: sticky;
59 | white-space: nowrap; /* Hack for Safari. Grrrrr */
60 | width: 0; /* Hack for Safari. Grrrrr */
61 | }
62 |
63 | table {
64 | border-collapse: collapse;
65 | margin-bottom: 15px;
66 | min-width: 750px;
67 | }
68 |
69 | tr:nth-child(2n) {
70 | background-color: #eee;
71 | }
72 |
73 | tr:nth-child(2n) code {
74 | background-color: #fff;
75 | }
76 |
77 | th, td {
78 | border: 1px solid #666;
79 | padding: 10px;
80 | }
81 |
82 | pre {
83 | background-color: #eee !important; /* Overriding an inline style. Grrrrr */
84 | border-radius: 5px;
85 | font-size: 14px;
86 | overflow-y: scroll !important; /* Overriding an inline style. Grrrrr */
87 | padding-right: 15px !important; /* Overriding an inline style. Grrrrr */
88 | }
89 |
90 | code {
91 | background-color: #eee;
92 | }
93 |
94 | code.block {
95 | border-radius: 3px;
96 | display: block;
97 | padding: 5px;
98 | width: fit-content;
99 | }
100 |
101 | code.inline {
102 | padding: 0 3px;
103 | }
104 |
105 | code.block,
106 | code.inline {
107 | font-size: 16px;
108 | }
109 |
110 | code.warning {
111 | color: red;
112 | margin: 3px 0;
113 | padding: 0 3px;
114 | }
115 |
116 | a {
117 | color: #00f;
118 | }
119 |
120 | a:hover {
121 | text-decoration: none;
122 | }
123 |
124 | nav {
125 | border-left: 1px solid #666;
126 | box-sizing: border-box;
127 | flex-shrink: 0;
128 | margin: 20px;
129 | margin-right: 0;
130 | padding-left: 20px;
131 | }
132 |
133 | nav a {
134 | color: #333;
135 | text-decoration: none;
136 | }
137 |
138 | .back-to-top {
139 | background-color: #333;
140 | border-radius: 3px;
141 | bottom: 20px;
142 | color: #fff;
143 | padding: 2px 5px;
144 | position: fixed;
145 | }
146 |
147 | .back-to-top:hover {
148 | text-decoration: none;
149 | }
150 |
151 | nav ul {
152 | background-color: #fff;
153 | list-style-type: none;
154 | margin: 0;
155 | margin-top: 30px;
156 | padding: 0;
157 | padding-bottom: 30px;
158 | position: -webkit-sticky;
159 | position: sticky;
160 | top: 50px;
161 | z-index: 1;
162 | }
163 |
164 | nav ul li {
165 | margin: 10px 0;
166 | }
167 |
168 | article {
169 | padding-bottom: 15px;
170 | }
171 |
172 | article:not(:last-child) {
173 | border-bottom: 1px solid #666;
174 | }
175 |
176 | article h2 {
177 | margin-top: 0;
178 | padding-top: 20px;
179 | }
180 |
181 | blockquote p {
182 | border-bottom: 2px solid #eee;
183 | border-top: 2px solid #eee;
184 | padding: 5px 0;
185 | }
186 |
187 | .output {
188 | background-color: #eee;
189 | border-radius: 5px;
190 | margin: 10px 0;
191 | padding: 10px;
192 | }
193 |
194 | focus-demo:focus {
195 | outline: 2px solid red;
196 | }
197 |
198 | .not-supported {
199 | color: #c00;
200 | margin: 0;
201 | }
202 |
203 | @media (max-width: 1023px) {
204 | main {
205 | margin: 0 10px;
206 | }
207 |
208 | nav {
209 | margin-left: 10px;
210 | padding-left: 10px;
211 | }
212 | }
213 |
214 | @media (min-width: 425px) {
215 | header h1 {
216 | font-size: 3em;
217 | }
218 | }
219 |
220 | @media (max-width: 666px) {
221 | nav {
222 | display: none;
223 | }
224 | }
225 |
226 | @media print {
227 | a {
228 | color: #333;
229 | text-decoration: none;
230 | }
231 |
232 | code {
233 | white-space: pre-wrap !important; /* Overriding an inline style. Grrrrr */
234 | }
235 |
236 | nav {
237 | display: none;
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/docs/utils/A.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default (props) => ({props.children} );
4 |
--------------------------------------------------------------------------------
/src/docs/utils/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import { coy } from 'react-syntax-highlighter/dist/esm/styles/prism';
4 |
5 | export function CSSBlock(props) {
6 | return (
7 | {props.code}
8 | );
9 | }
10 |
11 | export function JSXBlock(props) {
12 | return (
13 | {props.code}
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/docs/utils/NotSupported.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function() {
4 | return Your browser does not support attaching a shadow root.
;
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/ReactShadowRoot.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | const constructableStylesheetsSupported = typeof window !== 'undefined'
6 | && window.ShadowRoot
7 | && window.ShadowRoot.prototype.hasOwnProperty('adoptedStyleSheets')
8 | && window.CSSStyleSheet
9 | && window.CSSStyleSheet.prototype.hasOwnProperty('replace');
10 |
11 | const shadowRootSupported = typeof window !== 'undefined'
12 | && window.Element
13 | && window.Element.prototype.hasOwnProperty('attachShadow');
14 |
15 | export default class extends React.PureComponent {
16 | static constructableStylesheetsSupported = constructableStylesheetsSupported;
17 | static constructibleStylesheetsSupported = constructableStylesheetsSupported;
18 | static defaultProps = {
19 | declarative: false,
20 | delegatesFocus: false,
21 | mode: 'open'
22 | };
23 | static displayName = 'ReactShadowRoot';
24 | static propTypes = {
25 | declarative: PropTypes.bool,
26 | delegatesFocus: PropTypes.bool,
27 | mode: PropTypes.oneOf(['open', 'closed']),
28 | stylesheets: PropTypes.arrayOf(typeof window !== 'undefined' ? PropTypes.instanceOf(window.CSSStyleSheet) : PropTypes.any)
29 | };
30 | static shadowRootSupported = shadowRootSupported;
31 |
32 | state = { initialized: false };
33 |
34 | /**
35 | * @param {object} props Properties passed to the component
36 | * @param {boolean} props.declarative When true, uses a declarative shadow root
37 | * @param {boolean} props.delegatesFocus Expands the focus behavior of elements within the shadow DOM.
38 | * @param {string} props.mode Sets the mode of the shadow root. (open or closed)
39 | * @param {CSSStyleSheet[]} props.stylesheets Takes an array of CSSStyleSheet objects for constructable stylesheets.
40 | */
41 | constructor(props) {
42 | super(props);
43 | this.placeholder = React.createRef();
44 | }
45 |
46 | componentDidMount() {
47 | const {
48 | delegatesFocus,
49 | mode,
50 | stylesheets
51 | } = this.props;
52 |
53 | this.shadowRoot = this.placeholder.current.parentNode.attachShadow({
54 | delegatesFocus,
55 | mode
56 | });
57 |
58 | if (stylesheets) {
59 | this.shadowRoot.adoptedStyleSheets = stylesheets;
60 | }
61 |
62 | this.setState({
63 | initialized: true
64 | });
65 | }
66 |
67 | render() {
68 | if (!this.state.initialized) {
69 | if (this.props.declarative) {
70 | // @ts-ignore
71 | return (
72 | {this.props.children}
73 | );
74 | }
75 |
76 | return ( );
77 | }
78 |
79 | return ReactDOM.createPortal(this.props.children, this.shadowRoot);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | import ReactShadowRoot from './ReactShadowRoot.js';
2 |
3 | export default ReactShadowRoot;
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "commonjs", /* Specify what module code is generated. */
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | "outDir": "./lib", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 |
68 | /* Interop Constraints */
69 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
70 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
71 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
72 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
73 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
74 |
75 | /* Type Checking */
76 | "strict": true, /* Enable all strict type-checking options. */
77 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
78 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
95 |
96 | /* Completeness */
97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
98 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
99 | },
100 | "include": ["src/lib/**/*"],
101 | }
102 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: path.join(__dirname, "src/docs"),
6 | output: {
7 | path: path.join(__dirname, "docs"),
8 | filename: "bundle.js"
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|jsx)$/,
14 | use: "babel-loader",
15 | exclude: /node_modules/
16 | },
17 | {
18 | test: /\.css$/,
19 | use: ["style-loader", "css-loader"]
20 | }
21 | ]
22 | },
23 | plugins: [
24 | new HtmlWebpackPlugin({
25 | template: path.join(__dirname, "src/docs/index.html")
26 | })
27 | ],
28 | resolve: {
29 | extensions: [".js", ".jsx"]
30 | },
31 | devServer: {
32 | contentBase: path.join(__dirname, "docs"),
33 | port: 8000,
34 | stats: "minimal"
35 | }
36 | };
37 |
--------------------------------------------------------------------------------