react-component-instance 0.0.0 → 0.0.1

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` elements
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
+ ```
@@ -0,0 +1,42 @@
1
+ const debug = require('debug')
2
+
3
+ const log = debug('react-component-instance')
4
+
5
+ const {
6
+ env: {
7
+ NODE_ENV = 'development'
8
+ }
9
+ } = process
10
+
11
+ log('`react-component-instance` is awake')
12
+
13
+ function env () {
14
+ log({ NODE_ENV })
15
+
16
+ return (
17
+ NODE_ENV === 'production'
18
+ )
19
+ }
20
+
21
+ const presets = [
22
+ [
23
+ '@babel/env',
24
+ {
25
+ targets: {
26
+ node: 'current'
27
+ },
28
+ useBuiltIns: 'usage',
29
+ corejs: 3
30
+ }
31
+ ],
32
+ '@babel/react'
33
+ ]
34
+
35
+ // @ts-ignore
36
+ module.exports = (api) => {
37
+ if (api) api.cache.using(env)
38
+
39
+ return {
40
+ presets
41
+ }
42
+ }
package/eslint.config.mjs CHANGED
@@ -1,36 +1,168 @@
1
1
  import globals from 'globals'
2
+ import standard from '@sequencemedia/eslint-config-standard/configs/recommended/merge'
3
+ import typescript from '@sequencemedia/eslint-config-typescript/configs/recommended/merge'
4
+ import babelParser from '@babel/eslint-parser'
5
+ import typescriptParser from '@typescript-eslint/parser'
6
+ import reactPlugin from 'eslint-plugin-react'
2
7
 
3
- import {
4
- configs as standard
5
- } from '@sequencemedia/eslint-config-standard'
8
+ const reactParserOptions = {
9
+ ecmaFeatures: {
10
+ jsx: true
11
+ }
12
+ }
13
+
14
+ const reactPlugins = {
15
+ react: reactPlugin
16
+ }
17
+
18
+ const reactRules = {
19
+ 'no-unused-vars': [
20
+ 'error',
21
+ {
22
+ varsIgnorePattern: 'React'
23
+ }
24
+ ],
25
+ quotes: [
26
+ 'error',
27
+ 'single'
28
+ ],
29
+ 'jsx-quotes': [
30
+ 'error',
31
+ 'prefer-single'
32
+ ],
33
+ 'react/jsx-indent': [
34
+ 'error',
35
+ 2,
36
+ {
37
+ checkAttributes: true,
38
+ indentLogicalExpressions: true
39
+ }
40
+ ]
41
+ }
6
42
 
7
- import {
8
- configs as typescript
9
- } from '@sequencemedia/eslint-config-typescript'
43
+ const reactSettings = {
44
+ react: {
45
+ version: 'detect'
46
+ }
47
+ }
10
48
 
11
49
  export default [
50
+ /**
51
+ * React config for all `jsx` files
52
+ */
12
53
  {
13
- ...standard.recommended,
54
+ ...reactPlugin.configs.flat.recommended,
55
+ settings: {
56
+ ...reactSettings
57
+ }
58
+ },
59
+ /**
60
+ * Standard config
61
+ */
62
+ standard({
14
63
  files: [
15
64
  '**/*.{mjs,cjs,mts,cts}'
16
65
  ],
66
+ ignores: [
67
+ 'src'
68
+ ],
17
69
  languageOptions: {
18
- ...standard.recommended.languageOptions,
19
70
  globals: {
20
71
  ...globals.node
21
72
  }
73
+ },
74
+ rules: {
75
+ 'no-use-before-define': 'off'
22
76
  }
23
- },
24
- {
25
- ...typescript.recommended,
77
+ }),
78
+ /**
79
+ * Standard config for `mjs` and `mts` files
80
+ */
81
+ standard({
82
+ files: [
83
+ 'src/**/*.{mjs,cjs,mts,cts}'
84
+ ],
85
+ languageOptions: {
86
+ globals: {
87
+ ...globals.browser
88
+ }
89
+ }
90
+ }),
91
+ /**
92
+ * Standard config for `jsx` files
93
+ */
94
+ standard({
95
+ files: [
96
+ 'src/**/*.jsx'
97
+ ],
98
+ ignores: [
99
+ 'src/**/__tests__/**/*.jsx'
100
+ ],
101
+ languageOptions: {
102
+ parser: babelParser,
103
+ parserOptions: {
104
+ ...reactParserOptions,
105
+ project: null
106
+ },
107
+ globals: {
108
+ ...globals.browser
109
+ }
110
+ },
111
+ plugins: {
112
+ ...reactPlugins
113
+ }, // @ts-expect-error Storybook
114
+ rules: {
115
+ ...reactRules
116
+ },
117
+ settings: {
118
+ ...reactSettings
119
+ }
120
+ }),
121
+ /**
122
+ * Standard config for `jsx` files
123
+ */
124
+ standard({
125
+ files: [
126
+ 'src/**/__tests__/**/*.jsx'
127
+ ],
128
+ languageOptions: {
129
+ parser: babelParser,
130
+ parserOptions: {
131
+ ...reactParserOptions,
132
+ project: null
133
+ },
134
+ globals: {
135
+ ...globals.browser,
136
+ ...globals.jest
137
+ }
138
+ },
139
+ plugins: {
140
+ ...reactPlugins
141
+ }, // @ts-expect-error Storybook
142
+ rules: {
143
+ ...reactRules
144
+ },
145
+ settings: {
146
+ ...reactSettings
147
+ }
148
+ }),
149
+ /**
150
+ * TypeScript config
151
+ */
152
+ typescript({
26
153
  files: [
27
154
  '**/*.{mts,cts}'
28
155
  ],
29
156
  languageOptions: {
30
- ...typescript.recommended.languageOptions,
157
+ parser: typescriptParser,
158
+ parserOptions: {
159
+ projectService: true,
160
+ project: 'tsconfig.json'
161
+ },
31
162
  globals: {
32
- ...globals.node
163
+ ...globals.browser,
164
+ ReactComponentInstanceTypes: 'readonly'
33
165
  }
34
166
  }
35
- }
167
+ })
36
168
  ]
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 {}
@@ -0,0 +1,15 @@
1
+ export default {
2
+ bail: 1,
3
+ verbose: true,
4
+ rootDir: '.',
5
+ roots: [
6
+ './src'
7
+ ],
8
+ testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(mjs?|jsx?)$',
9
+ testEnvironment: 'jsdom',
10
+ transform: {
11
+ '^.+\\.jsx$': 'babel-jest',
12
+ '^.+\\.mjs$': 'babel-jest'
13
+ },
14
+ moduleFileExtensions: ['js', 'mjs', 'cjs', 'jsx']
15
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "react-component-instance",
3
- "version": "0.0.0",
4
- "main": "./index.mjs",
3
+ "version": "0.0.1",
4
+ "main": "./src/index.mjs",
5
5
  "type": "module",
6
6
  "types": "./index.d.mts",
7
7
  "author": {
8
- "name": "Jonathan Perry for Sequence Media Limited",
9
- "email": "jonathanperry@sequencemedia.net",
10
- "url": "https://sequencemedia.net"
8
+ "name": "Jonathan Perry for Modern Poacher Limited",
9
+ "email": "jonathanperry@modernpoacher.com",
10
+ "url": "https://modernpoacher.com"
11
11
  },
12
12
  "license": "ISC",
13
13
  "engines": {
@@ -15,19 +15,41 @@
15
15
  },
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git://github.com/sequencemedia/react-component-instance.git"
18
+ "url": "git://github.com/modernpoacher/react-component-instance.git"
19
19
  },
20
20
  "scripts": {
21
21
  "lint": "eslint",
22
- "lint:fix": "npm run lint -- --fix"
22
+ "lint:fix": "npm run lint -- --fix",
23
+ "test": "NODE_ENV=test jest",
24
+ "prepare": "husky"
23
25
  },
24
26
  "devDependencies": {
27
+ "@babel/core": "^7.26.10",
28
+ "@babel/eslint-parser": "^7.26.10",
29
+ "@babel/preset-env": "^7.26.9",
30
+ "@babel/preset-react": "^7.26.3",
25
31
  "@sequencemedia/eslint-config-standard": "^0.2.42",
26
32
  "@sequencemedia/eslint-config-typescript": "^0.1.60",
33
+ "@testing-library/jest-dom": "^6.6.3",
34
+ "@testing-library/react": "^16.2.0",
35
+ "@types/debug": "^4.1.12",
36
+ "@types/jest": "^29.5.14",
37
+ "@types/react": "^19.0.10",
38
+ "core-js": "^3.41.0",
27
39
  "eslint": "^9.22.0",
28
- "globals": "^16.0.0"
40
+ "eslint-plugin-react": "^7.37.4",
41
+ "globals": "^16.0.0",
42
+ "husky": "^9.1.7",
43
+ "jest": "^29.7.0",
44
+ "jest-environment-jsdom": "^29.7.0",
45
+ "react": "^19.0.0"
46
+ },
47
+ "imports": {
48
+ "#react-component-instance": "./src/index.mjs",
49
+ "#react-component-instance/container": "./src/container/index.mjs"
29
50
  },
30
51
  "exports": {
31
- ".": "./src/index.mjs"
52
+ ".": "./src/index.mjs",
53
+ "./container": "./src/container/index.mjs"
32
54
  }
33
55
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @typedef {ReactComponentInstanceTypes.FiberNode} FiberNode
3
+ */
4
+
5
+ import React from 'react'
6
+
7
+ import '@testing-library/jest-dom'
8
+ import {
9
+ render
10
+ } from '@testing-library/react'
11
+
12
+ import getReactComponentInstanceFrom, {
13
+ findReactComponentInstanceFor
14
+ } from '#react-component-instance'
15
+
16
+ describe('#react-component-instance', () => {
17
+ class Component extends React.Component {
18
+ render () {
19
+ return (
20
+ <div>
21
+ <div>
22
+ <div className='target' />
23
+ </div>
24
+ </div>
25
+ )
26
+ }
27
+ }
28
+
29
+ describe('`getReactComponentInstanceFrom`', () => {
30
+ it('gets the instance', () => {
31
+ const {
32
+ container: {
33
+ firstElementChild: element
34
+ }
35
+ } = render(
36
+ <Component />
37
+ )
38
+
39
+ expect(getReactComponentInstanceFrom(element))
40
+ .toBeInstanceOf(Component)
41
+ })
42
+ })
43
+
44
+ describe('`findReactComponentInstanceFor`', () => {
45
+ it('gets the instance', () => {
46
+ const {
47
+ container: {
48
+ firstElementChild: element
49
+ }
50
+ } = render(
51
+ <Component />
52
+ )
53
+
54
+ expect(findReactComponentInstanceFor(element?.querySelector('.target'), Component))
55
+ .toBeInstanceOf(Component)
56
+ })
57
+ })
58
+ })
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+
3
+ import '@testing-library/jest-dom'
4
+ import {
5
+ render
6
+ } from '@testing-library/react'
7
+
8
+ import getReactComponentInstanceFrom from '#react-component-instance/container'
9
+
10
+ describe('#react-component-instance/container', () => {
11
+ describe('`getReactComponentInstanceFrom`', () => {
12
+ class Component extends React.Component {
13
+ render () {
14
+ return <div />
15
+ }
16
+ }
17
+
18
+ it('gets the instance', () => {
19
+ const {
20
+ container
21
+ } = render(
22
+ <Component />
23
+ )
24
+
25
+ expect(getReactComponentInstanceFrom(container))
26
+ .toBeInstanceOf(Component)
27
+ })
28
+ })
29
+ })
@@ -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/index.mjs DELETED
File without changes