react-component-instance 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # react-component-instance
2
+
3
+ Get a React class component instance from its `DOM` element
4
+
5
+ ```javascript
6
+ import getInstance from 'react-component-instance'
7
+ ```
8
+
9
+ ## With `@testing-library/react`
10
+
11
+ **Note** that for use with _Testing Library_ import from `react-component-instance/container`
12
+
13
+ ```javascript
14
+ import getInstance from 'react-component-instance/container'
15
+
16
+ import {
17
+ render
18
+ } from '@testing-library/react'
19
+
20
+ const {
21
+ container
22
+ } = render(
23
+ <Component />
24
+ )
25
+
26
+ const instance = getInstance(container)
27
+ ```
package/index.d.mts CHANGED
@@ -1,5 +1,74 @@
1
+ import type React from 'react'
2
+
1
3
  declare global {
2
- namespace ReactComponentInstanceTypes {}
4
+ namespace ReactComponentInstanceTypes {
5
+ /**
6
+ * https://github.com/bendtherules/react-fiber-traverse
7
+ */
8
+
9
+ export type FiberNode =
10
+ | FiberNodeForComponentClass
11
+ | FiberNodeForFunctionComponent
12
+ | FiberNodeForInstrinsicElement
13
+ | FiberNodeForTextNode
14
+
15
+ export interface FiberRootNode {
16
+ child: FiberNode
17
+ sibling: FiberNode | null
18
+
19
+ elementType: React.FunctionComponent
20
+ type: React.FunctionComponent
21
+
22
+ stateNode: {
23
+ current: FiberRootNode
24
+ }
25
+ return: FiberRootNode
26
+ }
27
+
28
+ export interface FiberNodeForFunctionComponent {
29
+ child: FiberNode | null
30
+ sibling: FiberNode | null
31
+
32
+ elementType: React.FunctionComponent
33
+ type: React.FunctionComponent
34
+
35
+ stateNode: null
36
+ return: FiberNode
37
+ }
38
+
39
+ export interface FiberNodeForComponentClass {
40
+ child: FiberNode | null
41
+ sibling: FiberNode | null
42
+
43
+ elementType: React.ComponentClass
44
+ type: React.ComponentClass
45
+
46
+ stateNode: React.Component
47
+ return: FiberNode
48
+ }
49
+
50
+ export interface FiberNodeForInstrinsicElement {
51
+ child: FiberNode | null
52
+ sibling: FiberNode | null
53
+
54
+ elementType: keyof React.JSX.IntrinsicElements
55
+ type: keyof React.JSX.IntrinsicElements
56
+
57
+ stateNode: HTMLElement
58
+ return: FiberNode
59
+ }
60
+
61
+ export interface FiberNodeForTextNode {
62
+ child: null
63
+ sibling: FiberNode | null
64
+
65
+ elementType: null
66
+ type: null
67
+
68
+ stateNode: Text
69
+ return: FiberNode
70
+ }
71
+ }
3
72
  }
4
73
 
5
74
  export {}
package/package.json CHANGED
@@ -1,13 +1,20 @@
1
1
  {
2
2
  "name": "react-component-instance",
3
- "version": "0.0.0",
4
- "main": "./index.mjs",
3
+ "version": "0.0.2",
4
+ "description": "Get a React class component instance from its DOM Element",
5
+ "keywords": [
6
+ "react",
7
+ "class",
8
+ "component",
9
+ "instance"
10
+ ],
11
+ "main": "./src/index.mjs",
5
12
  "type": "module",
6
13
  "types": "./index.d.mts",
7
14
  "author": {
8
- "name": "Jonathan Perry for Sequence Media Limited",
9
- "email": "jonathanperry@sequencemedia.net",
10
- "url": "https://sequencemedia.net"
15
+ "name": "Jonathan Perry for Modern Poacher Limited",
16
+ "email": "jonathanperry@modernpoacher.com",
17
+ "url": "https://modernpoacher.com"
11
18
  },
12
19
  "license": "ISC",
13
20
  "engines": {
@@ -15,19 +22,41 @@
15
22
  },
16
23
  "repository": {
17
24
  "type": "git",
18
- "url": "git://github.com/sequencemedia/react-component-instance.git"
25
+ "url": "git://github.com/modernpoacher/react-component-instance.git"
19
26
  },
20
27
  "scripts": {
21
28
  "lint": "eslint",
22
- "lint:fix": "npm run lint -- --fix"
29
+ "lint:fix": "npm run lint -- --fix",
30
+ "prepare": "husky",
31
+ "test": "NODE_ENV=test jest"
23
32
  },
24
33
  "devDependencies": {
34
+ "@babel/core": "^7.26.10",
35
+ "@babel/eslint-parser": "^7.26.10",
36
+ "@babel/preset-env": "^7.26.9",
37
+ "@babel/preset-react": "^7.26.3",
25
38
  "@sequencemedia/eslint-config-standard": "^0.2.42",
26
39
  "@sequencemedia/eslint-config-typescript": "^0.1.60",
40
+ "@testing-library/jest-dom": "^6.6.3",
41
+ "@testing-library/react": "^16.2.0",
42
+ "@types/debug": "^4.1.12",
43
+ "@types/jest": "^29.5.14",
44
+ "@types/react": "^19.0.10",
45
+ "core-js": "^3.41.0",
27
46
  "eslint": "^9.22.0",
28
- "globals": "^16.0.0"
47
+ "eslint-plugin-react": "^7.37.4",
48
+ "globals": "^16.0.0",
49
+ "husky": "^9.1.7",
50
+ "jest": "^29.7.0",
51
+ "jest-environment-jsdom": "^29.7.0",
52
+ "react": "^19.0.0"
53
+ },
54
+ "imports": {
55
+ "#react-component-instance": "./src/index.mjs",
56
+ "#react-component-instance/container": "./src/container/index.mjs"
29
57
  },
30
58
  "exports": {
31
- ".": "./src/index.mjs"
59
+ ".": "./src/index.mjs",
60
+ "./container": "./src/container/index.mjs"
32
61
  }
33
62
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @typedef {ReactComponentInstanceTypes.FiberRootNode} FiberRootNode
3
+ */
4
+
5
+ /**
6
+ * @param {string} key
7
+ * @returns {boolean}
8
+ */
9
+ function isReactContainerKey (key) {
10
+ return key.startsWith('__reactContainer$')
11
+ }
12
+
13
+ /**
14
+ * @param {HTMLElement} container
15
+ * @returns {React.Component<any, any, any> | HTMLElement | Text | null}
16
+ */
17
+ export default function getReactComponentInstanceFrom (container) {
18
+ const key = (
19
+ Object.keys(container)
20
+ .find(isReactContainerKey)
21
+ )
22
+
23
+ if (key) {
24
+ const { // @ts-expect-error
25
+ [key]: fiberNode
26
+ } = container
27
+
28
+ if (fiberNode) {
29
+ const {
30
+ stateNode: {
31
+ current: {
32
+ child: {
33
+ stateNode
34
+ }
35
+ }
36
+ }
37
+ } = fiberNode
38
+
39
+ return stateNode
40
+ }
41
+ }
42
+
43
+ throw new Error('Component instance is not found')
44
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @typedef {ReactComponentInstanceTypes.FiberNode} FiberNode
3
+ */
4
+
5
+ /**
6
+ * @param {PropertyKey} key
7
+ * @returns {boolean}
8
+ */
9
+ function isReactFiberKey (key) {
10
+ return String(key).startsWith('__reactFiber$')
11
+ }
12
+
13
+ /**
14
+ * @param {FiberNode} fiberNode
15
+ * @returns {Generator<FiberNode, void, unknown>}
16
+ */
17
+ function * traverseFiberNodeTree (fiberNode) {
18
+ let currentNode = fiberNode
19
+
20
+ while (true) {
21
+ yield currentNode
22
+
23
+ currentNode = currentNode.return
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {Element | null | undefined} element
29
+ * @param {(() => React.JSX.Element) | typeof React.Component} Component
30
+ * @returns {React.Component<any, any, any> | HTMLElement | Text | null}
31
+ */
32
+ export function findReactComponentInstanceFor (element, Component) {
33
+ if (element instanceof HTMLElement) {
34
+ const key = (
35
+ Object.keys(element)
36
+ .find(isReactFiberKey)
37
+ )
38
+
39
+ if (key) {
40
+ const { // @ts-expect-error
41
+ [key]: fiberNode
42
+ } = element
43
+
44
+ if (fiberNode) {
45
+ for (const fiber of traverseFiberNodeTree(fiberNode)) {
46
+ const {
47
+ type
48
+ } = fiber
49
+
50
+ if (type === Component) {
51
+ const {
52
+ stateNode
53
+ } = fiber
54
+
55
+ return stateNode
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ throw new Error('Component instance is not found')
62
+ }
63
+
64
+ throw new Error('Element is not an Element')
65
+ }
66
+
67
+ /**
68
+ * @param {Element | null} element
69
+ * @returns {React.Component<any, any, any> | HTMLElement | Text | null}
70
+ */
71
+ export default function getReactComponentInstanceFrom (element) {
72
+ if (element instanceof HTMLElement) {
73
+ const key = (
74
+ Object.keys(element)
75
+ .find(isReactFiberKey)
76
+ )
77
+
78
+ if (key) {
79
+ const { // @ts-expect-error
80
+ [key]: fiberNode
81
+ } = element
82
+
83
+ if (fiberNode) {
84
+ /**
85
+ * `return` is the component which owns `element`
86
+ */
87
+ const {
88
+ return: {
89
+ stateNode
90
+ }
91
+ } = fiberNode
92
+
93
+ return stateNode
94
+ }
95
+ }
96
+
97
+ throw new Error('Component instance is not found')
98
+ }
99
+
100
+ throw new Error('Element is not an Element')
101
+ }
package/tsconfig.json CHANGED
@@ -5,7 +5,8 @@
5
5
  "allowJs": true,
6
6
  "checkJs": true,
7
7
  "noEmit": true,
8
- "strict": true
8
+ "strict": true,
9
+ "jsx": "react"
9
10
  },
10
11
  "include": [
11
12
  "."
package/eslint.config.mjs DELETED
@@ -1,36 +0,0 @@
1
- import globals from 'globals'
2
-
3
- import {
4
- configs as standard
5
- } from '@sequencemedia/eslint-config-standard'
6
-
7
- import {
8
- configs as typescript
9
- } from '@sequencemedia/eslint-config-typescript'
10
-
11
- export default [
12
- {
13
- ...standard.recommended,
14
- files: [
15
- '**/*.{mjs,cjs,mts,cts}'
16
- ],
17
- languageOptions: {
18
- ...standard.recommended.languageOptions,
19
- globals: {
20
- ...globals.node
21
- }
22
- }
23
- },
24
- {
25
- ...typescript.recommended,
26
- files: [
27
- '**/*.{mts,cts}'
28
- ],
29
- languageOptions: {
30
- ...typescript.recommended.languageOptions,
31
- globals: {
32
- ...globals.node
33
- }
34
- }
35
- }
36
- ]
package/index.mjs DELETED
File without changes