Skip to content

Commit d628fef

Browse files
author
Christopher J Baker
authoredMay 13, 2023
Update Renderer API (bitovi#101)
* update react-to-web-component * refactor * fix tests * fix
1 parent f9e5239 commit d628fef

18 files changed

+407
-693
lines changed
 

‎.github/actions/test/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ runs:
1111
steps:
1212
- name: Test
1313
shell: bash
14-
run: npx nx test ${{ inputs.package }}
14+
run: npx nx test:ci ${{ inputs.package }}

‎nx.json

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"prettier",
1212
"depcheck",
1313
"test",
14+
"test:ci",
15+
"test:coverage",
1416
"clean",
1517
"build"
1618
]

‎packages/core/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
"prettier": "prettier --check vite.config.ts src",
3535
"depcheck": "depcheck .",
3636
"dev": "vite",
37-
"test": "vitest run",
38-
"test:dev": "vitest",
37+
"test": "vitest",
38+
"test:ci": "vitest run",
3939
"test:coverage": "vitest run --coverage",
4040
"clean": "rm -rf tsconfig.tsbuildinfo dist",
4141
"build": "vite build"

‎packages/core/src/core.test.tsx

+79-70
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { describe, test, it, expect, vi, afterEach } from "vitest"
22
import matchers from "@testing-library/jest-dom/matchers"
3-
import React from "react"
43

54
import r2wc from "./core"
65

76
expect.extend(matchers)
87

9-
const mountCheck = vi.fn()
8+
const mount = vi.fn(() => ({ why: "context" }))
109
const unmount = vi.fn()
1110
const update = vi.fn()
12-
const onUpdated = vi.fn()
13-
14-
const mount = (el: HTMLElement, reactComponent: any, _props: any) => {
15-
mountCheck()
16-
return {
17-
reactContainer: el,
18-
component: reactComponent,
19-
}
20-
}
2111

22-
function flushPromises() {
12+
function wait() {
2313
return new Promise((resolve) => setImmediate(resolve))
2414
}
2515

@@ -28,40 +18,62 @@ describe("core", () => {
2818
document.body.innerHTML = ""
2919
})
3020

31-
it("mounts and unmounts for a functional component", async () => {
32-
function TestComponent() {
33-
return <div>hello</div>
34-
}
21+
it("mounts and unmounts in light mode", async () => {
22+
const ReactComponent: React.FC = () => <h1>Hello</h1>
3523

36-
const TestElement = r2wc(TestComponent, {}, { mount, unmount, update })
37-
customElements.define("test-func-element", TestElement)
24+
const WebComponent = r2wc(ReactComponent, {}, { mount, unmount, update })
25+
customElements.define("test-light", WebComponent)
3826

39-
const testEl = new TestElement()
27+
const element = new WebComponent()
4028

41-
document.body.appendChild(testEl)
42-
expect(mountCheck).toBeCalledTimes(1)
29+
document.body.appendChild(element)
30+
expect(mount).toBeCalledTimes(1)
4331

44-
document.body.removeChild(testEl)
32+
document.body.removeChild(element)
4533
expect(unmount).toBeCalledTimes(1)
34+
expect(unmount).toBeCalledWith({ why: "context" })
4635
})
4736

48-
it("mounts and unmounts for a class component", async () => {
49-
class TestComponent extends React.Component {
50-
render() {
51-
return <div>hello</div>
52-
}
53-
}
37+
it("mounts and unmounts in open shadow mode", async () => {
38+
const ReactComponent: React.FC = () => <h1>Hello</h1>
39+
40+
const WebComponent = r2wc(
41+
ReactComponent,
42+
{ shadow: "open" },
43+
{ mount, unmount, update },
44+
)
45+
customElements.define("test-shadow-open", WebComponent)
46+
47+
const element = new WebComponent()
5448

55-
const TestElement = r2wc(TestComponent, {}, { mount, unmount, update })
56-
customElements.define("test-element", TestElement)
49+
document.body.appendChild(element)
50+
expect(element).toHaveProperty("shadowRoot")
51+
expect(mount).toBeCalledTimes(1)
52+
53+
document.body.removeChild(element)
54+
expect(unmount).toBeCalledTimes(1)
55+
expect(unmount).toBeCalledWith({ why: "context" })
56+
})
57+
58+
it("mounts and unmounts in closed shadow mode", async () => {
59+
const ReactComponent: React.FC = () => <h1>Hello</h1>
60+
61+
const WebComponent = r2wc(
62+
ReactComponent,
63+
{ shadow: "closed" },
64+
{ mount, unmount, update },
65+
)
66+
customElements.define("test-shadow-closed", WebComponent)
5767

58-
const testEl = new TestElement()
68+
const element = new WebComponent()
5969

60-
document.body.appendChild(testEl)
61-
expect(mountCheck).toBeCalledTimes(1)
70+
document.body.appendChild(element)
71+
expect(element).toHaveProperty("shadowRoot")
72+
expect(mount).toBeCalledTimes(1)
6273

63-
document.body.removeChild(testEl)
74+
document.body.removeChild(element)
6475
expect(unmount).toBeCalledTimes(1)
76+
expect(unmount).toBeCalledWith({ why: "context" })
6577
})
6678

6779
test("updated attribute updates the component prop and the HTMLElement property", async () => {
@@ -72,7 +84,7 @@ describe("core", () => {
7284
const ButtonElement = r2wc(
7385
Button,
7486
{ props: ["text"] },
75-
{ mount, unmount, update, onUpdated },
87+
{ mount, unmount, update },
7688
)
7789

7890
customElements.define("test-button-element-attribute", ButtonElement)
@@ -81,20 +93,18 @@ describe("core", () => {
8193
body.innerHTML =
8294
"<test-button-element-attribute text='hello'></test-button-element-attribute>"
8395

84-
const testEl = body.querySelector(
96+
const element = body.querySelector(
8597
"test-button-element-attribute",
8698
) as HTMLElement & { text: string }
8799

88-
testEl.setAttribute("text", "world")
100+
element.setAttribute("text", "world")
89101

90-
await flushPromises()
102+
await wait()
91103

92-
expect(onUpdated).toBeCalledTimes(1)
93-
expect(testEl.text).toBe("world")
104+
expect(element.text).toBe("world")
94105
})
95106

96107
test("updated HTMLElement property updates the component prop and the HTMLElement attribute", async () => {
97-
expect.assertions(13)
98108
interface Props {
99109
text: string
100110
numProp: number
@@ -127,7 +137,7 @@ describe("core", () => {
127137
funcProp: "function",
128138
},
129139
},
130-
{ mount, unmount, update, onUpdated },
140+
{ mount, unmount, update },
131141
)
132142

133143
//@ts-ignore
@@ -147,33 +157,32 @@ describe("core", () => {
147157
body.innerHTML = `<test-button-element-property text='hello' obj-prop='{"greeting": "hello, world"}' arr-prop='["hello", "world"]' num-prop='240' bool-prop='true' func-prop='globalFn'>
148158
</test-button-element-property>`
149159

150-
const testEl = body.querySelector(
160+
const element = body.querySelector(
151161
"test-button-element-property",
152162
) as HTMLElement & Props
153163

154-
await flushPromises()
164+
await wait()
155165

156-
expect(testEl.text).toBe("hello")
157-
expect(testEl.numProp).toBe(240)
158-
expect(testEl.boolProp).toBe(true)
159-
expect(testEl.arrProp).toEqual(["hello", "world"])
160-
expect(testEl.objProp).toEqual({ greeting: "hello, world" })
161-
expect(testEl.funcProp).toBeInstanceOf(Function)
162-
expect(testEl.funcProp()).toBe(true)
166+
expect(element.text).toBe("hello")
167+
expect(element.numProp).toBe(240)
168+
expect(element.boolProp).toBe(true)
169+
expect(element.arrProp).toEqual(["hello", "world"])
170+
expect(element.objProp).toEqual({ greeting: "hello, world" })
171+
expect(element.funcProp).toBeInstanceOf(Function)
172+
expect(element.funcProp()).toBe(true)
163173

164-
testEl.text = "world"
165-
testEl.numProp = 100
166-
testEl.boolProp = false
174+
element.text = "world"
175+
element.numProp = 100
176+
element.boolProp = false
167177
//@ts-ignore
168-
testEl.funcProp = global.newFunc
178+
element.funcProp = global.newFunc
169179

170-
await flushPromises()
180+
await wait()
171181

172-
expect(onUpdated).toBeCalledTimes(4)
173-
expect(testEl.getAttribute("text")).toBe("world")
174-
expect(testEl.getAttribute("num-prop")).toBe("100")
175-
expect(testEl.getAttribute("bool-prop")).toBe("false")
176-
expect(testEl.getAttribute("func-prop")).toBe("newFunc")
182+
expect(element.getAttribute("text")).toBe("world")
183+
expect(element.getAttribute("num-prop")).toBe("100")
184+
expect(element.getAttribute("bool-prop")).toBe("false")
185+
expect(element.getAttribute("func-prop")).toBe("newFunc")
177186
})
178187

179188
test("sets HTML property not defined in props but found on HTML object", async () => {
@@ -184,25 +193,25 @@ describe("core", () => {
184193
const ButtonElement = r2wc(
185194
Button,
186195
{ props: ["text"] },
187-
{ mount, unmount, update, onUpdated },
196+
{ mount, unmount, update },
188197
)
189198

190199
customElements.define("test-button-element-non-prop", ButtonElement)
191200

192201
const body = document.body
193202
body.innerHTML = `<test-button-element-non-prop></test-button-element-non-prop>`
194203

195-
const testEl = body.querySelector(
204+
const element = body.querySelector(
196205
"test-button-element-non-prop",
197206
) as HTMLElement & { text: string }
198-
testEl.style.backgroundColor = "red"
199-
testEl.style.visibility = "hidden"
200-
testEl.id = "test-button-id"
207+
element.style.backgroundColor = "red"
208+
element.style.visibility = "hidden"
209+
element.id = "test-button-id"
201210

202-
await flushPromises()
211+
await wait()
203212

204-
expect(testEl).toHaveStyle("background-color: red;")
205-
expect(testEl).not.toBeVisible()
206-
expect(body.querySelector("#test-button-id")).toBe(testEl)
213+
expect(element).toHaveStyle("background-color: red;")
214+
expect(element).not.toBeVisible()
215+
expect(body.querySelector("#test-button-id")).toBe(element)
207216
})
208217
})

‎packages/core/src/core.ts

+133-284
Large diffs are not rendered by default.
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Transform } from "./index"
2+
3+
const string: Transform<boolean> = {
4+
stringify: (value) => (value ? "true" : "false"),
5+
parse: (value) => /^[ty1-9]/i.test(value),
6+
}
7+
8+
export default string
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Transform } from "./index"
2+
3+
const string: Transform<(...args: unknown[]) => unknown> = {
4+
stringify: (value) => value.name,
5+
parse: (value, element) => {
6+
const fn = (() => {
7+
if (typeof window !== "undefined" && value in window) {
8+
// @ts-expect-error
9+
return window[value]
10+
}
11+
12+
if (typeof global !== "undefined" && value in global) {
13+
// @ts-expect-error
14+
return global[value]
15+
}
16+
})()
17+
18+
return typeof fn === "function" ? fn.bind(element) : undefined
19+
},
20+
}
21+
22+
export default string

‎packages/core/src/transforms/index.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import string from "./string"
2+
import number from "./number"
3+
import boolean from "./boolean"
4+
import function_ from "./function"
5+
import json from "./json"
6+
7+
export interface Transform<Type> {
8+
stringify?: (value: Type) => string
9+
parse: (value: string, element: HTMLElement) => Type
10+
}
11+
12+
const transforms = {
13+
string,
14+
number,
15+
boolean,
16+
function: function_,
17+
json,
18+
}
19+
20+
export type R2WCType = keyof typeof transforms
21+
22+
export default transforms

‎packages/core/src/transforms/json.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Transform } from "./index"
2+
3+
const string: Transform<string> = {
4+
stringify: (value) => JSON.stringify(value),
5+
parse: (value) => JSON.parse(value),
6+
}
7+
8+
export default string
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Transform } from "./index"
2+
3+
const string: Transform<number> = {
4+
stringify: (value) => `${value}`,
5+
parse: (value) => parseFloat(value),
6+
}
7+
8+
export default string
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Transform } from "./index"
2+
3+
const string: Transform<string> = {
4+
stringify: (value) => value,
5+
parse: (value) => value,
6+
}
7+
8+
export default string

‎packages/core/src/types.ts

-81
This file was deleted.

‎packages/legacy/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
"prettier": "prettier --check vite.config.ts src",
3737
"depcheck": "depcheck .",
3838
"dev": "vite",
39-
"test": "vitest run",
40-
"test:dev": "vitest",
39+
"test": "vitest",
40+
"test:ci": "vitest run",
4141
"test:coverage": "vitest run --coverage",
4242
"clean": "rm -rf tsconfig.tsbuildinfo dist",
4343
"build": "vite build"

‎packages/legacy/src/react-to-webcomponent.ts

+75-61
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
1-
import type {
2-
ComponentClass,
3-
Container,
4-
FC,
5-
R2WCOptions,
6-
ReactElement,
7-
RefObject,
8-
Context,
9-
} from "@r2wc/core"
10-
import r2wcCore from "@r2wc/core"
11-
12-
/* eslint-disable @typescript-eslint/no-explicit-any */
13-
const rootSymbol = Symbol.for("r2wc.root")
1+
import type { R2WCOptions } from "@r2wc/core"
142

15-
interface ReactDOMType {
16-
createRoot?: (container: Element | DocumentFragment, options?: any) => unknown
17-
unmountComponentAtNode?: (container: Element | DocumentFragment) => boolean
18-
render?: (
19-
element: ReactElement<any, any> | null | any,
20-
container: Container | null,
21-
) => unknown
22-
}
3+
import r2wcCore from "@r2wc/core"
234

245
interface ReactType {
25-
createRef: () => RefObject<unknown>
266
createElement: (
27-
type: string | FC<any> | ComponentClass<any>,
7+
type: any,
288
data: any,
299
children?: any,
30-
) => ReactElement<any, any> | null | any
10+
) => React.ReactElement | null
11+
}
12+
13+
interface ReactDOMRootRootType {
14+
render: (element: React.ReactElement | null) => void
15+
unmount: () => void
16+
}
17+
18+
interface ReactDOMRootType {
19+
createRoot: (
20+
container: Element | DocumentFragment,
21+
options?: any,
22+
) => ReactDOMRootRootType
23+
}
24+
25+
interface ReactDOMRenderType {
26+
unmountComponentAtNode: (container: Element | DocumentFragment) => boolean
27+
render: (
28+
element: React.ReactElement | null,
29+
container: ReactDOM.Container | null,
30+
) => unknown
31+
}
32+
33+
interface Context<Props> {
34+
container: HTMLElement
35+
root?: ReactDOMRootRootType
36+
ReactComponent: React.ComponentType<Props>
3137
}
3238

3339
/**
@@ -40,60 +46,68 @@ interface ReactType {
4046
* @param {Object|Array?} options.props - Array of camelCasedProps to watch as Strings or { [camelCasedProp]: String | Number | Boolean | Function | Object | Array }
4147
*/
4248
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
43-
export default function (
44-
ReactComponent: FC<any> | ComponentClass<any>,
49+
export default function r2wc<Props>(
50+
ReactComponent: React.ComponentType<Props>,
4551
React: ReactType,
46-
ReactDOM: ReactDOMType,
47-
options: R2WCOptions = {},
52+
ReactDOM: ReactDOMRootType | ReactDOMRenderType,
53+
options: R2WCOptions<Props> = {},
4854
): CustomElementConstructor {
49-
const isReact18 =
50-
ReactDOM.createRoot && typeof ReactDOM.createRoot === "function"
51-
function unmount<Props extends React.Attributes>(
52-
this: any,
53-
{ reactContainer }: Context<React.ComponentType<Props>>,
54-
): void {
55-
if (isReact18) {
56-
this[rootSymbol].unmount()
57-
} else if (ReactDOM.unmountComponentAtNode) {
58-
ReactDOM.unmountComponentAtNode(reactContainer)
59-
}
60-
}
61-
62-
function mount<Props extends React.Attributes>(
63-
this: any,
55+
function mount(
6456
container: HTMLElement,
6557
ReactComponent: React.ComponentType<Props>,
6658
props: Props,
67-
): Context<React.ComponentType<Props>> {
59+
): Context<Props> {
6860
const element = React.createElement(ReactComponent, props)
69-
if (isReact18) {
70-
if (!this[rootSymbol]) {
71-
this[rootSymbol] = ReactDOM.createRoot?.(container)
61+
62+
if ("createRoot" in ReactDOM) {
63+
const root = ReactDOM.createRoot(container)
64+
root.render(element)
65+
66+
return {
67+
container,
68+
root,
69+
ReactComponent,
7270
}
71+
}
7372

74-
this[rootSymbol].render(element)
75-
} else if (ReactDOM.render) {
73+
if ("render" in ReactDOM) {
7674
ReactDOM.render(element, container)
77-
}
7875

79-
return {
80-
reactContainer: container,
81-
component: ReactComponent,
76+
return {
77+
container,
78+
ReactComponent,
79+
}
8280
}
81+
82+
throw new Error("Invalid ReactDOM instance provided.")
8383
}
8484

85-
function update<Props extends React.Attributes>(
86-
this: any,
87-
{ reactContainer, component }: Context<React.ComponentType<Props>>,
85+
function update(
86+
{ container, root, ReactComponent }: Context<Props>,
8887
props: Props,
8988
): void {
90-
const element = React.createElement(component, props)
89+
const element = React.createElement(ReactComponent, props)
90+
91+
if (root) {
92+
root.render(element)
93+
return
94+
}
95+
96+
if ("render" in ReactDOM) {
97+
ReactDOM.render(element, container)
98+
return
99+
}
100+
}
101+
102+
function unmount({ container, root }: Context<Props>): void {
103+
if (root) {
104+
root.unmount()
105+
return
106+
}
91107

92-
if (isReact18) {
93-
this[rootSymbol].render(element)
108+
if ("unmountComponentAtNode" in ReactDOM) {
109+
ReactDOM.unmountComponentAtNode(container)
94110
return
95-
} else if (ReactDOM.render) {
96-
ReactDOM.render(element, reactContainer)
97111
}
98112
}
99113

‎packages/react-to-web-component/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"prettier": "prettier --check vite.config.ts src",
3636
"depcheck": "depcheck .",
3737
"dev": "vite",
38-
"test": "vitest run",
39-
"test:dev": "vitest",
38+
"test": "vitest",
39+
"test:ci": "vitest run",
4040
"test:coverage": "vitest run --coverage",
4141
"clean": "rm -rf tsconfig.tsbuildinfo dist",
4242
"build": "vite build"

‎packages/react-to-web-component/src/react-to-web-component.test.tsx

+13-128
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ import { describe, it, expect, assert } from "vitest"
33
import matchers from "@testing-library/jest-dom/matchers"
44

55
import r2wc from "./react-to-web-component"
6-
import React from "react"
76

87
expect.extend(matchers)
98

109
function flushPromises() {
1110
return new Promise((resolve) => setImmediate(resolve))
1211
}
1312

14-
const Greeting = ({ name }: { name: string }) => <h1>Hello, {name}</h1>
13+
const Greeting: React.FC<{ name: string }> = ({ name }) => (
14+
<h1>Hello, {name}</h1>
15+
)
1516

16-
describe("react", () => {
17+
describe("react-to-web-component", () => {
1718
it("basics with react", () => {
1819
const MyWelcome = r2wc(Greeting)
1920
customElements.define("my-welcome", MyWelcome)
@@ -140,18 +141,19 @@ describe("react", () => {
140141
arrayProp,
141142
objProp,
142143
}
144+
143145
return <h1>{stringProp}</h1>
144146
}
145147

146148
const WebOptionsPropsTypeCasting = r2wc(OptionsPropsTypeCasting, {
147149
props: {
148-
stringProp: String,
149-
numProp: Number,
150-
floatProp: Number,
151-
trueProp: Boolean,
152-
falseProp: Boolean,
153-
arrayProp: Array,
154-
objProp: Object,
150+
stringProp: "string",
151+
numProp: "number",
152+
floatProp: "number",
153+
trueProp: "boolean",
154+
falseProp: "boolean",
155+
arrayProp: "json",
156+
objProp: "json",
155157
},
156158
})
157159

@@ -221,7 +223,7 @@ describe("react", () => {
221223

222224
const WebThemeSelect = r2wc(ThemeSelect, {
223225
props: {
224-
handleClick: Function,
226+
handleClick: "function",
225227
},
226228
})
227229

@@ -256,121 +258,4 @@ describe("react", () => {
256258
}, 0)
257259
})
258260
})
259-
260-
it("Props typed as 'ref' work with functional components", async () => {
261-
expect.assertions(5)
262-
263-
type TestRef = {
264-
tag: string
265-
setTag: React.Dispatch<React.SetStateAction<string>>
266-
}
267-
268-
const RCom = React.forwardRef<
269-
TestRef,
270-
{ h1Ref: React.RefObject<HTMLButtonElement> }
271-
>(function RCom(props, ref) {
272-
const [tag, setTag] = React.useState("h1")
273-
React.useImperativeHandle(ref, () => ({
274-
tag,
275-
setTag,
276-
}))
277-
return (
278-
<button ref={props.h1Ref} onClick={() => setTag("h2")}>
279-
Ref, {tag}
280-
</button>
281-
)
282-
})
283-
284-
class WebCom extends r2wc(RCom, {
285-
props: {
286-
ref: "ref",
287-
h1Ref: "ref",
288-
},
289-
}) {}
290-
291-
customElements.define("ref-test-func", WebCom)
292-
293-
const body = document.body
294-
295-
await new Promise((r) => {
296-
body.innerHTML = "<ref-test-func ref h1-ref></ref-test-func>"
297-
298-
setTimeout(() => {
299-
const el = document.querySelector("ref-test-func") as HTMLElement & {
300-
ref: React.RefObject<TestRef>
301-
h1Ref: React.RefObject<HTMLButtonElement>
302-
}
303-
expect(el.ref.current?.tag).toEqual("h1")
304-
expect(typeof el.ref.current?.setTag).toEqual("function")
305-
const button = document.querySelector(
306-
"ref-test-func button",
307-
) as HTMLButtonElement
308-
expect(el.h1Ref.current).toEqual(button)
309-
button?.click()
310-
setTimeout(() => {
311-
expect(el.ref.current?.tag).toEqual("h2")
312-
expect(button.innerHTML).toEqual("Ref, h2")
313-
r(true)
314-
}, 0)
315-
}, 0)
316-
})
317-
})
318-
319-
it("Props typed as 'ref' work with class components", async () => {
320-
expect.assertions(3)
321-
322-
type TestClassProps = {
323-
h1Ref: React.RefObject<HTMLButtonElement>
324-
}
325-
326-
class RCom extends React.Component<TestClassProps, { tag: string }> {
327-
constructor(props: TestClassProps) {
328-
super(props)
329-
this.state = { tag: "h1" }
330-
}
331-
render() {
332-
const Tag = this.state.tag
333-
return (
334-
<button
335-
ref={this.props.h1Ref}
336-
onClick={() => this.setState({ tag: "h2" })}
337-
>
338-
Ref, {Tag}
339-
</button>
340-
)
341-
}
342-
}
343-
344-
class WebCom extends r2wc(RCom, {
345-
props: {
346-
ref: "ref",
347-
h1Ref: "ref",
348-
},
349-
}) {}
350-
351-
customElements.define("ref-test", WebCom)
352-
353-
const body = document.body
354-
355-
await new Promise((r) => {
356-
body.innerHTML = "<ref-test ref h1-ref></ref-test>"
357-
358-
setTimeout(() => {
359-
const el = document.querySelector("ref-test") as HTMLElement & {
360-
ref: React.RefObject<HTMLElement>
361-
h1Ref: React.RefObject<HTMLButtonElement>
362-
}
363-
expect(el?.ref.current instanceof RCom).toEqual(true)
364-
const button = document.querySelector(
365-
"ref-test button",
366-
) as HTMLButtonElement
367-
expect(el.h1Ref.current).toEqual(button)
368-
button.click()
369-
setTimeout(() => {
370-
expect(button.innerHTML).toEqual("Ref, h2")
371-
r(true)
372-
}, 0)
373-
}, 0)
374-
})
375-
})
376261
})
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,46 @@
1-
import type { Context, R2WCOptions } from "@r2wc/core"
1+
import type { R2WCOptions } from "@r2wc/core"
22

3-
import ReactDOM from "react-dom"
4-
import type { ComponentType } from "react"
53
import React from "react"
4+
import ReactDOM from "react-dom"
65

76
import r2wcCore from "@r2wc/core"
87

9-
function mount<Props extends React.Attributes>(
8+
interface Context<Props extends object> {
9+
container: HTMLElement
10+
ReactComponent: React.ComponentType<Props>
11+
}
12+
13+
function mount<Props extends object>(
1014
container: HTMLElement,
11-
ReactComponent: ComponentType<Props>,
15+
ReactComponent: React.ComponentType<Props>,
1216
props: Props,
13-
): Context<ComponentType<Props>> {
17+
): Context<Props> {
1418
const element = React.createElement(ReactComponent, props)
1519

1620
ReactDOM.render(element, container)
1721

1822
return {
19-
reactContainer: container,
20-
component: ReactComponent,
23+
container,
24+
ReactComponent,
2125
}
2226
}
2327

24-
function update<Props extends React.Attributes>(
25-
{ reactContainer, component }: Context<ComponentType<Props>>,
28+
function update<Props extends object>(
29+
{ container, ReactComponent }: Context<Props>,
2630
props: Props,
2731
): void {
28-
const element = React.createElement(component, props)
32+
const element = React.createElement(ReactComponent, props)
2933

30-
ReactDOM.render(element, reactContainer)
34+
ReactDOM.render(element, container)
3135
}
3236

33-
function unmount<Props extends React.Attributes>({
34-
reactContainer,
35-
}: Context<ComponentType<Props>>): void {
36-
ReactDOM.unmountComponentAtNode(reactContainer)
37+
function unmount<Props extends object>({ container }: Context<Props>): void {
38+
ReactDOM.unmountComponentAtNode(container)
3739
}
3840

39-
export default function r2wc(
40-
ReactComponent: React.FC<any> | React.ComponentClass<any>,
41-
config: R2WCOptions = {},
41+
export default function r2wc<Props extends object>(
42+
ReactComponent: React.ComponentType<Props>,
43+
options: R2WCOptions<Props> = {},
4244
): CustomElementConstructor {
43-
return r2wcCore(ReactComponent, config, { mount, unmount, update })
45+
return r2wcCore(ReactComponent, options, { mount, update, unmount })
4446
}

‎vite.config.ts

-42
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.