UNPKG

5.5 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { MessageLoop } from '@lumino/messaging';
4import { Signal } from '@lumino/signaling';
5import { Widget } from '@lumino/widgets';
6import * as React from 'react';
7import * as ReactDOM from 'react-dom';
8/**
9 * An abstract class for a Phosphor widget which renders a React component.
10 */
11export class ReactWidget extends Widget {
12 /**
13 * Creates a new `ReactWidget` that renders a constant element.
14 * @param element React element to render.
15 */
16 static create(element) {
17 return new (class extends ReactWidget {
18 render() {
19 return element;
20 }
21 })();
22 }
23 /**
24 * Called to update the state of the widget.
25 *
26 * The default implementation of this method triggers
27 * VDOM based rendering by calling the `renderDOM` method.
28 */
29 onUpdateRequest(msg) {
30 this.renderPromise = this.renderDOM();
31 }
32 /**
33 * Called after the widget is attached to the DOM
34 */
35 onAfterAttach(msg) {
36 // Make *sure* the widget is rendered.
37 MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
38 }
39 /**
40 * Called before the widget is detached from the DOM.
41 */
42 onBeforeDetach(msg) {
43 // Unmount the component so it can tear down.
44 ReactDOM.unmountComponentAtNode(this.node);
45 }
46 /**
47 * Render the React nodes to the DOM.
48 *
49 * @returns a promise that resolves when the rendering is done.
50 */
51 renderDOM() {
52 return new Promise(resolve => {
53 const vnode = this.render();
54 // Split up the array/element cases so type inference chooses the right
55 // signature.
56 if (Array.isArray(vnode)) {
57 ReactDOM.render(vnode, this.node, resolve);
58 }
59 else if (vnode) {
60 ReactDOM.render(vnode, this.node, resolve);
61 }
62 });
63 }
64}
65/**
66 * An abstract ReactWidget with a model.
67 */
68export class VDomRenderer extends ReactWidget {
69 /**
70 * Create a new VDomRenderer
71 */
72 constructor(model) {
73 super();
74 this._modelChanged = new Signal(this);
75 this.model = (model !== null && model !== void 0 ? model : null);
76 }
77 /**
78 * A signal emitted when the model changes.
79 */
80 get modelChanged() {
81 return this._modelChanged;
82 }
83 /**
84 * Set the model and fire changed signals.
85 */
86 set model(newValue) {
87 if (this._model === newValue) {
88 return;
89 }
90 if (this._model) {
91 this._model.stateChanged.disconnect(this.update, this);
92 }
93 this._model = newValue;
94 if (newValue) {
95 newValue.stateChanged.connect(this.update, this);
96 }
97 this.update();
98 this._modelChanged.emit(void 0);
99 }
100 /**
101 * Get the current model.
102 */
103 get model() {
104 return this._model;
105 }
106 /**
107 * Dispose this widget.
108 */
109 dispose() {
110 if (this.isDisposed) {
111 return;
112 }
113 this._model = null;
114 super.dispose();
115 }
116}
117/**
118 * UseSignal provides a way to hook up a Phosphor signal to a React element,
119 * so that the element is re-rendered every time the signal fires.
120 *
121 * It is implemented through the "render props" technique, using the `children`
122 * prop as a function to render, so that it can be used either as a prop or as a child
123 * of this element
124 * https://reactjs.org/docs/render-props.html
125 *
126 *
127 * Example as child:
128 *
129 * ```
130 * function LiveButton(isActiveSignal: ISignal<any, boolean>) {
131 * return (
132 * <UseSignal signal={isActiveSignal} initialArgs={True}>
133 * {(_, isActive) => <Button isActive={isActive}>}
134 * </UseSignal>
135 * )
136 * }
137 * ```
138 *
139 * Example as prop:
140 *
141 * ```
142 * function LiveButton(isActiveSignal: ISignal<any, boolean>) {
143 * return (
144 * <UseSignal
145 * signal={isActiveSignal}
146 * initialArgs={True}
147 * children={(_, isActive) => <Button isActive={isActive}>}
148 * />
149 * )
150 * }
151 * ```
152 */
153export class UseSignal extends React.Component {
154 constructor(props) {
155 super(props);
156 this.slot = (sender, args) => {
157 // skip setting new state if we have a shouldUpdate function and it returns false
158 if (this.props.shouldUpdate && !this.props.shouldUpdate(sender, args)) {
159 return;
160 }
161 this.setState({ value: [sender, args] });
162 };
163 this.state = { value: [this.props.initialSender, this.props.initialArgs] };
164 }
165 componentDidMount() {
166 this.props.signal.connect(this.slot);
167 }
168 componentWillUnmount() {
169 this.props.signal.disconnect(this.slot);
170 }
171 render() {
172 return this.props.children(...this.state.value);
173 }
174}
175/**
176 * Concrete implementation of VDomRenderer model.
177 */
178export class VDomModel {
179 constructor() {
180 /**
181 * A signal emitted when any model state changes.
182 */
183 this.stateChanged = new Signal(this);
184 this._isDisposed = false;
185 }
186 /**
187 * Test whether the model is disposed.
188 */
189 get isDisposed() {
190 return this._isDisposed;
191 }
192 /**
193 * Dispose the model.
194 */
195 dispose() {
196 if (this.isDisposed) {
197 return;
198 }
199 this._isDisposed = true;
200 Signal.clearData(this);
201 }
202}
203//# sourceMappingURL=vdom.js.map
\No newline at end of file