1 | import React, { Component } from 'react';
|
2 | import { debounce } from 'lodash-es';
|
3 | import autoprefix from './autoprefix.js';
|
4 | function autoprefixes(styles) {
|
5 | return Object.keys(styles).reduce((obj, key) => ((obj[key] = autoprefix(styles[key])), obj), {});
|
6 | }
|
7 | const styles = autoprefixes({
|
8 | wrapper: {
|
9 | position: 'fixed',
|
10 | width: 0,
|
11 | height: 0,
|
12 | top: 0,
|
13 | left: 0,
|
14 | },
|
15 | dim: {
|
16 | position: 'fixed',
|
17 | left: 0,
|
18 | right: 0,
|
19 | top: 0,
|
20 | bottom: 0,
|
21 | zIndex: 0,
|
22 | background: 'rgba(0, 0, 0, 0.2)',
|
23 | opacity: 1,
|
24 | },
|
25 | dimAppear: {
|
26 | opacity: 0,
|
27 | },
|
28 | dimTransparent: {
|
29 | pointerEvents: 'none',
|
30 | },
|
31 | dimHidden: {
|
32 | opacity: 0,
|
33 | },
|
34 | dock: {
|
35 | position: 'fixed',
|
36 | zIndex: 1,
|
37 | boxShadow: '0 0 4px rgba(0, 0, 0, 0.3)',
|
38 | background: 'white',
|
39 | left: 0,
|
40 | top: 0,
|
41 | width: '100%',
|
42 | height: '100%',
|
43 | },
|
44 | dockHidden: {
|
45 | opacity: 0,
|
46 | },
|
47 | dockResizing: {
|
48 | transition: 'none',
|
49 | },
|
50 | dockContent: {
|
51 | width: '100%',
|
52 | height: '100%',
|
53 | overflow: 'auto',
|
54 | },
|
55 | resizer: {
|
56 | position: 'absolute',
|
57 | zIndex: 2,
|
58 | opacity: 0,
|
59 | },
|
60 | });
|
61 | function getTransitions(duration) {
|
62 | return ['left', 'top', 'width', 'height'].map((p) => `${p} ${duration / 1000}s ease-out`);
|
63 | }
|
64 | function getDockStyles({ fluid, dockStyle, dockHiddenStyle, duration, position, isVisible }, { size, isResizing, fullWidth, fullHeight }) {
|
65 | let posStyle;
|
66 | const absSize = fluid ? `${size * 100}%` : `${size}px`;
|
67 | function getRestSize(fullSize) {
|
68 | return fluid ? `${100 - size * 100}%` : `${fullSize - size}px`;
|
69 | }
|
70 | switch (position) {
|
71 | case 'left':
|
72 | posStyle = {
|
73 | width: absSize,
|
74 | left: isVisible ? 0 : '-' + absSize,
|
75 | };
|
76 | break;
|
77 | case 'right':
|
78 | posStyle = {
|
79 | left: isVisible ? getRestSize(fullWidth) : fullWidth,
|
80 | width: absSize,
|
81 | };
|
82 | break;
|
83 | case 'top':
|
84 | posStyle = {
|
85 | top: isVisible ? 0 : '-' + absSize,
|
86 | height: absSize,
|
87 | };
|
88 | break;
|
89 | case 'bottom':
|
90 | posStyle = {
|
91 | top: isVisible ? getRestSize(fullHeight) : fullHeight,
|
92 | height: absSize,
|
93 | };
|
94 | break;
|
95 | }
|
96 | const transitions = getTransitions(duration);
|
97 | return [
|
98 | styles.dock,
|
99 | autoprefix({
|
100 | transition: [
|
101 | ...transitions,
|
102 | !isVisible && `opacity 0.01s linear ${duration / 1000}s`,
|
103 | ]
|
104 | .filter((t) => t)
|
105 | .join(','),
|
106 | }),
|
107 | dockStyle,
|
108 | autoprefix(posStyle),
|
109 | isResizing && styles.dockResizing,
|
110 | !isVisible && styles.dockHidden,
|
111 | !isVisible && dockHiddenStyle,
|
112 | ];
|
113 | }
|
114 | function getDimStyles({ dimMode, dimStyle, duration, isVisible }, { isTransitionStarted }) {
|
115 | return [
|
116 | styles.dim,
|
117 | autoprefix({
|
118 | transition: `opacity ${duration / 1000}s ease-out`,
|
119 | }),
|
120 | dimStyle,
|
121 | dimMode === 'transparent' && styles.dimTransparent,
|
122 | !isVisible && styles.dimHidden,
|
123 | isTransitionStarted && isVisible && styles.dimAppear,
|
124 | isTransitionStarted && !isVisible && styles.dimDisappear,
|
125 | ];
|
126 | }
|
127 | function getResizerStyles(position) {
|
128 | let resizerStyle;
|
129 | const size = 10;
|
130 | switch (position) {
|
131 | case 'left':
|
132 | resizerStyle = {
|
133 | right: -size / 2,
|
134 | width: size,
|
135 | top: 0,
|
136 | height: '100%',
|
137 | cursor: 'col-resize',
|
138 | };
|
139 | break;
|
140 | case 'right':
|
141 | resizerStyle = {
|
142 | left: -size / 2,
|
143 | width: size,
|
144 | top: 0,
|
145 | height: '100%',
|
146 | cursor: 'col-resize',
|
147 | };
|
148 | break;
|
149 | case 'top':
|
150 | resizerStyle = {
|
151 | bottom: -size / 2,
|
152 | height: size,
|
153 | left: 0,
|
154 | width: '100%',
|
155 | cursor: 'row-resize',
|
156 | };
|
157 | break;
|
158 | case 'bottom':
|
159 | resizerStyle = {
|
160 | top: -size / 2,
|
161 | height: size,
|
162 | left: 0,
|
163 | width: '100%',
|
164 | cursor: 'row-resize',
|
165 | };
|
166 | break;
|
167 | }
|
168 | return [styles.resizer, autoprefix(resizerStyle)];
|
169 | }
|
170 | function getFullSize(position, fullWidth, fullHeight) {
|
171 | return position === 'left' || position === 'right' ? fullWidth : fullHeight;
|
172 | }
|
173 | class Dock extends Component {
|
174 | constructor() {
|
175 | super(...arguments);
|
176 | this.state = {
|
177 | isControlled: typeof this.props.size !== 'undefined',
|
178 | size: this.props.size || this.props.defaultSize,
|
179 | isDimHidden: !this.props.isVisible,
|
180 | fullWidth: window.innerWidth,
|
181 | fullHeight: window.innerHeight,
|
182 | isTransitionStarted: false,
|
183 | isWindowResizing: false,
|
184 | };
|
185 | this.transitionEnd = () => {
|
186 | this.setState({ isTransitionStarted: false });
|
187 | };
|
188 | this.hideDim = () => {
|
189 | if (!this.props.isVisible) {
|
190 | this.setState({ isDimHidden: true });
|
191 | }
|
192 | };
|
193 | this.handleDimClick = () => {
|
194 | if (this.props.dimMode === 'opaque') {
|
195 | this.props.onVisibleChange && this.props.onVisibleChange(false);
|
196 | }
|
197 | };
|
198 | this.handleResize = () => {
|
199 | if (window.requestAnimationFrame) {
|
200 | window.requestAnimationFrame(this.updateWindowSize.bind(this, true));
|
201 | }
|
202 | else {
|
203 | this.updateWindowSize(true);
|
204 | }
|
205 | };
|
206 | this.updateWindowSize = (windowResize) => {
|
207 | const sizeState = {
|
208 | fullWidth: window.innerWidth,
|
209 | fullHeight: window.innerHeight,
|
210 | };
|
211 | if (windowResize) {
|
212 | this.setState({
|
213 | ...sizeState,
|
214 | isResizing: true,
|
215 | isWindowResizing: windowResize,
|
216 | });
|
217 | this.debouncedUpdateWindowSizeEnd();
|
218 | }
|
219 | else {
|
220 | this.setState(sizeState);
|
221 | }
|
222 | };
|
223 | this.updateWindowSizeEnd = () => {
|
224 | this.setState({
|
225 | isResizing: false,
|
226 | isWindowResizing: false,
|
227 | });
|
228 | };
|
229 | this.debouncedUpdateWindowSizeEnd = debounce(this.updateWindowSizeEnd, 30);
|
230 | this.handleWrapperLeave = () => {
|
231 | this.setState({ isResizing: false });
|
232 | };
|
233 | this.handleMouseDown = () => {
|
234 | this.setState({ isResizing: true });
|
235 | };
|
236 | this.handleMouseUp = () => {
|
237 | this.setState({ isResizing: false });
|
238 | };
|
239 | this.handleMouseMove = (e) => {
|
240 | if (!this.state.isResizing || this.state.isWindowResizing)
|
241 | return;
|
242 | if (!e.touches)
|
243 | e.preventDefault();
|
244 | const { position, fluid } = this.props;
|
245 | const { fullWidth, fullHeight, isControlled } = this.state;
|
246 | let { clientX: x, clientY: y } = e;
|
247 | if (e.touches) {
|
248 | x = e.touches[0].clientX;
|
249 | y = e.touches[0].clientY;
|
250 | }
|
251 | let size;
|
252 | switch (position) {
|
253 | case 'left':
|
254 | size = fluid ? x / fullWidth : x;
|
255 | break;
|
256 | case 'right':
|
257 | size = fluid ? (fullWidth - x) / fullWidth : fullWidth - x;
|
258 | break;
|
259 | case 'top':
|
260 | size = fluid ? y / fullHeight : y;
|
261 | break;
|
262 | case 'bottom':
|
263 | size = fluid ? (fullHeight - y) / fullHeight : fullHeight - y;
|
264 | break;
|
265 | }
|
266 | this.props.onSizeChange && this.props.onSizeChange(size);
|
267 | if (!isControlled) {
|
268 | this.setState({ size });
|
269 | }
|
270 | };
|
271 | }
|
272 | componentDidMount() {
|
273 | window.addEventListener('touchend', this.handleMouseUp);
|
274 | window.addEventListener('mouseup', this.handleMouseUp);
|
275 | window.addEventListener('touchmove', this.handleMouseMove);
|
276 | window.addEventListener('mousemove', this.handleMouseMove);
|
277 | window.addEventListener('resize', this.handleResize);
|
278 | this.updateWindowSize();
|
279 | }
|
280 | componentWillUnmount() {
|
281 | window.removeEventListener('touchend', this.handleMouseUp);
|
282 | window.removeEventListener('mouseup', this.handleMouseUp);
|
283 | window.removeEventListener('touchmove', this.handleMouseMove);
|
284 | window.removeEventListener('mousemove', this.handleMouseMove);
|
285 | window.removeEventListener('resize', this.handleResize);
|
286 | }
|
287 | UNSAFE_componentWillReceiveProps(nextProps) {
|
288 | const isControlled = typeof nextProps.size !== 'undefined';
|
289 | this.setState({ isControlled });
|
290 | if (isControlled && nextProps.size && this.props.size !== nextProps.size) {
|
291 | this.setState({ size: nextProps.size });
|
292 | }
|
293 | else if (this.props.fluid !== nextProps.fluid) {
|
294 | this.updateSize(nextProps);
|
295 | }
|
296 | if (this.props.isVisible !== nextProps.isVisible) {
|
297 | this.setState({
|
298 | isTransitionStarted: true,
|
299 | });
|
300 | }
|
301 | }
|
302 | updateSize(props) {
|
303 | const { fullWidth, fullHeight } = this.state;
|
304 | this.setState({
|
305 | size: props.fluid
|
306 | ? this.state.size / getFullSize(props.position, fullWidth, fullHeight)
|
307 | : getFullSize(props.position, fullWidth, fullHeight) * this.state.size,
|
308 | });
|
309 | }
|
310 | componentDidUpdate(prevProps) {
|
311 | if (this.props.isVisible !== prevProps.isVisible) {
|
312 | if (!this.props.isVisible) {
|
313 | window.setTimeout(() => this.hideDim(), this.props.duration);
|
314 | }
|
315 | else {
|
316 | this.setState({ isDimHidden: false });
|
317 | }
|
318 | window.setTimeout(() => this.setState({ isTransitionStarted: false }), 0);
|
319 | }
|
320 | }
|
321 | render() {
|
322 | const { children, zIndex, dimMode, position, isVisible } = this.props;
|
323 | const { isResizing, size, isDimHidden } = this.state;
|
324 | const dimStyles = Object.assign({}, ...getDimStyles(this.props, this.state));
|
325 | const dockStyles = Object.assign({}, ...getDockStyles(this.props, this.state));
|
326 | const resizerStyles = Object.assign({}, ...getResizerStyles(position));
|
327 | return (React.createElement("div", { style: Object.assign({}, styles.wrapper, { zIndex }) },
|
328 | dimMode !== 'none' && !isDimHidden && (React.createElement("div", { style: dimStyles, onClick: this.handleDimClick })),
|
329 | React.createElement("div", { style: dockStyles },
|
330 | React.createElement("div", { style: resizerStyles, onMouseDown: this.handleMouseDown, onTouchStart: this.handleMouseDown }),
|
331 | React.createElement("div", { style: styles.dockContent }, typeof children === 'function'
|
332 | ? children({
|
333 | position,
|
334 | isResizing,
|
335 | size,
|
336 | isVisible,
|
337 | })
|
338 | : children))));
|
339 | }
|
340 | }
|
341 | Dock.defaultProps = {
|
342 | position: 'left',
|
343 | zIndex: 99999999,
|
344 | fluid: true,
|
345 | defaultSize: 0.3,
|
346 | dimMode: 'opaque',
|
347 | duration: 200,
|
348 | };
|
349 | export default Dock;
|