├── .npmignore
├── tslint.json
├── server.yml
├── .dawn
├── rc.yml
└── pipe.yml
├── .gitignore
├── src
├── IShadowRootOptions.ts
├── index.ts
├── example
│ ├── Dialog.tsx
│ ├── index.html
│ └── index.tsx
├── IShadowViewProps.ts
├── EventNames.ts
├── EventBridge.ts
├── ShadowRoot.ts
├── Hooks.ts
└── ShadowView.ts
├── test
└── unit
│ └── demo.js
├── tsconfig.json
├── package.json
├── README.md
├── .eslintrc.yml
└── .eslintrc.json
/.npmignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules/
4 | .nyc_output/
5 | coverage/
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint-config-dawn"
4 | ]
5 | }
--------------------------------------------------------------------------------
/server.yml:
--------------------------------------------------------------------------------
1 | proxy:
2 | rules:
3 | ^/api(.*): 'https://www.aliyun.com/'
--------------------------------------------------------------------------------
/.dawn/rc.yml:
--------------------------------------------------------------------------------
1 | server: https://alibaba.github.io/dawn
2 | registry: https://registry.npmjs.com/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules/
4 | .nyc_output/
5 | coverage/
6 | build/
--------------------------------------------------------------------------------
/src/IShadowRootOptions.ts:
--------------------------------------------------------------------------------
1 | export interface IShadowRootOptions {
2 | delegatesFocus?: boolean;
3 | mode?: ShadowRootMode;
4 | }
5 |
--------------------------------------------------------------------------------
/test/unit/demo.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | describe('Array', function () {
4 | describe('#indexOf()', function () {
5 | it('should return -1 when the value is not present', function () {
6 | assert.equal(-1, [1, 2, 3].indexOf(4));
7 | });
8 | });
9 | });
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ShadowView } from "./ShadowView";
2 |
3 | export * from "./ShadowView";
4 | export * from "./EventBridge";
5 | export * from "./EventNames";
6 | export * from "./ShadowRoot";
7 | export * from "./IShadowRootOptions";
8 | export * from "./IShadowViewProps";
9 |
10 | export default ShadowView;
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "sourceMap": true,
5 | "declaration": true,
6 | "noImplicitAny": true,
7 | "module": "commonjs",
8 | "target": "es5",
9 | "jsx": "react",
10 | "noUnusedLocals": true,
11 | "esModuleInterop": true,
12 | "emitDecoratorMetadata": false,
13 | "experimentalDecorators": true,
14 | "lib": [
15 | "dom",
16 | "es2017"
17 | ],
18 | },
19 | "include": [
20 | "./src/**/*.ts",
21 | "./src/**/*.tsx"
22 | ]
23 | }
--------------------------------------------------------------------------------
/.dawn/pipe.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - name: pkginfo
3 |
4 | dev:
5 | - name: clean
6 | - name: typescript
7 | - name: webpack
8 | common:
9 | disabled: true
10 | watch: true
11 | entry: ./src/example/*.tsx
12 | template: ./src/example/*.html
13 | - name: server
14 | port: 5007
15 | - name: browser-sync
16 |
17 | build:
18 | - name: clean
19 | - name: shell
20 | script:
21 | - tsc
22 |
23 | test:
24 | - name: tslint
25 |
26 | publish:
27 | - name: shell
28 | script:
29 | - dn test
30 | - dn build
31 | - npm pu --registry=http://registry.npmjs.org
--------------------------------------------------------------------------------
/src/example/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | export class Dialog extends React.Component {
5 | root: HTMLElement;
6 | constructor(props: any, ...args: any[]) {
7 | super(props, ...args);
8 | const doc = window.document;
9 | this.root = doc.createElement("div");
10 | doc.body.appendChild(this.root);
11 | }
12 |
13 | render() {
14 | const { children } = this.props;
15 | return createPortal(
{children}
, this.root);
16 | }
17 |
18 | componentWillUnmount() {
19 | window.document.body.removeChild(this.root);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Shadow DOM
9 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/IShadowViewProps.ts:
--------------------------------------------------------------------------------
1 | import { IShadowRootOptions } from "./IShadowRootOptions";
2 |
3 | /**
4 | * ShadowView 属性
5 | */
6 | export interface IShadowViewProps extends IShadowRootOptions {
7 | /**
8 | * 根元素的 tagName
9 | * 默认为 `shadow-view`
10 | */
11 | tagName?: string;
12 |
13 | /**
14 | * Shadow 容器中的子元素
15 | */
16 | children?: React.ReactNode;
17 |
18 | /**
19 | * React Ref function
20 | */
21 | ref?: Function;
22 |
23 | /**
24 | * 顶层元素的 className
25 | */
26 | className?: string;
27 |
28 | /**
29 | * 顶层元素 style
30 | */
31 | style?: any;
32 |
33 | /**
34 | * 引入的样式表单
35 | */
36 | styleSheets?: string[];
37 |
38 | /**
39 | * 引入的样式文本
40 | */
41 | styleContent?: string;
42 |
43 | /**
44 | * 显示延时
45 | */
46 | showDelay?: number;
47 |
48 | /**
49 | * 是否启用隔离,默认为 true
50 | */
51 | scoped?: boolean;
52 |
53 | /**
54 | * 显示动画持续时间
55 | */
56 | transitionDuration?: string;
57 | }
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shadow-view",
3 | "version": "4.0.1",
4 | "description": "",
5 | "main": "./build/index.js",
6 | "types": "./build/index.d.ts",
7 | "scripts": {
8 | "test": "dn test && dn build -e prod"
9 | },
10 | "author": {
11 | "name": "Houfeng",
12 | "email": "admin@xhou.net",
13 | "url": "http://houfeng.net"
14 | },
15 | "license": "MIT",
16 | "dependencies": {
17 | "@types/node": "^12.6.3",
18 | "@types/react": "^16.8.23",
19 | "@types/react-dom": "^16.8.4",
20 | "react": "^16.8.6",
21 | "react-dom": "^16.8.6"
22 | },
23 | "devDependencies": {
24 | "dn-middleware-browser-sync": "^0.1.0",
25 | "dn-middleware-clean": "^1.0.2",
26 | "dn-middleware-copy": "^0.2.7",
27 | "dn-middleware-server": "^1.1.3",
28 | "dn-middleware-shell": "^1.1.0",
29 | "dn-middleware-tslint": "^1.1.3",
30 | "dn-middleware-typescript": "^2.0.8",
31 | "dn-middleware-webpack": "^1.11.5",
32 | "typescript": "^3.5.3"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/example/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | export class ShadowView extends React.Component {
5 | attachShadow = (host: Element) => {
6 | const shadowRoot = host.attachShadow({ mode: "open" });
7 | [].slice.call(host.children).forEach((child: any) => {
8 | shadowRoot.appendChild(child);
9 | });
10 | }
11 | render() {
12 | const { children } = this.props;
13 | return
14 | {children}
15 |
;
16 | }
17 | }
18 |
19 | export class App extends React.Component {
20 | state = { message: '...' };
21 | onBtnClick = () => {
22 | this.setState({ message: 'haha' });
23 | }
24 | render() {
25 | const { message } = this.state;
26 | return
27 |
28 | {message}
29 |
30 |
31 |
32 |
33 | }
34 | }
35 |
36 | ReactDOM.render(, document.getElementById("root"));
--------------------------------------------------------------------------------
/src/EventNames.ts:
--------------------------------------------------------------------------------
1 | export const BRIDGE_EVENT_NAMES = [
2 | "abort",
3 | "animationcancel",
4 | "animationend",
5 | "animationiteration",
6 | "auxclick",
7 | "blur",
8 | "change",
9 | "click",
10 | "close",
11 | "contextmenu",
12 | "doubleclick",
13 | "error",
14 | "focus",
15 | "gotpointercapture",
16 | "input",
17 | "keydown",
18 | "keypress",
19 | "keyup",
20 | "load",
21 | "loadend",
22 | "loadstart",
23 | "lostpointercapture",
24 | "mousedown",
25 | "mousemove",
26 | "mouseout",
27 | "mouseover",
28 | "mouseup",
29 | "pointercancel",
30 | "pointerdown",
31 | "pointerenter",
32 | "pointerleave",
33 | "pointermove",
34 | "pointerout",
35 | "pointerover",
36 | "pointerup",
37 | "reset",
38 | "resize",
39 | "scroll",
40 | "select",
41 | "selectionchange",
42 | "selectstart",
43 | "submit",
44 | "touchcancel",
45 | "touchmove",
46 | "touchstart",
47 | "transitioncancel",
48 | "transitionend",
49 | "drag",
50 | "dragend",
51 | "dragenter",
52 | "dragexit",
53 | "dragleave",
54 | "dragover",
55 | "dragstart",
56 | "drop",
57 | "focusout"
58 | ];
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShadowView
2 |
3 | ShadowView 是一个写好的可开箱即用的面向 React 的 Shadow DOM 容器组件,利用 ShadowView 可以像普通组件一样方便的在 React 应用中创建启用 Shadow DOM 的容器元素。
4 |
5 | ShadowView 目前完整兼容支持 React 15/16,组件的「事件处理、组件渲染更新」等行为在两个版中都是一致的。
6 |
7 |
8 | ## 安装组件
9 |
10 | ```bash
11 | npm i shadow-view --save
12 | ```
13 |
14 |
15 | ## 使用组件
16 |
17 | ```javascript
18 | import * as React from "react";
19 | import * as ReactDOM from "react-dom";
20 | import { ShadowView } from "shadow-view";
21 |
22 | function App() {
23 | return (
24 |
31 |
32 | 这是一个测试
33 |
34 | );
35 | }
36 |
37 | ReactDOM.render(, document.getElementById('root'));
38 | ```
39 |
40 |
41 | ## 组件属性
42 |
43 | | **属性名** | **类型** | **说明** |
44 | | :--- | :--- | :--- |
45 | | className | string | 组件自身 className |
46 | | style | any | 组件自身的内联样式 |
47 | | styleContent | string | 作用于 ShadowView 内部的样式 |
48 | | styleSheets | string[] | 作用于 ShadowView 内部的外联样式表 |
49 | | scoped | boolean | 是否开始隔离,默认为 true |
50 | | tagName | string | 外层容器 tagName,默认为 shadow-view |
51 |
--------------------------------------------------------------------------------
/src/EventBridge.ts:
--------------------------------------------------------------------------------
1 | import { BRIDGE_EVENT_NAMES } from "./EventNames";
2 |
3 | export function bridge(fromNode: Node, toNode: Node) {
4 | if (!fromNode || !toNode || fromNode === toNode) return;
5 | const define = Object.defineProperty;
6 | BRIDGE_EVENT_NAMES.forEach(eventName => {
7 | fromNode.addEventListener(eventName, (fromEvent: any) => {
8 | fromEvent.stopPropagation();
9 | const Event = fromEvent.constructor;
10 | const toEvent = new Event(eventName, {
11 | ...fromEvent,
12 | bubbles: true,
13 | cancelable: true,
14 | composed: true
15 | });
16 | const {
17 | path = [],
18 | target = path[0],
19 | srcElement = path[0],
20 | toElement = path[0],
21 | preventDefault
22 | } = fromEvent;
23 | define(toEvent, "path", { get: () => path });
24 | define(toEvent, "target", { get: () => target });
25 | define(toEvent, "srcElement", { get: () => srcElement });
26 | define(toEvent, "toElement", { get: () => toElement });
27 | define(toEvent, "preventDefault", {
28 | value: () => {
29 | preventDefault.call(fromEvent);
30 | return preventDefault.call(toEvent);
31 | }
32 | });
33 | toNode.dispatchEvent(toEvent);
34 | });
35 | });
36 | }
37 |
38 | export function bridgeShadowRoot(shadowRoot: ShadowRoot) {
39 | bridge(shadowRoot, shadowRoot.host);
40 | }
41 |
42 | export function bridgeShadowHost(shadowHost: Element) {
43 | bridge(shadowHost.shadowRoot, shadowHost);
44 | }
45 |
--------------------------------------------------------------------------------
/src/ShadowRoot.ts:
--------------------------------------------------------------------------------
1 | import { bridgeShadowRoot } from "./EventBridge";
2 | import { IShadowRootOptions } from "./IShadowRootOptions";
3 | import "./Hooks";
4 |
5 | export const supportShadow = "attachShadow" in document.createElement("div");
6 |
7 | export function attachShadow(host: HTMLElement, optinos: IShadowRootOptions) {
8 | if (!host || !supportShadow) return (host as any) as ShadowRoot;
9 | const { mode = "open", delegatesFocus } = { ...optinos };
10 | const shadowRoot = host.attachShadow({ mode, delegatesFocus });
11 | const define = Object.defineProperty;
12 | define(shadowRoot, "style", { get: () => host.style });
13 | define(shadowRoot, "parentNode", { get: () => host.parentNode });
14 | define(shadowRoot, "parentElement", { get: () => host.parentElement });
15 | define(shadowRoot, "offsetParent", { get: () => host.offsetParent });
16 | define(shadowRoot, "offsetTop", { get: () => host.offsetTop });
17 | define(shadowRoot, "offsetLeft", { get: () => host.offsetLeft });
18 | define(shadowRoot, "offsetWidth", { get: () => host.offsetWidth });
19 | define(shadowRoot, "offsetHeight", { get: () => host.offsetHeight });
20 | define(shadowRoot, "getAttribute", {
21 | get: () => host.getAttribute.bind(host)
22 | });
23 | define(shadowRoot, "getBoundingClientRect", {
24 | get: () => host.getBoundingClientRect.bind(host)
25 | });
26 | define(shadowRoot, "getClientRects", {
27 | get: () => host.getClientRects.bind(host)
28 | });
29 | define(host, "insertBefore", {
30 | get: () => shadowRoot.insertBefore.bind(shadowRoot)
31 | });
32 | define(host, "appendChild", {
33 | get: () => shadowRoot.appendChild.bind(shadowRoot)
34 | });
35 | bridgeShadowRoot(shadowRoot);
36 | return shadowRoot;
37 | }
38 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | parser: babel-eslint
2 | plugins:
3 | - react
4 | - html
5 | env:
6 | es6: true
7 | browser: true
8 | commonjs: true
9 | extends:
10 | - 'eslint:recommended'
11 | ecmaFeatures:
12 | jsx: true
13 | modules: true
14 | experimentalObjectRestSpread: true
15 | parserOptions:
16 | ecmaVersion: 6
17 | sourceType": module
18 | ecmaFeatures:
19 | jsx: true
20 | modules: true
21 | experimentalObjectRestSpread: true
22 | rules:
23 | strict:
24 | - 0
25 | indent:
26 | - 2
27 | - 2
28 | - SwitchCase: 1
29 | quotes:
30 | - 2
31 | - single
32 | linebreak-style:
33 | - 2
34 | - unix
35 | semi:
36 | - 2
37 | - always
38 | no-multi-spaces:
39 | - 2
40 | no-self-compare:
41 | - 2
42 | max-depth:
43 | - 2
44 | - 4
45 | max-nested-callbacks:
46 | - 2
47 | - 4
48 | max-params:
49 | - 2
50 | - 4
51 | max-statements:
52 | - 2
53 | - 25
54 | max-statements-per-line:
55 | - 2
56 | max-len:
57 | - 2
58 | - 120
59 | multiline-ternary:
60 | - 0
61 | callback-return:
62 | - 2
63 | handle-callback-err:
64 | - 2
65 | array-bracket-spacing:
66 | - 2
67 | no-const-assign:
68 | - 2
69 | no-return-assign:
70 | - 0
71 | no-inner-declarations:
72 | - 2
73 | no-var:
74 | - 2
75 | no-console:
76 | - 1
77 | no-lonely-if:
78 | - 2
79 | require-jsdoc:
80 | - 0
81 | - require:
82 | FunctionDeclaration: true
83 | MethodDefinition: true
84 | ClassDeclaration: true
85 | valid-jsdoc:
86 | - 2
87 | comma-dangle:
88 | - 2
89 | - never
90 | no-undef:
91 | - 2
92 | react/jsx-uses-react:
93 | - 2
94 | react/jsx-uses-vars:
95 | - 2
96 | react/jsx-no-undef:
97 | - 2
98 |
--------------------------------------------------------------------------------
/src/Hooks.ts:
--------------------------------------------------------------------------------
1 | export function getRootNode(node: Node) {
2 | let root: any = node;
3 | while (root && !root.host && root.parentNode) root = root.parentNode;
4 | return root;
5 | }
6 |
7 | const { removeChild } = Node.prototype;
8 | Node.prototype.removeChild = function(child) {
9 | if (!removeChild) return;
10 | try {
11 | return removeChild.call(this, child);
12 | } catch (err) {
13 | return removeChild.call(child.parentNode, child);
14 | }
15 | };
16 |
17 | const { contains } = Node.prototype;
18 | Node.prototype.contains = function(otherNode) {
19 | if (!otherNode || !contains) return false;
20 | const root = getRootNode(this);
21 | const otherRoot = getRootNode(otherNode);
22 | if (!root || !otherRoot) return false;
23 | try {
24 | return root === otherRoot
25 | ? contains.call(this, otherNode)
26 | : contains.call(this, otherRoot.host || otherNode);
27 | } catch (err) {
28 | console.error(err);
29 | return false;
30 | }
31 | };
32 |
33 | const { compareDocumentPosition } = Node.prototype;
34 | Node.prototype.compareDocumentPosition = function(otherNode) {
35 | if (!otherNode || !compareDocumentPosition) return false;
36 | const root = getRootNode(this);
37 | const otherRoot = getRootNode(otherNode);
38 | if (!root || !otherRoot) return false;
39 | try {
40 | return root === otherRoot
41 | ? compareDocumentPosition.call(this, otherNode)
42 | : compareDocumentPosition.call(this, otherRoot.host || otherNode);
43 | } catch (err) {
44 | console.error(err);
45 | return false;
46 | }
47 | };
48 |
49 | const parentElementDescriptor = Object.getOwnPropertyDescriptor(
50 | Node.prototype,
51 | "parentElement"
52 | );
53 | Object.defineProperty(Node.prototype, "parentElement", {
54 | configurable: true,
55 | enumerable: true,
56 | get() {
57 | const parentElement = parentElementDescriptor.get.call(this);
58 | if (parentElement) return parentElement;
59 | const parentNode = this.parentNode;
60 | if (!parentNode) return null;
61 | if (parentNode.constructor !== (window as any).ShadowRoot) return null;
62 | return parentNode.parentElement || parentNode;
63 | }
64 | });
65 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [
4 | "react",
5 | "html"
6 | ],
7 | "env": {
8 | "es6": true,
9 | "browser": true,
10 | "commonjs": true
11 | },
12 | "extends": [
13 | "eslint:recommended"
14 | ],
15 | "ecmaFeatures": {
16 | "jsx": true,
17 | "modules": true,
18 | "experimentalObjectRestSpread": true
19 | },
20 | "parserOptions": {
21 | "ecmaVersion": 6,
22 | "sourceType\"": "module",
23 | "ecmaFeatures": {
24 | "jsx": true,
25 | "modules": true,
26 | "experimentalObjectRestSpread": true
27 | }
28 | },
29 | "rules": {
30 | "strict": [
31 | 0
32 | ],
33 | "indent": [
34 | 2,
35 | 2,
36 | {
37 | "SwitchCase": 1
38 | }
39 | ],
40 | "quotes": [
41 | 2,
42 | "single"
43 | ],
44 | "linebreak-style": [
45 | 2,
46 | "unix"
47 | ],
48 | "semi": [
49 | 2,
50 | "always"
51 | ],
52 | "no-multi-spaces": [
53 | 2
54 | ],
55 | "no-self-compare": [
56 | 2
57 | ],
58 | "max-depth": [
59 | 2,
60 | 4
61 | ],
62 | "max-nested-callbacks": [
63 | 2,
64 | 4
65 | ],
66 | "max-params": [
67 | 2,
68 | 4
69 | ],
70 | "max-statements": [
71 | 2,
72 | 25
73 | ],
74 | "max-statements-per-line": [
75 | 2
76 | ],
77 | "max-len": [
78 | 2,
79 | 120
80 | ],
81 | "multiline-ternary": [
82 | 0
83 | ],
84 | "callback-return": [
85 | 2
86 | ],
87 | "handle-callback-err": [
88 | 2
89 | ],
90 | "array-bracket-spacing": [
91 | 2
92 | ],
93 | "no-const-assign": [
94 | 2
95 | ],
96 | "no-return-assign": [
97 | 0
98 | ],
99 | "no-inner-declarations": [
100 | 2
101 | ],
102 | "no-var": [
103 | 2
104 | ],
105 | "no-console": [
106 | 1
107 | ],
108 | "no-lonely-if": [
109 | 2
110 | ],
111 | "require-jsdoc": [
112 | 0,
113 | {
114 | "require": {
115 | "FunctionDeclaration": true,
116 | "MethodDefinition": true,
117 | "ClassDeclaration": true
118 | }
119 | }
120 | ],
121 | "valid-jsdoc": [
122 | 2
123 | ],
124 | "comma-dangle": [
125 | 2,
126 | "never"
127 | ],
128 | "no-undef": [
129 | 2
130 | ],
131 | "react/jsx-uses-react": [
132 | 2
133 | ],
134 | "react/jsx-uses-vars": [
135 | 2
136 | ],
137 | "react/jsx-no-undef": [
138 | 2
139 | ]
140 | }
141 | }
--------------------------------------------------------------------------------
/src/ShadowView.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { attachShadow, supportShadow } from "./ShadowRoot";
3 | import { IShadowViewProps } from "./IShadowViewProps";
4 |
5 | /**
6 | * ShadowView 创建一个启用了 Shadow DOM 的容器
7 | */
8 | export class ShadowView extends React.Component {
9 | /**
10 | * ShadowRoot
11 | */
12 | public shadowRoot: ShadowRoot;
13 |
14 | /**
15 | * DOM Root
16 | */
17 | public root: HTMLElement;
18 |
19 | /**
20 | * 原始的 visibility 属性值
21 | */
22 | private originVisibility: string;
23 |
24 | /**
25 | * 渲染组件内容
26 | */
27 | public render() {
28 | const { tagName = "shadow-view", children, className, style } = this.props;
29 | const props = { className, style, ref: this.onRef };
30 | const styleElement = this.renderStyle();
31 | return React.createElement(tagName, props, children, styleElement);
32 | }
33 |
34 | /**
35 | * 在执行 ref 函数时
36 | */
37 | private onRef = (root: HTMLElement) => {
38 | const { ref } = this.props;
39 | this.root = root;
40 | this.hideRoot();
41 | this.attachShadow();
42 | if (typeof ref === "function") ref(root);
43 | else if (typeof ref === "string") (this)[ref] = root;
44 | };
45 |
46 | /**
47 | * 渲染局部作用域的样式
48 | */
49 | private renderStyle() {
50 | const { styleContent, styleSheets = [] } = this.props;
51 | const styleBuffer = [
52 | ...styleSheets.map(url => `@import url("${url}")`),
53 | ...(styleContent ? [styleContent] : [])
54 | ];
55 | return React.createElement(
56 | "style",
57 | { key: "style" },
58 | styleBuffer.join(";")
59 | );
60 | }
61 |
62 | /**
63 | * 在组件挂载时
64 | */
65 | componentDidMount() {
66 | const { showDelay = 16 } = this.props;
67 | setTimeout(this.checkRootVisibility, showDelay);
68 | }
69 |
70 | /**
71 | * 启用 Shadow DOM
72 | */
73 | private attachShadow = () => {
74 | if (this.props.scoped === false) return;
75 | if (!supportShadow || !this.root || !this.root.children) return;
76 | const children = [].slice.call(this.root.children);
77 | const { mode = "open", delegatesFocus } = this.props;
78 | this.shadowRoot = attachShadow(this.root, { mode, delegatesFocus });
79 | children.forEach((child: HTMLElement) => {
80 | this.shadowRoot.appendChild(child);
81 | });
82 | };
83 |
84 | /**
85 | * 隐藏根元素
86 | */
87 | private hideRoot = () => {
88 | if (!this.root || !this.root.style) return;
89 | this.originVisibility = this.root.style.opacity;
90 | this.root.style.opacity = "0";
91 | };
92 |
93 | /**
94 | * 显示根元素
95 | */
96 | private showRoot = () => {
97 | if (!this.root || !this.root.style) return;
98 | const { transitionDuration } = this.props;
99 | this.root.style.transitionDuration = transitionDuration || ".3s";
100 | this.root.style.opacity = this.originVisibility;
101 | };
102 |
103 | /**
104 | * 检查样式加载状态
105 | */
106 | private checkRootVisibility = () => {
107 | if (!this.shadowRoot || !this.shadowRoot.styleSheets) {
108 | return this.showRoot();
109 | }
110 | const style = this.shadowRoot.styleSheets[0] as any;
111 | if (!style) return this.showRoot();
112 | const rules = [].slice.call(style.rules || style.cssRules || []);
113 | if (rules.length < 1) return this.showRoot();
114 | const pending = rules.some((rule: any) => {
115 | return !(rule.styleSheet || rule.href === "") && !rule.style;
116 | });
117 | return pending ? setTimeout(this.checkRootVisibility, 16) : this.showRoot();
118 | };
119 | }
120 |
--------------------------------------------------------------------------------