UNPKG

5.2 kBJavaScriptView Raw
1/**
2 * React Blessed Component
3 * ========================
4 *
5 * React component abstraction for the blessed library.
6 */
7import blessed from 'blessed';
8import ReactMultiChild from 'react/lib/ReactMultiChild';
9import ReactBlessedIDOperations from './ReactBlessedIDOperations';
10import invariant from 'invariant';
11import update from './update';
12import solveClass from './solveClass';
13import {extend, groupBy, startCase} from 'lodash';
14
15/**
16 * Variable types that must be solved as content rather than real children.
17 */
18const CONTENT_TYPES = {string: true, number: true};
19
20/**
21 * Renders the given react element with blessed.
22 *
23 * @constructor ReactBlessedComponent
24 * @extends ReactMultiChild
25 */
26export default class ReactBlessedComponent {
27 constructor(tag) {
28 this._tag = tag.toLowerCase();
29 this._updating = false;
30 this._renderedChildren = null;
31 this._previousStyle = null;
32 this._previousStyleCopy = null;
33 this._rootNodeID = null;
34 this._wrapperState = null;
35 this._topLevelWrapper = null;
36 this._nodeWithLegacyProperties = null;
37 }
38
39 construct(element) {
40
41 // Setting some properties
42 this._currentElement = element;
43 this._eventListener = (type, ...args) => {
44 if (this._updating) return;
45
46 const handler = this._currentElement.props['on' + startCase(type).replace(/ /g, '')];
47
48 if (typeof handler === 'function') {
49 if (type === 'focus' || type === 'blur') {
50 args[0] = ReactBlessedIDOperations.get(this._rootNodeID)
51 }
52 handler(...args);
53 }
54 };
55 }
56
57 /**
58 * Mounting the root component.
59 *
60 * @internal
61 * @param {string} rootID - The root blessed ID for this node.
62 * @param {ReactBlessedReconcileTransaction} transaction
63 * @param {object} context
64 */
65 mountComponent(rootID, transaction, context) {
66 this._rootNodeID = rootID;
67
68 // Mounting blessed node
69 const node = this.mountNode(
70 ReactBlessedIDOperations.getParent(rootID),
71 this._currentElement
72 );
73
74 ReactBlessedIDOperations.add(rootID, node);
75
76 // Mounting children
77 let childrenToUse = this._currentElement.props.children;
78 childrenToUse = childrenToUse === null ? [] : [].concat(childrenToUse);
79
80 if (childrenToUse.length) {
81
82 // Discriminating content components from real children
83 const {content=null, realChildren=[]} = groupBy(childrenToUse, (c) => {
84 return CONTENT_TYPES[typeof c] ? 'content' : 'realChildren';
85 });
86
87 // Setting textual content
88 if (content)
89 node.setContent('' + content.join(''));
90
91 // Mounting real children
92 this.mountChildren(
93 realChildren,
94 transaction,
95 context
96 );
97 }
98
99 // Rendering the screen
100 ReactBlessedIDOperations.screen.debouncedRender();
101 }
102
103 /**
104 * Mounting the blessed node itself.
105 *
106 * @param {BlessedNode|BlessedScreen} parent - The parent node.
107 * @param {ReactElement} element - The element to mount.
108 * @return {BlessedNode} - The mounted node.
109 */
110 mountNode(parent, element) {
111 const {props, type} = element,
112 {children, ...options} = props,
113 blessedElement = blessed[type];
114
115 invariant(
116 !!blessedElement,
117 `Invalid blessed element "${type}".`
118 );
119
120 const node = blessed[type](solveClass(options));
121
122 node.on('event', this._eventListener);
123 parent.append(node);
124
125 return node;
126 }
127
128 /**
129 * Receive a component update.
130 *
131 * @param {ReactElement} nextElement
132 * @param {ReactReconcileTransaction} transaction
133 * @param {object} context
134 * @internal
135 * @overridable
136 */
137 receiveComponent(nextElement, transaction, context) {
138 const {props: {children, ...options}} = nextElement,
139 node = ReactBlessedIDOperations.get(this._rootNodeID);
140
141 this._updating = true;
142 update(node, solveClass(options));
143 this._updating = false;
144
145 // Updating children
146 const childrenToUse = children === null ? [] : [].concat(children);
147
148 // Discriminating content components from real children
149 const {content=null, realChildren=[]} = groupBy(childrenToUse, (c) => {
150 return CONTENT_TYPES[typeof c] ? 'content' : 'realChildren';
151 });
152
153 // Setting textual content
154 if (content)
155 node.setContent('' + content.join(''));
156
157 this.updateChildren(realChildren, transaction, context);
158
159 ReactBlessedIDOperations.screen.debouncedRender();
160 }
161
162 /**
163 * Dropping the component.
164 */
165 unmountComponent() {
166 this.unmountChildren();
167
168 const node = ReactBlessedIDOperations.get(this._rootNodeID);
169
170 node.off('event', this._eventListener);
171 node.destroy();
172
173 ReactBlessedIDOperations.drop(this._rootNodeID);
174
175 this._rootNodeID = null;
176
177 ReactBlessedIDOperations.screen.debouncedRender();
178 }
179
180 /**
181 * Getting a public instance of the component for refs.
182 *
183 * @return {BlessedNode} - The instance's node.
184 */
185 getPublicInstance() {
186 return ReactBlessedIDOperations.get(this._rootNodeID);
187 }
188}
189
190/**
191 * Extending the component with the MultiChild mixin.
192 */
193extend(
194 ReactBlessedComponent.prototype,
195 ReactMultiChild.Mixin
196);