hocs

v2.6.5arrow_drop_down
v2.6.5
v2.5.5
STATUS
Passing
DOWNLOADS
5,082
LICENSE
Apache 2.0
VISIBILITY
Public
PUBLISHED
4 months ago
1 contributor
Like
Use hocs in your project ?
Copied
npm i @bit/grommet.grommet.internal.hocs
Set Bit as a scoped registryLearn more
npm config set '@bit:registry' https://node.bit.dev
Files
components/hocs.js
159 Lines(124 sloc)
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* eslint-disable react/no-multi-comp */
import React, { Component } from 'react';
import getDisplayName from 'recompose/getDisplayName';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { withTheme } from 'styled-components';
import { AnnounceContext } from '../contexts';

export const withFocus = ({ focusWithMouse } = {}) => WrappedComponent => {
  class FocusableComponent extends Component {
    static getDerivedStateFromProps(nextProps, prevState) {
      const { withFocusRef } = nextProps;
      const { wrappedRef } = prevState;
      const nextWrappedRef = withFocusRef || wrappedRef;
      if (nextWrappedRef !== wrappedRef) {
        return { wrappedRef: nextWrappedRef };
      }
      return null;
    }

    mouseActive = false; // not in state because it doesn't affect rendering

    state = {
      focus: false,
      wrappedRef: React.createRef(),
    };

    componentDidMount = () => {
      const { wrappedRef } = this.state;

      // components such as anchors and buttons should not retain focus after
      // being clicked while text-based components should
      if (!focusWithMouse) {
        window.addEventListener('mousedown', this.handleActiveMouse);
      }

      // we could be using onFocus in the wrapper node itself
      // but react does not invoke it if you programically
      // call wrapperNode.focus() inside componentWillUnmount
      // see Drop "this.originalFocusedElement.focus();" for reference
      const wrapperNode = wrappedRef.current;
      if (wrapperNode && wrapperNode.addEventListener) {
        wrapperNode.addEventListener('focus', this.setFocus);
      }
    };

    componentWillUnmount = () => {
      const { wrappedRef } = this.state;
      window.removeEventListener('mousedown', this.handleActiveMouse);
      const wrapperNode = wrappedRef.current;
      if (wrapperNode && wrapperNode.addEventListener) {
        wrapperNode.removeEventListener('focus', this.setFocus);
      }
      clearTimeout(this.focusTimer);
      clearTimeout(this.mouseTimer);
    };

    handleActiveMouse = () => {
      // from https://marcysutton.com/button-focus-hell/
      this.mouseActive = true;
      // this avoids showing focus when clicking around
      clearTimeout(this.mouseTimer);
      // empirical number to reset mouseActive after
      // some time has passed without mousedown
      this.mouseTimer = setTimeout(() => {
        this.mouseActive = false;
      }, 150);
    };

    setFocus = () => {
      // delay setting focus to avoid interupting events,
      // 1ms was chosen empirically based on ie11 using Select and TextInput
      // with and without a FormField.
      clearTimeout(this.focusTimer);
      this.focusTimer = setTimeout(() => {
        const { focus } = this.state;
        if (!focus && !this.mouseActive) {
          this.setState({ focus: true });
        }
      }, 1);
    };

    resetFocus = () => {
      clearTimeout(this.focusTimer);
      this.focusTimer = setTimeout(() => {
        const { focus } = this.state;
        if (focus) {
          this.setState({ focus: false });
        }
      }, 1);
    };

    render() {
      const { onFocus, onBlur, withFocusRef, ...rest } = this.props;
      const { focus, wrappedRef } = this.state;
      return (
        <WrappedComponent
          ref={wrappedRef}
          focus={focus}
          {...rest}
          onFocus={event => {
            this.setFocus();
            if (onFocus) {
              onFocus(event);
            }
          }}
          onBlur={event => {
            this.resetFocus();
            if (onBlur) {
              onBlur(event);
            }
          }}
        />
      );
    }
  }

  const ForwardRef = React.forwardRef((props, ref) => (
    <FocusableComponent {...props} withFocusRef={ref} />
  ));

  ForwardRef.displayName = getDisplayName(WrappedComponent);
  ForwardRef.name = ForwardRef.displayName;
  ForwardRef.defaultProps = WrappedComponent.defaultProps;
  hoistNonReactStatics(ForwardRef, WrappedComponent);

  return ForwardRef;
};

export const withForwardRef = WrappedComponent => {
  const ForwardRefComponent = React.forwardRef((props, ref) => (
    <WrappedComponent forwardRef={ref} {...props} />
  ));

  ForwardRefComponent.displayName = getDisplayName(WrappedComponent);
  ForwardRefComponent.name = ForwardRefComponent.displayName;
  ForwardRefComponent.defaultProps = WrappedComponent.defaultProps;
  hoistNonReactStatics(ForwardRefComponent, WrappedComponent);

  return ForwardRefComponent;
};

export const withAnnounce = WrappedComponent => {
  const ForwardRef = React.forwardRef((props, ref) => (
    <AnnounceContext.Consumer>
      {announce => (
        <WrappedComponent {...props} announce={announce} ref={ref} />
      )}
    </AnnounceContext.Consumer>
  ));

  ForwardRef.displayName = getDisplayName(WrappedComponent);
  ForwardRef.name = ForwardRef.displayName;
  ForwardRef.defaultProps = WrappedComponent.defaultProps;
  hoistNonReactStatics(ForwardRef, WrappedComponent);

  return ForwardRef;
};

export { withTheme };