1 | // Copyright (c) Jupyter Development Team.
|
2 | // Distributed under the terms of the Modified BSD License.
|
3 | import { MessageLoop } from '@lumino/messaging';
|
4 | import { Signal } from '@lumino/signaling';
|
5 | import { Widget } from '@lumino/widgets';
|
6 | import * as React from 'react';
|
7 | import * as ReactDOM from 'react-dom';
|
8 | /**
|
9 | * An abstract class for a Phosphor widget which renders a React component.
|
10 | */
|
11 | export 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 | */
|
68 | export 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 | */
|
153 | export 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 | */
|
178 | export 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 |