-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathcontext-connect.js
117 lines (98 loc) · 3.48 KB
/
context-connect.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import {
getInterpolatedClassName,
INTERPOLATION_CLASS_NAME,
} from '@wp-g2/create-styles';
import { uniq } from 'lodash';
import React, { forwardRef } from 'react';
import { CONNECT_STATIC_NAMESPACE } from './constants';
/**
* Forwards ref (React.ForwardRef) and "Connects" (or registers) a component
* within the Context system under a specified namespace.
*
* This is an (experimental) evolution of the initial connect() HOC.
* The hope is that we can improve render performance by removing functional
* component wrappers.
*
* @template {import('@wp-g2/create-styles').ViewOwnProps<{}, any>} P
* @param {(props: P, ref: import('react').Ref<any>) => JSX.Element | null} Component The component to register into the Context system.
* @param {Array<string>|string} namespace The namespace to register the component under.
* @param {object} options
* @param {boolean} [options.memo=true]
* @return {import('@wp-g2/create-styles').PolymorphicComponent<import('@wp-g2/create-styles').ElementTypeFromViewOwnProps<P>, import('@wp-g2/create-styles').PropsFromViewOwnProps<P>>}
*/
export function contextConnect(Component, namespace, options = {}) {
const { memo = true } = options;
let WrappedComponent = forwardRef(Component);
if (memo) {
// @ts-ignore
WrappedComponent = React.memo(WrappedComponent);
}
const displayName = Array.isArray(namespace)
? namespace[0]
: namespace || WrappedComponent.name;
if (process.env.NODE_ENV === 'development') {
if (typeof namespace === 'undefined') {
console.warn('contextConnect', 'Please provide a namespace.');
}
}
let mergedNamespace = WrappedComponent[CONNECT_STATIC_NAMESPACE] || [
displayName,
];
/**
* Consolidate (merge) namespaces before attaching it to the WrappedComponent.
*/
if (Array.isArray(namespace)) {
mergedNamespace = [...mergedNamespace, ...namespace];
}
if (typeof namespace === 'string') {
mergedNamespace = [...mergedNamespace, namespace];
}
WrappedComponent.displayName = displayName;
WrappedComponent[CONNECT_STATIC_NAMESPACE] = uniq(mergedNamespace);
// @ts-ignore internal property
WrappedComponent[INTERPOLATION_CLASS_NAME] = getInterpolatedClassName(
displayName,
);
// @ts-ignore
return WrappedComponent;
}
/**
* Attempts to retrieve the connected namespace from a component.
*
* @param {import('react').ReactChild | undefined | {}} Component The component to retrieve a namespace from.
* @returns {Array<string>} The connected namespaces.
*/
export function getConnectNamespace(Component) {
if (!Component) return [];
let namespaces = [];
if (Component[CONNECT_STATIC_NAMESPACE]) {
namespaces = Component[CONNECT_STATIC_NAMESPACE];
}
// @ts-ignore
if (Component.type && Component.type[CONNECT_STATIC_NAMESPACE]) {
// @ts-ignore
namespaces = Component.type[CONNECT_STATIC_NAMESPACE];
}
return namespaces;
}
/**
* Checks to see if a component is connected within the Context system.
*
* @param {import('react').ReactNode} Component The component to retrieve a namespace from.
* @param {Array<string>|string} match The namespace to check.
* @returns {boolean} The result.
*/
export function hasConnectNamespace(Component, match) {
if (!Component) return false;
if (typeof match === 'string') {
return getConnectNamespace(Component).includes(match);
}
if (Array.isArray(match)) {
return match.some((result) =>
getConnectNamespace(Component).includes(result),
);
}
return false;
}
export const getNamespace = getConnectNamespace;
export const hasNamespace = hasConnectNamespace;