{ }
59 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Re-F|ex Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 | The most basic example with two fixed panes vertically arranged. By default each ReflexElement
20 | will be equally arranged within its ReflexContainer . To specify a custom arrangement, you can use flex={number in [0.0, 1.0]}
21 | property.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 | Basic example with two resizable panes vertically arranged: placing a ReflexSplitter
37 | draggable component between two ReflexElements will allow those elements to be manually resized by user.
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
52 | Example with two splitters propagating the drag: this is where Re-Flex really makes the difference between any other
53 | layout library you can find out there.
54 |
55 |
56 |
57 | Setting the propagate={true}
58 | property on the ReflexSplitter components will propagate the drag all the way from right to left or
59 | left to right (respectively up and down for an horizontal layout) depending on user interaction. In that demo minSize
60 | and maxSize properties are also specified on the middle pane, Re-Flex will respect those attributes when dragging
61 | and propagating the offset applied by user.
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
76 | Example with three splitters propagating the drag, same as example above but one more splitter just for fun ...
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
89 |
90 |
91 | A more advanced example with multi-nested resizable layouts using events: while dragging a splitter
92 | the affected panes will get a different background color, illustrating how you can use Re-Flex events to
93 | perform some additional custom logic.
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Try the sample below and judge the power of Re-Flex ...
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
115 |
116 |
117 | A more verbose example that illustrates how to programmatically control the size of ReflexElements .
118 |
119 |
120 | Clicking the (- / + ) buttons in the control bar above each pane will respectively minimize or
121 | maximize that pane into the allocated layout space.
122 |
123 |
124 | Clicking the (= ) button will lock the pane to its current size. Click the button again to unlock it.
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
137 |
138 |
139 | This demo illustrates how to enable
ReflexElement children to be "
size-aware ".
140 | Under the hoods, Re-Flex is using
react-measure to inject a
141 |
dimensions property to its child elements.
142 |
143 |
144 | To activate that feature, you need to use use
propagateDimensions={true} and also
renderOnResize={true}
145 | in order to force the child elelement to re-render when its size has been modified.
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
158 |
159 |
160 | This demo illustrates how to use onResize event on ReflexElement to create a persistent layout state:
161 | Everytime a pane is being resized, we store the current layout state to the localStorage and restore it when the component gets created.
162 | Reload the page to see the layout will appear in the same state as you left it.
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
175 |
176 |
177 | This demo illustrates how to create collapsible panels with Re-Flex:
178 | the side panels will collapse when their size becomes smaller than the defined threshold.
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
191 |
192 |
193 | This demo illustrates how to use a ReflexHandle:
194 | when inserted as child of a ReflexElement, a ReflexHandle allows user to resize
195 | the element as if the splitter was manipulated. It needs to be inserted as child of an
196 | element following a splitter.
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reflex",
3 | "version": "4.2.7",
4 | "description": "Flex layout component for advanced React web applications",
5 | "main": "dist/commonjs/index.js",
6 | "module": "dist/es/index.js",
7 | "types": "./dist/index.d.ts",
8 | "files": [
9 | "dist",
10 | "styles.css"
11 | ],
12 | "scripts": {
13 | "build-demo-dev": "webpack --watch --config ./webpack/demo/development.webpack.config",
14 | "build-demo": "webpack --config ./webpack/demo/production.webpack.config",
15 | "build-dev": "webpack --config ./webpack/lib/development.webpack.config",
16 | "build-lib-umd-dev": "webpack --config ./webpack/lib/development.webpack.config",
17 | "build-lib-umd": "webpack --config ./webpack/lib/production.webpack.config",
18 | "build-lib-commonjs": "rimraf dist/commonjs && BABEL_ENV=commonjs babel src/lib --out-dir=dist/commonjs",
19 | "build-lib-es": "rimraf dist/es && BABEL_ENV=es babel src/lib --out-dir=dist/es",
20 | "prebuild-css": "rimraf styles.css && node-sass src/lib/reflex-styles.scss styles.css",
21 | "build-css": "npm run prebuild-css && postcss styles.css -u autoprefixer -r",
22 | "build-dts": "copy ./index.d.ts ./dist",
23 | "build": "npm run build-css && npm run build-lib-commonjs && npm run build-lib-es && npm run build-lib-umd && npm run build-lib-umd-dev && npm run build-dts && npm run build-demo",
24 | "prepublish": "npm run build"
25 | },
26 | "author": "Philippe Leefsma",
27 | "license": "MIT",
28 | "keywords": [
29 | "flex",
30 | "layout",
31 | "react",
32 | "reactjs",
33 | "react-component",
34 | "pane",
35 | "panel",
36 | "split-pane",
37 | "split-panel",
38 | "resize",
39 | "resizable",
40 | "splitter"
41 | ],
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/leefsmp/Re-Flex.git"
45 | },
46 | "devDependencies": {
47 | "@babel/cli": "^7.0.0",
48 | "@babel/core": "7.1.2",
49 | "@babel/plugin-proposal-class-properties": "7.1.0",
50 | "@babel/plugin-proposal-decorators": "7.1.2",
51 | "@babel/plugin-proposal-do-expressions": "7.0.0",
52 | "@babel/plugin-proposal-export-default-from": "7.0.0",
53 | "@babel/plugin-proposal-export-namespace-from": "7.0.0",
54 | "@babel/plugin-proposal-function-bind": "7.0.0",
55 | "@babel/plugin-proposal-function-sent": "7.1.0",
56 | "@babel/plugin-proposal-logical-assignment-operators": "7.0.0",
57 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
58 | "@babel/plugin-proposal-numeric-separator": "7.0.0",
59 | "@babel/plugin-proposal-object-rest-spread": "7.0.0",
60 | "@babel/plugin-proposal-optional-chaining": "^7.0.0",
61 | "@babel/plugin-proposal-pipeline-operator": "7.0.0",
62 | "@babel/plugin-proposal-throw-expressions": "7.0.0",
63 | "@babel/plugin-syntax-dynamic-import": "7.0.0",
64 | "@babel/plugin-syntax-import-meta": "7.0.0",
65 | "@babel/plugin-transform-modules-commonjs": "7.1.0",
66 | "@babel/plugin-transform-runtime": "7.1.0",
67 | "@babel/preset-env": "7.1.0",
68 | "@babel/preset-react": "7.0.0",
69 | "autoprefixer": "9.3.1",
70 | "babel-eslint": "^6.0.0-beta.6",
71 | "babel-loader": "8.0.4",
72 | "babel-plugin-istanbul": "^5.1.4",
73 | "clean-webpack-plugin": "0.1.19",
74 | "copy": "^0.3.2",
75 | "cp": "^0.2.0",
76 | "css-loader": "1.0.0",
77 | "es6-promise": "^4.2.5",
78 | "eslint": "^6.0.1",
79 | "eslint-plugin-react": "^2.3.0",
80 | "node-sass": "^6.0.1",
81 | "postcss-cli": "^6.1.3",
82 | "postcss-loader": "3.0.0",
83 | "precss": "3.1.2",
84 | "react": "^16.2.0",
85 | "react-dom": "^16.0.0",
86 | "react-hot-loader": "4.3.11",
87 | "rimraf": "^2.6.1",
88 | "sass-loader": "10.2.1",
89 | "style-loader": "0.23.0",
90 | "webpack": "4.36.0",
91 | "webpack-cli": "3.1.2"
92 | },
93 | "dependencies": {
94 | "@babel/runtime": "^7.0.0",
95 | "lodash.throttle": "^4.1.1",
96 | "prop-types": "^15.5.8",
97 | "react-measure": "^2.0.2"
98 | },
99 | "peerDependencies": {
100 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/resources/img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leefsmp/Re-Flex/7f7ecbc3979a7bbb940edfa4b0f024ef47813915/resources/img/demo.png
--------------------------------------------------------------------------------
/resources/img/re-flex-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leefsmp/Re-Flex/7f7ecbc3979a7bbb940edfa4b0f024ef47813915/resources/img/re-flex-banner.png
--------------------------------------------------------------------------------
/resources/img/re-flex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leefsmp/Re-Flex/7f7ecbc3979a7bbb940edfa4b0f024ef47813915/resources/img/re-flex.png
--------------------------------------------------------------------------------
/resources/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leefsmp/Re-Flex/7f7ecbc3979a7bbb940edfa4b0f024ef47813915/resources/img/react.png
--------------------------------------------------------------------------------
/src/demo/demo.scss:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | height: 100%;
7 | padding: 0;
8 | margin: 0;
9 | }
10 |
11 | body {
12 | background-color: #dddddd;
13 | margin: 50px;
14 | }
15 |
16 | .reflex-element {
17 | background-color: rgba(1, 210, 248, 0.06);
18 | overflow: hidden !important;
19 | }
20 |
21 | .reflex-element.resizing {
22 | background-color: rgba(255, 0, 0, 0.20);
23 | }
24 |
25 | .reflex-splitter.resizing {
26 | background-color: rgba(255, 0, 0, 0.5);
27 | }
28 |
29 | .pane-content {
30 | text-align: center;
31 | position: relative;
32 | user-select: none;
33 | height: 100%;
34 | }
35 |
36 | .pane-content > label {
37 | position: relative;
38 | top:30%;
39 | }
40 |
41 | .header {
42 | background-color: rgba(1, 210, 248, 0.30);
43 | border-bottom: 1px solid #c6c6c6;
44 | overflow: hidden !important;
45 | }
46 |
47 | .footer {
48 | background-color: rgba(1, 210, 248, 0.30);
49 | border-top: 1px solid #c6c6c6;
50 | overflow: hidden !important;
51 | text-align: center;
52 | }
53 |
54 | .left-pane {
55 | background-color: rgb(34, 34, 34);
56 | color: #03d8ff;
57 | }
58 |
59 | .middle-pane {
60 | background-color: rgb(255, 255, 255);
61 | }
62 |
63 | .right-pane {
64 | background-color: rgb(3, 216, 255);
65 | }
66 |
67 | .bottom-pane {
68 | background-color: rgb(34, 34, 34);
69 | color: #03d8ff;
70 | }
71 |
72 | .pane-control {
73 | border-bottom: 1px solid #c6c6c6;
74 | background-color: #b3f2fd;
75 | overflow: hidden;
76 | height: 25px;
77 | }
78 |
79 | .pane-control > label {
80 | white-space: nowrap;
81 | margin-left: 10px;
82 | margin-top: 4px;
83 | font-size: 14px;
84 | float: left;
85 | }
86 |
87 | .pane-control > button {
88 | transition-timing-function: ease;
89 | transition-duration: 1.0s;
90 | transition-property: all;
91 | transition-delay: 0.0s;
92 |
93 | border: 1px solid #eeeeee;
94 | background-color: #c6c6c6;
95 | border-radius: 6px;
96 | position: relative;
97 | margin-right: 4px;
98 | overflow: hidden;
99 | margin-top: 4px;
100 | outline: none;
101 | float: right;
102 | height: 17px;
103 | width: 30px;
104 | }
105 |
106 | .pane-control > button:hover {
107 | border: 1px solid #0c63ff;
108 | }
109 |
110 | .pane-control > button > label {
111 | transition-timing-function: ease;
112 | transition-duration: 1.0s;
113 | transition-property: all;
114 | transition-delay: 0.0s;
115 |
116 | position: relative;
117 | font-size: 20px;
118 | color: #f0fcff;
119 | top: -8px;
120 | }
121 |
122 | .pane-control > button:hover > label {
123 | color: #0c63ff;
124 | }
125 |
126 | .ctrl-pane-content {
127 | height: calc(100% - 26px);
128 | overflow: hidden;
129 | }
130 |
131 | .ctrl-pane-content > label {
132 | position: relative;
133 | top:30%;
134 | }
135 |
136 | .handle {
137 | background: #eae9e9;
138 | padding: 8px 0 0 8px;
139 | height: 28px;
140 | }
141 |
142 | #demo-basic {
143 | border: 1px solid #0c63ff;
144 | background-color: white;
145 | height: 250px;
146 | }
147 |
148 | #demo-basic-splitter {
149 | border: 1px solid #0c63ff;
150 | background-color: white;
151 | height: 250px;
152 | }
153 |
154 | #demo-splitter-propagation-2x {
155 | border: 1px solid #0c63ff;
156 | background-color: white;
157 | height: 250px;
158 | }
159 |
160 | #demo-splitter-propagation-3x {
161 | border: 1px solid #0c63ff;
162 | background-color: white;
163 | height: 250px;
164 | }
165 |
166 | #demo-advanced {
167 | border: 1px solid #0c63ff;
168 | background-color: white;
169 | height: 600px;
170 | }
171 |
172 | #demo-controls {
173 | border: 1px solid #0c63ff;
174 | background-color: white;
175 | height: 400px;
176 | }
177 |
178 | #demo-size-aware {
179 | border: 1px solid #0c63ff;
180 | background-color: white;
181 | height: 280px;
182 | }
183 |
184 | #demo-storage {
185 | border: 1px solid #0c63ff;
186 | background-color: white;
187 | height: 280px;
188 | }
189 |
190 | #demo-collapse {
191 | border: 1px solid #0c63ff;
192 | background-color: white;
193 | height: 280px;
194 | }
195 |
196 | #demo-handle {
197 | border: 1px solid #0c63ff;
198 | background-color: white;
199 | height: 280px;
200 | }
201 |
202 | #page-footer-filler {
203 | height: 100px;
204 | }
205 |
--------------------------------------------------------------------------------
/src/demo/index.js:
--------------------------------------------------------------------------------
1 | import './demo.jsx'
2 |
--------------------------------------------------------------------------------
/src/lib/Polyfills.js:
--------------------------------------------------------------------------------
1 | if (!Array.prototype.includes) {
2 | Object.defineProperty(Array.prototype, 'includes', {
3 | value: function(valueToFind, fromIndex) {
4 |
5 | if (this == null) {
6 | throw new TypeError('"this" is null or not defined');
7 | }
8 |
9 | // 1. Let O be ? ToObject(this value).
10 | var o = Object(this);
11 |
12 | // 2. Let len be ? ToLength(? Get(O, "length")).
13 | var len = o.length >>> 0;
14 |
15 | // 3. If len is 0, return false.
16 | if (len === 0) {
17 | return false;
18 | }
19 |
20 | // 4. Let n be ? ToInteger(fromIndex).
21 | // (If fromIndex is undefined, this step produces the value 0.)
22 | var n = fromIndex | 0;
23 |
24 | // 5. If n ≥ 0, then
25 | // a. Let k be n.
26 | // 6. Else n < 0,
27 | // a. Let k be len + n.
28 | // b. If k < 0, let k be 0.
29 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
30 |
31 | function sameValueZero(x, y) {
32 | return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
33 | }
34 |
35 | // 7. Repeat, while k < len
36 | while (k < len) {
37 | // a. Let elementK be the result of ? Get(O, ! ToString(k)).
38 | // b. If SameValueZero(valueToFind, elementK) is true, return true.
39 | if (sameValueZero(o[k], valueToFind)) {
40 | return true;
41 | }
42 | // c. Increase k by 1.
43 | k++;
44 | }
45 |
46 | // 8. Return false
47 | return false;
48 | }
49 | });
50 | }
51 |
52 | if (!Math.sign) {
53 | Math.sign = function (x) {
54 | return ((x > 0) - (x < 0)) || +x
55 | }
56 | }
--------------------------------------------------------------------------------
/src/lib/ReflexContainer.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | // ReflexContainer
3 | // By Philippe Leefsma
4 | // December 2016
5 | //
6 | ///////////////////////////////////////////////////////////
7 | import ReflexSplitter from './ReflexSplitter'
8 | import ReflexEvents from './ReflexEvents'
9 | import {getDataProps} from './utilities'
10 | import PropTypes from 'prop-types'
11 | import React from 'react'
12 | import './Polyfills'
13 |
14 | export default class ReflexContainer extends React.Component {
15 |
16 | /////////////////////////////////////////////////////////
17 | // orientation: Orientation of the layout container
18 | // valid values are ['horizontal', 'vertical']
19 | // maxRecDepth: Maximun recursion depth to solve initial flex
20 | // of layout elements based on user provided values
21 | // className: Space separated classnames to apply custom styles
22 | // to the layout container
23 | // style: allows passing inline style to the container
24 | /////////////////////////////////////////////////////////
25 | static propTypes = {
26 | windowResizeAware: PropTypes.bool,
27 | orientation: PropTypes.oneOf([
28 | 'horizontal', 'vertical'
29 | ]),
30 | maxRecDepth: PropTypes.number,
31 | className: PropTypes.string,
32 | style: PropTypes.object
33 | }
34 |
35 | static defaultProps = {
36 | orientation: 'horizontal',
37 | windowResizeAware: false,
38 | maxRecDepth: 100,
39 | className: '',
40 | style: {}
41 | }
42 |
43 | constructor (props) {
44 | super (props)
45 | this.events = new ReflexEvents()
46 | this.children = []
47 | this.state = {
48 | flexData: []
49 | }
50 | this.ref = React.createRef()
51 | }
52 |
53 | componentDidMount () {
54 |
55 | const flexData = this.computeFlexData()
56 |
57 | const {windowResizeAware} = this.props
58 |
59 | if (windowResizeAware) {
60 | window.addEventListener(
61 | 'resize', this.onWindowResize)
62 | }
63 |
64 | this.setState ({
65 | windowResizeAware,
66 | flexData
67 | })
68 |
69 | this.events.on(
70 | 'element.size', this.onElementSize)
71 |
72 | this.events.on(
73 | 'startResize', this.onStartResize)
74 |
75 | this.events.on(
76 | 'stopResize', this.onStopResize)
77 |
78 | this.events.on(
79 | 'resize', this.onResize)
80 | }
81 |
82 | componentWillUnmount () {
83 |
84 | this.events.off()
85 |
86 | window.removeEventListener(
87 | 'resize', this.onWindowResize)
88 | }
89 |
90 | getValidChildren (props = this.props) {
91 | return this.toArray(props.children).filter((child) => {
92 | return !!child
93 | })
94 | }
95 |
96 | componentDidUpdate (prevProps, prevState) {
97 |
98 | const children = this.getValidChildren(this.props)
99 |
100 | if ((children.length !== this.state.flexData.length) ||
101 | (prevProps.orientation !== this.props.orientation) ||
102 | this.flexHasChanged(prevProps)) {
103 |
104 | const flexData = this.computeFlexData(
105 | children, this.props)
106 |
107 | this.setState({
108 | flexData
109 | })
110 | }
111 |
112 | if (this.props.windowResizeAware !== this.state.windowResizeAware) {
113 | !this.props.windowResizeAware
114 | ? window.removeEventListener('resize', this.onWindowResize)
115 | : window.addEventListener('resize', this.onWindowResize)
116 | this.setState({
117 | windowResizeAware: this.props.windowResizeAware
118 | })
119 | }
120 | }
121 |
122 | // UNSAFE_componentWillReceiveProps(props) {
123 |
124 | // const children = this.getValidChildren(props)
125 |
126 | // if (children.length !== this.state.flexData.length ||
127 | // props.orientation !== this.props.orientation ||
128 | // this.flexHasChanged(props))
129 | // {
130 | // const flexData = this.computeFlexData(
131 | // children, props)
132 |
133 | // this.setState({
134 | // flexData
135 | // });
136 | // }
137 |
138 | // if (props.windowResizeAware !== this.state.windowResizeAware) {
139 | // !props.windowResizeAware
140 | // ? window.removeEventListener('resize', this.onWindowResize)
141 | // : window.addEventListener('resize', this.onWindowResize)
142 | // this.setState({
143 | // windowResizeAware: props.windowResizeAware
144 | // })
145 | // }
146 | // }
147 |
148 | /////////////////////////////////////////////////////////
149 | // attempts to preserve current flex on window resize
150 | //
151 | /////////////////////////////////////////////////////////
152 | onWindowResize = () => {
153 |
154 | this.setState({
155 | flexData: this.computeFlexData()
156 | })
157 | }
158 |
159 | /////////////////////////////////////////////////////////
160 | // Check if flex has changed: this allows updating the
161 | // component when different flex is passed as property
162 | // to one or several children
163 | //
164 | /////////////////////////////////////////////////////////
165 | flexHasChanged (prevProps) {
166 |
167 | const prevChildrenFlex =
168 | this.getValidChildren(prevProps).map((child) => {
169 | return child.props.flex || 0
170 | })
171 |
172 | const childrenFlex =
173 | this.getValidChildren().map((child) => {
174 | return child.props.flex || 0
175 | })
176 |
177 | return !childrenFlex.every((flex, idx) => {
178 | return flex === prevChildrenFlex[idx]
179 | })
180 | }
181 |
182 | /////////////////////////////////////////////////////////
183 | // Returns size of a ReflexElement
184 | //
185 | /////////////////////////////////////////////////////////
186 | getSize (element) {
187 |
188 | const domElement = element?.ref?.current
189 |
190 | switch (this.props.orientation) {
191 | case 'horizontal':
192 | return domElement?.offsetHeight ?? 0
193 | case 'vertical':
194 | default:
195 | return domElement?.offsetWidth ?? 0
196 | }
197 | }
198 |
199 | /////////////////////////////////////////////////////////
200 | // Computes offset from pointer position
201 | //
202 | /////////////////////////////////////////////////////////
203 | getOffset (pos, domElement) {
204 |
205 | const {
206 | top, bottom,
207 | left, right
208 | } = domElement.getBoundingClientRect()
209 |
210 | switch (this.props.orientation) {
211 | case 'horizontal': {
212 | const offset = pos.clientY - this.previousPos
213 | if (offset > 0) {
214 | if (pos.clientY >= top) {
215 | return offset
216 | }
217 | } else {
218 | if (pos.clientY <= bottom) {
219 | return offset
220 | }
221 | }
222 | break
223 | }
224 | case 'vertical':
225 | default: {
226 | const offset = pos.clientX - this.previousPos
227 | if (offset > 0) {
228 | if (pos.clientX > left) {
229 | return offset
230 | }
231 | } else {
232 | if (pos.clientX < right) {
233 | return offset
234 | }
235 | }
236 | }
237 | break
238 | }
239 | return 0
240 | }
241 |
242 | /////////////////////////////////////////////////////////
243 | // Handles startResize event
244 | //
245 | /////////////////////////////////////////////////////////
246 | onStartResize = (data) => {
247 |
248 | const pos = data.event.changedTouches
249 | ? data.event.changedTouches[0]
250 | : data.event
251 |
252 | switch (this.props.orientation) {
253 |
254 | case 'horizontal':
255 | document.body.classList.add('reflex-row-resize')
256 | this.previousPos = pos.clientY
257 | break
258 |
259 | case 'vertical':
260 | default:
261 | document.body.classList.add('reflex-col-resize')
262 | this.previousPos = pos.clientX
263 | break
264 | }
265 |
266 | this.elements = [
267 | this.children[data.index - 1],
268 | this.children[data.index + 1]
269 | ]
270 |
271 | this.emitElementsEvent(
272 | this.elements,
273 | 'onStartResize')
274 | }
275 |
276 | /////////////////////////////////////////////////////////
277 | // Handles splitter resize event
278 | //
279 | /////////////////////////////////////////////////////////
280 | onResize = (data) => {
281 |
282 | const pos = data.event.changedTouches
283 | ? data.event.changedTouches[0]
284 | : data.event
285 |
286 | const offset = this.getOffset(
287 | pos, data.domElement)
288 |
289 | switch (this.props.orientation) {
290 | case 'horizontal':
291 | this.previousPos = pos.clientY
292 | break
293 | case 'vertical':
294 | default:
295 | this.previousPos = pos.clientX
296 | break
297 | }
298 |
299 | if (offset) {
300 |
301 | const availableOffset =
302 | this.computeAvailableOffset(
303 | data.index, offset)
304 |
305 | if (availableOffset) {
306 |
307 | this.elements = this.dispatchOffset(
308 | data.index, availableOffset)
309 |
310 | this.adjustFlex(this.elements)
311 |
312 | this.setState({
313 | resizing: true
314 | }, () => {
315 | this.emitElementsEvent(
316 | this.elements, 'onResize')
317 | })
318 | }
319 | }
320 | }
321 |
322 | /////////////////////////////////////////////////////////
323 | // Handles stopResize event
324 | //
325 | /////////////////////////////////////////////////////////
326 | onStopResize = (data) => {
327 |
328 | document.body.classList.remove('reflex-row-resize')
329 | document.body.classList.remove('reflex-col-resize')
330 |
331 | const resizedRefs = this.elements ? this.elements.map(element => {
332 | return element.ref
333 | }) : [];
334 |
335 | const elements = this.children.filter(child => {
336 | return !ReflexSplitter.isA(child) &&
337 | resizedRefs.includes(child.ref)
338 | })
339 |
340 | this.emitElementsEvent(
341 | elements, 'onStopResize')
342 |
343 | this.setState({
344 | resizing: false
345 | })
346 | }
347 |
348 | /////////////////////////////////////////////////////////
349 | // Handles element size modified event
350 | //
351 | /////////////////////////////////////////////////////////
352 | onElementSize = (data) => {
353 |
354 | return new Promise((resolve) => {
355 |
356 | try {
357 |
358 | const idx = data.index
359 |
360 | const size = this.getSize(this.children[idx])
361 |
362 | const offset = data.size - size
363 |
364 | const dir = data.direction
365 |
366 | const splitterIdx = idx + dir
367 |
368 | const availableOffset =
369 | this.computeAvailableOffset(
370 | splitterIdx, dir * offset)
371 |
372 | this.elements = null
373 |
374 | if (availableOffset) {
375 |
376 | this.elements = this.dispatchOffset(
377 | splitterIdx, availableOffset)
378 |
379 | this.adjustFlex(this.elements)
380 | }
381 |
382 | this.setState(this.state, () => {
383 | this.emitElementsEvent(
384 | this.elements, 'onResize')
385 | resolve()
386 | })
387 |
388 | } catch (ex) {
389 |
390 | // TODO handle exception ...
391 | console.log(ex)
392 | }
393 | })
394 | }
395 |
396 | /////////////////////////////////////////////////////////
397 | // Adjusts flex after a dispatch to make sure
398 | // total flex of modified elements remains the same
399 | //
400 | /////////////////////////////////////////////////////////
401 | adjustFlex (elements) {
402 |
403 | const diffFlex = elements.reduce((sum, element) => {
404 |
405 | const idx = element.props.index
406 |
407 | const previousFlex = element.props.flex
408 |
409 | const nextFlex = this.state.flexData[idx].flex
410 |
411 | return sum +
412 | (previousFlex - nextFlex) / elements.length
413 |
414 | }, 0)
415 |
416 | elements.forEach((element) => {
417 | this.state.flexData[element.props.index].flex
418 | += diffFlex
419 | })
420 | }
421 |
422 | /////////////////////////////////////////////////////////
423 | // Returns available offset for a given raw offset value
424 | // This checks how much the panes can be stretched and
425 | // shrink, then returns the min
426 | //
427 | /////////////////////////////////////////////////////////
428 | computeAvailableOffset (idx, offset) {
429 |
430 | const stretch = this.computeAvailableStretch(
431 | idx, offset)
432 |
433 | const shrink = this.computeAvailableShrink(
434 | idx, offset)
435 |
436 | const availableOffset =
437 | Math.min(stretch, shrink) *
438 | Math.sign(offset)
439 |
440 | return availableOffset
441 | }
442 |
443 | /////////////////////////////////////////////////////////
444 | // Returns true if the next splitter than the one at idx
445 | // can propagate the drag. This can happen if that
446 | // next element is actually a splitter and it has
447 | // propagate=true property set
448 | //
449 | /////////////////////////////////////////////////////////
450 | checkPropagate (idx, direction) {
451 |
452 | if (direction > 0) {
453 |
454 | if (idx < this.children.length - 2) {
455 |
456 | const child = this.children[idx + 2]
457 |
458 | const typeCheck = ReflexSplitter.isA(child)
459 |
460 | return typeCheck && child.props.propagate
461 | }
462 |
463 | } else {
464 |
465 | if (idx > 2) {
466 |
467 | const child = this.children[idx - 2]
468 |
469 | const typeCheck = ReflexSplitter.isA(child)
470 |
471 | return typeCheck && child.props.propagate
472 | }
473 | }
474 |
475 | return false
476 | }
477 |
478 | /////////////////////////////////////////////////////////
479 | // Recursively computes available stretch at splitter
480 | // idx for given raw offset
481 | //
482 | /////////////////////////////////////////////////////////
483 | computeAvailableStretch (idx, offset) {
484 |
485 | const childIdx = offset < 0 ? idx + 1 : idx - 1
486 |
487 | const child = this.children[childIdx]
488 |
489 | const size = this.getSize(child)
490 |
491 | const maxSize = child?.props.maxSize ?? 0
492 |
493 | const availableStretch = maxSize - size
494 |
495 | if (availableStretch < Math.abs(offset)) {
496 |
497 | if (this.checkPropagate(idx, -1 * offset)) {
498 |
499 | const nextOffset = Math.sign(offset) *
500 | (Math.abs(offset) - availableStretch)
501 |
502 | return availableStretch +
503 | this.computeAvailableStretch(
504 | offset < 0 ? idx + 2 : idx - 2,
505 | nextOffset)
506 | }
507 | }
508 |
509 | return Math.min(availableStretch, Math.abs(offset))
510 | }
511 |
512 | /////////////////////////////////////////////////////////
513 | // Recursively computes available shrink at splitter
514 | // idx for given raw offset
515 | //
516 | /////////////////////////////////////////////////////////
517 | computeAvailableShrink (idx, offset) {
518 |
519 | const childIdx = offset > 0 ? idx + 1 : idx -1
520 |
521 | const child = this.children[childIdx]
522 |
523 | const size = this.getSize(child)
524 |
525 | const minSize = Math.max(
526 | child?.props.minSize ?? 0, 0)
527 |
528 | const availableShrink = size - minSize
529 |
530 | if (availableShrink < Math.abs(offset)) {
531 |
532 | if (this.checkPropagate(idx, offset)) {
533 |
534 | const nextOffset = Math.sign(offset) *
535 | (Math.abs(offset) - availableShrink)
536 |
537 | return availableShrink +
538 | this.computeAvailableShrink(
539 | offset > 0 ? idx + 2 : idx - 2,
540 | nextOffset)
541 | }
542 | }
543 |
544 | return Math.min(availableShrink, Math.abs(offset))
545 | }
546 |
547 | /////////////////////////////////////////////////////////
548 | // Returns flex value for unit pixel
549 | //
550 | /////////////////////////////////////////////////////////
551 | computePixelFlex (orientation = this.props.orientation) {
552 | if (!this.ref.current) {
553 | console.warn('Unable to locate ReflexContainer dom node');
554 | return 0.0;
555 | }
556 |
557 | switch (orientation) {
558 |
559 | case 'horizontal':
560 |
561 | if (this.ref.current.offsetHeight === 0.0) {
562 | console.warn(
563 | 'Found ReflexContainer with height=0, ' +
564 | 'this will cause invalid behavior...')
565 | console.warn(this.ref.current)
566 | return 0.0
567 | }
568 |
569 | return 1.0 / this.ref.current.offsetHeight
570 |
571 | case 'vertical':
572 | default:
573 |
574 | if (this.ref.current.offsetWidth === 0.0) {
575 | console.warn(
576 | 'Found ReflexContainer with width=0, ' +
577 | 'this will cause invalid behavior...')
578 | console.warn(this.ref.current)
579 | return 0.0
580 | }
581 |
582 | return 1.0 / this.ref.current.offsetWidth
583 | }
584 | }
585 |
586 | /////////////////////////////////////////////////////////
587 | // Adds offset to a given ReflexElement
588 | //
589 | /////////////////////////////////////////////////////////
590 | addOffset (element, offset) {
591 |
592 | const size = this.getSize(element)
593 |
594 | const idx = element.props.index
595 |
596 | const newSize = Math.max(size + offset, 0)
597 |
598 | const currentFlex = this.state.flexData[idx].flex
599 |
600 | const newFlex = (currentFlex > 0)
601 | ? currentFlex * newSize / size
602 | : this.computePixelFlex () * newSize
603 |
604 | this.state.flexData[idx].flex =
605 | (!isFinite(newFlex) || isNaN(newFlex))
606 | ? 0 : newFlex
607 | }
608 |
609 | /////////////////////////////////////////////////////////
610 | // Recursively dispatches stretch offset across
611 | // children elements starting at splitter idx
612 | //
613 | /////////////////////////////////////////////////////////
614 | dispatchStretch (idx, offset) {
615 |
616 | const childIdx = offset < 0 ? idx + 1 : idx - 1
617 |
618 | if (childIdx < 0 || childIdx > this.children.length-1) {
619 |
620 | return []
621 | }
622 |
623 | const child = this.children[childIdx]
624 |
625 | const size = this.getSize(child)
626 |
627 | const newSize = Math.min(
628 | child.props.maxSize,
629 | size + Math.abs(offset))
630 |
631 | const dispatchedStretch = newSize - size
632 |
633 | this.addOffset(child, dispatchedStretch)
634 |
635 | if (dispatchedStretch < Math.abs(offset)) {
636 |
637 | const nextIdx = idx - Math.sign(offset) * 2
638 |
639 | const nextOffset = Math.sign(offset) *
640 | (Math.abs(offset) - dispatchedStretch)
641 |
642 | return [
643 | child,
644 | ...this.dispatchStretch(nextIdx, nextOffset)
645 | ]
646 | }
647 |
648 | return [child]
649 | }
650 |
651 | /////////////////////////////////////////////////////////
652 | // Recursively dispatches shrink offset across
653 | // children elements starting at splitter idx
654 | //
655 | /////////////////////////////////////////////////////////
656 | dispatchShrink (idx, offset) {
657 |
658 | const childIdx = offset > 0 ? idx + 1 : idx - 1
659 |
660 | if (childIdx < 0 || childIdx > this.children.length-1) {
661 |
662 | return []
663 | }
664 |
665 | const child = this.children[childIdx]
666 |
667 | const size = this.getSize(child)
668 |
669 | const newSize = Math.max(
670 | child.props.minSize,
671 | size - Math.abs(offset))
672 |
673 | const dispatchedShrink = newSize - size
674 |
675 | this.addOffset(child, dispatchedShrink)
676 |
677 | if (Math.abs(dispatchedShrink) < Math.abs(offset)) {
678 |
679 | const nextIdx = idx + Math.sign(offset) * 2
680 |
681 | const nextOffset = Math.sign(offset) *
682 | (Math.abs(offset) + dispatchedShrink)
683 |
684 | return [
685 | child,
686 | ...this.dispatchShrink(nextIdx, nextOffset)
687 | ]
688 | }
689 |
690 | return [child]
691 | }
692 |
693 | /////////////////////////////////////////////////////////
694 | // Dispatch offset at splitter idx
695 | //
696 | /////////////////////////////////////////////////////////
697 | dispatchOffset (idx, offset) {
698 | return [
699 | ...this.dispatchStretch(idx, offset),
700 | ...this.dispatchShrink(idx, offset)
701 | ]
702 | }
703 |
704 | /////////////////////////////////////////////////////////
705 | // Emits given if event for each given element
706 | // if present in the component props
707 | //
708 | /////////////////////////////////////////////////////////
709 | emitElementsEvent (elements, event) {
710 | this.toArray(elements).forEach(component => {
711 | if (component.props[event]) {
712 | component.props[event]({
713 | domElement: component.ref.current,
714 | component
715 | })
716 | }
717 | })
718 | }
719 |
720 | /////////////////////////////////////////////////////////
721 | // Computes initial flex data based on provided flex
722 | // properties. By default each ReflexElement gets
723 | // evenly arranged within its container
724 | //
725 | /////////////////////////////////////////////////////////
726 | computeFlexData (
727 | children = this.getValidChildren(),
728 | props = this.props) {
729 |
730 | const pixelFlex = this.computePixelFlex(props.orientation)
731 |
732 | const computeFreeFlex = (flexData) => {
733 | return flexData.reduce((sum, entry) => {
734 | if (!ReflexSplitter.isA(entry)
735 | && entry.constrained) {
736 | return sum - entry.flex
737 | }
738 | return sum
739 | }, 1.0)
740 | }
741 |
742 | const computeFreeElements = (flexData) => {
743 | return flexData.reduce((sum, entry) => {
744 | if (!ReflexSplitter.isA(entry)
745 | && !entry.constrained) {
746 | return sum + 1
747 | }
748 | return sum
749 | }, 0.0)
750 | }
751 |
752 | const flexDataInit = children.map((child) => {
753 | const props = child.props
754 | return {
755 | maxFlex: (props.maxSize || Number.MAX_VALUE) * pixelFlex,
756 | sizeFlex: (props.size || Number.MAX_VALUE) * pixelFlex,
757 | minFlex: (props.minSize || 1) * pixelFlex,
758 | constrained: props.flex !== undefined,
759 | flex: props.flex || 0,
760 | type: child.type
761 | }
762 | })
763 |
764 | const computeFlexDataRec = (flexDataIn, depth=0) => {
765 |
766 | let hasContrain = false
767 |
768 | const freeElements = computeFreeElements(flexDataIn)
769 |
770 | const freeFlex = computeFreeFlex(flexDataIn)
771 |
772 | const flexDataOut = flexDataIn.map(entry => {
773 |
774 | if (ReflexSplitter.isA(entry)) {
775 | return entry
776 | }
777 |
778 | const proposedFlex = !entry.constrained
779 | ? freeFlex/freeElements
780 | : entry.flex
781 |
782 | const constrainedFlex =
783 | Math.min(entry.sizeFlex,
784 | Math.min(entry.maxFlex,
785 | Math.max(entry.minFlex,
786 | proposedFlex)))
787 |
788 | const constrained = entry.constrained ||
789 | (constrainedFlex !== proposedFlex)
790 |
791 | hasContrain = hasContrain || constrained
792 |
793 | return {
794 | ...entry,
795 | flex: constrainedFlex,
796 | constrained
797 | }
798 | })
799 |
800 | return (hasContrain && depth < this.props.maxRecDepth)
801 | ? computeFlexDataRec(flexDataOut, depth+1)
802 | : flexDataOut
803 | }
804 |
805 | const flexData = computeFlexDataRec(flexDataInit)
806 |
807 | return flexData.map(entry => {
808 | return {
809 | flex: !ReflexSplitter.isA(entry)
810 | ? entry.flex
811 | : 0.0,
812 | ref: React.createRef()
813 | }
814 | })
815 | }
816 |
817 | /////////////////////////////////////////////////////////
818 | // Utility method to ensure given argument is
819 | // returned as an array
820 | //
821 | /////////////////////////////////////////////////////////
822 | toArray (obj) {
823 | return obj ? (Array.isArray(obj) ? obj : [obj]) : []
824 | }
825 |
826 | /////////////////////////////////////////////////////////
827 | // Render container. This will clone all original child
828 | // components in order to pass some internal properties
829 | // used to handle resizing logic
830 | //
831 | /////////////////////////////////////////////////////////
832 | render () {
833 |
834 | const className = [
835 | this.state.resizing ? 'reflex-resizing':'',
836 | ...this.props.className.split(' '),
837 | this.props.orientation,
838 | 'reflex-container'
839 | ].join(' ').trim()
840 |
841 | this.children = React.Children.map(
842 | this.getValidChildren(), (child, index) => {
843 |
844 | if (index > this.state.flexData.length - 1) {
845 | return
846 | }
847 |
848 | const flexData = this.state.flexData[index]
849 |
850 | const newProps = {
851 | ...child.props,
852 | maxSize: child.props.maxSize || Number.MAX_VALUE,
853 | orientation: this.props.orientation,
854 | minSize: child.props.minSize || 1,
855 | events: this.events,
856 | flex: flexData.flex,
857 | ref: flexData.ref,
858 | index
859 | }
860 |
861 | return React.cloneElement(child, newProps)
862 | })
863 |
864 | return (
865 |
870 | { this.children }
871 |
872 | )
873 | }
874 | }
875 |
876 |
877 |
--------------------------------------------------------------------------------
/src/lib/ReflexElement.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | // ReflexElement
3 | // By Philippe Leefsma
4 | // December 2016
5 | //
6 | ///////////////////////////////////////////////////////////
7 | import ReflexHandle from './ReflexHandle'
8 | import {getDataProps} from './utilities'
9 | import throttle from 'lodash.throttle'
10 | import Measure from 'react-measure'
11 | import PropTypes from 'prop-types'
12 | import React from 'react'
13 |
14 | const toArray = (obj) => {
15 | return obj ? (Array.isArray(obj) ? obj : [obj]) : []
16 | }
17 |
18 | class SizeAwareReflexElement extends React.Component {
19 |
20 | constructor (props) {
21 |
22 | super (props)
23 |
24 | this.setDimensions = throttle((dimensions) => {
25 | this.setState(dimensions)
26 | }, this.props.propagateDimensionsRate/1000)
27 |
28 | this.state = {
29 | height: "100%",
30 | width: "100%"
31 | }
32 | }
33 |
34 | onResize = (rect) => {
35 |
36 | const { resizeHeight, resizeWidth } = this.props
37 |
38 | const {height, width} = rect.bounds
39 |
40 | this.setDimensions({
41 | ...(resizeHeight && {height}),
42 | ...(resizeWidth && {width})
43 | })
44 | }
45 |
46 | renderChildren () {
47 |
48 | const {propagateDimensions} = this.props
49 |
50 | const validChildren = toArray(this.props.children).filter(child => {
51 | return !!child
52 | })
53 |
54 | return React.Children.map(validChildren, (child) => {
55 |
56 | if (this.props.withHandle || ReflexHandle.isA(child)) {
57 | return React.cloneElement(child, {
58 | dimensions: propagateDimensions && this.state,
59 | ...child.props,
60 | index: this.props.index - 1,
61 | events: this.props.events
62 | })
63 | }
64 |
65 | if (propagateDimensions) {
66 | return React.cloneElement(child, {
67 | ...child.props,
68 | dimensions: this.state
69 | })
70 | }
71 |
72 | return child
73 | })
74 | }
75 |
76 | render () {
77 |
78 | return (
79 |
80 | {
81 | ({measureRef}) => {
82 | return (
83 |
84 |
85 | { this.renderChildren() }
86 |
87 |
88 | )
89 | }
90 | }
91 |
92 | )
93 | }
94 | }
95 |
96 |
97 | class ReflexElement extends React.Component {
98 |
99 | static propTypes = {
100 | propagateDimensions: PropTypes.bool,
101 | resizeHeight: PropTypes.bool,
102 | resizeWidth: PropTypes.bool,
103 | className: PropTypes.string,
104 | size: PropTypes.number
105 | }
106 |
107 | static defaultProps = {
108 | propagateDimensionsRate: 100,
109 | propagateDimensions: false,
110 | resizeHeight: true,
111 | resizeWidth: true,
112 | direction: [1],
113 | className: ''
114 | }
115 |
116 | constructor (props) {
117 | super (props)
118 | this.state = {
119 | size: props.size
120 | }
121 | }
122 |
123 | static getDerivedStateFromProps (nextProps, prevState) {
124 | if (nextProps.size !== prevState.size) {
125 | return {
126 | ...prevState,
127 | size: nextProps.size
128 | }
129 | }
130 | return null
131 | }
132 |
133 | async componentDidUpdate (prevProps, prevState, snapshot) {
134 |
135 | if (prevState.size !== this.state.size) {
136 |
137 | const directions = toArray(this.props.direction)
138 |
139 | for (let direction of directions) {
140 |
141 | await this.props.events.emit('element.size', {
142 | index: this.props.index,
143 | size: this.props.size,
144 | direction
145 | })
146 | }
147 | }
148 | }
149 |
150 | renderChildren () {
151 |
152 | const validChildren = toArray(this.props.children).filter(child => {
153 | return !!child
154 | })
155 |
156 | return React.Children.map(validChildren, (child) => {
157 | if (this.props.withHandle || ReflexHandle.isA(child)) {
158 | return React.cloneElement(child, {
159 | ...child.props,
160 | index: this.props.index - 1,
161 | events: this.props.events
162 | })
163 | }
164 | return child
165 | })
166 | }
167 |
168 | render () {
169 |
170 | const className = [
171 | ...this.props.className.split(' '),
172 | this.props.orientation,
173 | 'reflex-element'
174 | ].join(' ').trim()
175 |
176 | const style = {
177 | ...this.props.style,
178 | flexGrow: this.props.flex,
179 | flexShrink: 1,
180 | flexBasis: '0%'
181 | }
182 |
183 | return (
184 |
189 | {
190 | this.props.propagateDimensions
191 | ?
192 | : this.renderChildren()
193 | }
194 |
195 | )
196 | }
197 | }
198 |
199 | export default React.forwardRef((props, ref) => {
200 | return (
201 |
202 | )
203 | })
--------------------------------------------------------------------------------
/src/lib/ReflexEvents.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | // ReflexEvents
3 | // By Philippe Leefsma
4 | // December 2016
5 | //
6 | ///////////////////////////////////////////////////////////
7 | class ReflexEvents {
8 |
9 | constructor () {
10 |
11 | this._events = {}
12 | }
13 |
14 | /////////////////////////////////////////////////////////
15 | // Supports multiple events space-separated
16 | //
17 | /////////////////////////////////////////////////////////
18 | on (events, fct) {
19 |
20 | events.split(' ').forEach((event) => {
21 |
22 | this._events[event] = this._events[event] || []
23 | this._events[event].push(fct)
24 | })
25 |
26 | return this
27 | }
28 |
29 | /////////////////////////////////////////////////////////
30 | // Supports multiple events space-separated
31 | //
32 | /////////////////////////////////////////////////////////
33 | off (events, fct) {
34 |
35 | if (events == undefined) {
36 |
37 | this._events = {}
38 | return
39 | }
40 |
41 | events.split(' ').forEach((event) => {
42 |
43 | if (event in this._events === false)
44 | return;
45 |
46 | if (fct) {
47 |
48 | this._events[event].splice(
49 | this._events[event].indexOf(fct), 1)
50 |
51 | } else {
52 |
53 | this._events[event] = []
54 | }
55 | })
56 |
57 | return this
58 | }
59 |
60 | emit (event /* , args... */) {
61 |
62 | if(this._events[event] === undefined)
63 | return;
64 |
65 | var tmpArray = this._events[event].slice()
66 |
67 | for(var i = 0; i < tmpArray.length; ++i) {
68 |
69 | var result = tmpArray[i].apply(this,
70 | Array.prototype.slice.call(arguments, 1))
71 |
72 | if(result !== undefined) {
73 |
74 | return result
75 | }
76 | }
77 |
78 | return undefined
79 | }
80 | }
81 |
82 | export default ReflexEvents
83 |
--------------------------------------------------------------------------------
/src/lib/ReflexHandle.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | // ReflexHandle
3 | // By Philippe Leefsma
4 | // June 2018
5 | //
6 | ///////////////////////////////////////////////////////////
7 | import {getDataProps} from './utilities'
8 | import PropTypes from 'prop-types'
9 | import React from 'react'
10 |
11 | export default class ReflexHandle extends React.Component {
12 |
13 | ref = React.createRef()
14 |
15 |
16 | static propTypes = {
17 | children: PropTypes.oneOfType([
18 | PropTypes.arrayOf(PropTypes.node),
19 | PropTypes.node
20 | ]),
21 | onStartResize: PropTypes.func,
22 | onStopResize: PropTypes.func,
23 | className: PropTypes.string,
24 | propagate: PropTypes.bool,
25 | onResize: PropTypes.func,
26 | style: PropTypes.object
27 | }
28 |
29 | static defaultProps = {
30 | document: typeof document === 'undefined'
31 | ? null
32 | : document,
33 | onStartResize: null,
34 | onStopResize: null,
35 | propagate: false,
36 | onResize:null,
37 | className: '',
38 | style: {}
39 | }
40 |
41 | static isA (element) {
42 | if (!element) {
43 | return false
44 | }
45 | //https://github.com/leefsmp/Re-Flex/issues/49
46 | return (process.env.NODE_ENV === 'development')
47 | ? (element.type === ( ).type)
48 | : (element.type === ReflexHandle)
49 | }
50 |
51 | constructor (props) {
52 | super (props)
53 | this.state = {
54 | active: false
55 | }
56 | this.document = props.document
57 | }
58 |
59 | componentDidMount () {
60 |
61 | if (!this.document) {
62 | return
63 | }
64 |
65 | this.document.addEventListener(
66 | 'touchend',
67 | this.onMouseUp)
68 |
69 | this.document.addEventListener(
70 | 'mouseup',
71 | this.onMouseUp)
72 |
73 | this.document.addEventListener(
74 | 'mousemove',
75 | this.onMouseMove, {
76 | passive: false
77 | })
78 |
79 | this.document.addEventListener(
80 | 'touchmove',
81 | this.onMouseMove, {
82 | passive: false
83 | })
84 | }
85 |
86 | componentWillUnmount () {
87 |
88 | if (!this.document) {
89 | return
90 | }
91 |
92 | this.document.removeEventListener(
93 | 'mouseup',
94 | this.onMouseUp)
95 |
96 | this.document.removeEventListener(
97 | 'touchend',
98 | this.onMouseUp)
99 |
100 | this.document.removeEventListener(
101 | 'mousemove',
102 | this.onMouseMove)
103 |
104 | this.document.removeEventListener(
105 | 'touchmove',
106 | this.onMouseMove)
107 |
108 | if (this.state.active) {
109 |
110 | this.props.events.emit('stopResize', {
111 | index: this.props.index,
112 | event: null
113 | })
114 | }
115 | }
116 |
117 | onMouseMove = (event) => {
118 |
119 | if (this.state.active) {
120 |
121 | const domElement = this.ref.current
122 |
123 | this.props.events.emit(
124 | 'resize', {
125 | index: this.props.index,
126 | domElement,
127 | event
128 | })
129 |
130 | if (this.props.onResize) {
131 |
132 | this.props.onResize({
133 | component: this,
134 | domElement
135 | })
136 | }
137 |
138 | event.stopPropagation()
139 | event.preventDefault()
140 | }
141 | }
142 |
143 | onMouseDown = (event) => {
144 |
145 | this.setState({
146 | active: true
147 | })
148 |
149 | if (this.props.onStartResize) {
150 |
151 | // cancels resize from controller
152 | // if needed by returning true
153 | // to onStartResize
154 | if (this.props.onStartResize({
155 | domElement: this.ref.current,
156 | component: this
157 | })) {
158 |
159 | return
160 | }
161 | }
162 |
163 | this.props.events.emit('startResize', {
164 | index: this.props.index,
165 | event
166 | })
167 | }
168 |
169 | onMouseUp = (event) => {
170 |
171 | if (this.state.active) {
172 |
173 | this.setState({
174 | active: false
175 | })
176 |
177 | if (this.props.onStopResize) {
178 |
179 | this.props.onStopResize({
180 | domElement: this.ref.current,
181 | component: this
182 | })
183 | }
184 |
185 | this.props.events.emit('stopResize', {
186 | index: this.props.index,
187 | event
188 | })
189 | }
190 | }
191 |
192 | render () {
193 |
194 | const className = [
195 | ...this.props.className.split(' '),
196 | this.state.active? 'active' : '',
197 | 'reflex-handle'
198 | ].join(' ').trim()
199 |
200 | return (
201 |
209 | {this.props.children}
210 |
211 | )
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/lib/ReflexSplitter.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | // ReflexSplitter
3 | // By Philippe Leefsma
4 | // December 2016
5 | //
6 | ///////////////////////////////////////////////////////////
7 | import {Browser, getDataProps} from './utilities'
8 | import PropTypes from 'prop-types'
9 | import React from 'react'
10 |
11 | export default class ReflexSplitter extends React.Component {
12 |
13 | ref = React.createRef()
14 |
15 | static propTypes = {
16 | children: PropTypes.oneOfType([
17 | PropTypes.arrayOf(PropTypes.node),
18 | PropTypes.node
19 | ]),
20 | onStartResize: PropTypes.func,
21 | onStopResize: PropTypes.func,
22 | className: PropTypes.string,
23 | propagate: PropTypes.bool,
24 | onResize: PropTypes.func,
25 | style: PropTypes.object
26 | }
27 |
28 | static defaultProps = {
29 | document: typeof document !== 'undefined'
30 | ? document
31 | : null,
32 | onStartResize: null,
33 | onStopResize: null,
34 | propagate: false,
35 | onResize:null,
36 | className: '',
37 | style: {}
38 | }
39 |
40 | /////////////////////////////////////////////////////////
41 | // Determines if element is a splitter
42 | // or wraps a splitter
43 | //
44 | /////////////////////////////////////////////////////////
45 | static isA (element) {
46 | if (!element) {
47 | return false
48 | }
49 | //https://github.com/leefsmp/Re-Flex/issues/49
50 | return (element.type === ( ).type)
51 | }
52 |
53 | constructor (props) {
54 | super (props)
55 | this.state = {
56 | active: false
57 | }
58 | this.document = props.document
59 | }
60 |
61 | componentDidMount () {
62 |
63 | if (!this.document) {
64 | return;
65 | }
66 |
67 | this.document.addEventListener(
68 | 'touchend',
69 | this.onMouseUp)
70 |
71 | this.document.addEventListener(
72 | 'mouseup',
73 | this.onMouseUp)
74 |
75 | this.document.addEventListener(
76 | 'mousemove',
77 | this.onMouseMove, {
78 | passive: false
79 | })
80 |
81 | this.document.addEventListener(
82 | 'touchmove',
83 | this.onMouseMove, {
84 | passive: false
85 | })
86 | }
87 |
88 | componentWillUnmount () {
89 |
90 | if (!this.document) {
91 | return;
92 | }
93 |
94 | this.document.removeEventListener(
95 | 'mouseup',
96 | this.onMouseUp)
97 |
98 | this.document.removeEventListener(
99 | 'touchend',
100 | this.onMouseUp)
101 |
102 | this.document.removeEventListener(
103 | 'mousemove',
104 | this.onMouseMove)
105 |
106 | this.document.removeEventListener(
107 | 'touchmove',
108 | this.onMouseMove)
109 |
110 | if (this.state.active) {
111 | this.props.events.emit('stopResize', {
112 | index: this.props.index,
113 | event: null
114 | })
115 | }
116 | }
117 |
118 | onMouseMove = (event) => {
119 |
120 | if (this.state.active) {
121 |
122 | const domElement = this.ref.current
123 |
124 | this.props.events.emit(
125 | 'resize', {
126 | index: this.props.index,
127 | domElement,
128 | event
129 | })
130 |
131 | if (this.props.onResize) {
132 |
133 | this.props.onResize({
134 | component: this,
135 | domElement
136 | })
137 | }
138 |
139 | event.stopPropagation()
140 | event.preventDefault()
141 | }
142 | }
143 |
144 | onMouseDown = (event) => {
145 |
146 | this.setState({
147 | active: true
148 | })
149 |
150 | if (this.props.onStartResize) {
151 |
152 | // cancels resize from controller
153 | // if needed by returning true
154 | // to onStartResize
155 | if (this.props.onStartResize({
156 | domElement: this.ref.current,
157 | component: this
158 | })) {
159 | return
160 | }
161 | }
162 |
163 | this.props.events.emit('startResize', {
164 | index: this.props.index,
165 | event
166 | })
167 | }
168 |
169 | onMouseUp = (event) => {
170 |
171 | if (this.state.active) {
172 |
173 | this.setState({
174 | active: false
175 | })
176 |
177 | if (this.props.onStopResize) {
178 | this.props.onStopResize({
179 | domElement: this.ref.current,
180 | component: this
181 | })
182 | }
183 |
184 | this.props.events.emit('stopResize', {
185 | index: this.props.index,
186 | event
187 | })
188 | }
189 | }
190 |
191 | render () {
192 |
193 | const className = [
194 | Browser.isMobile() ? 'reflex-thin' : '',
195 | ...this.props.className.split(' '),
196 | this.state.active ? 'active' : '',
197 | 'reflex-splitter'
198 | ].join(' ').trim()
199 |
200 | return (
201 |
209 | {this.props.children}
210 |
211 | )
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | import ReflexContainer from './ReflexContainer'
2 | import ReflexSplitter from './ReflexSplitter'
3 | import ReflexElement from './ReflexElement'
4 | import ReflexHandle from './ReflexHandle'
5 |
6 | export {
7 | ReflexContainer,
8 | ReflexSplitter,
9 | ReflexElement,
10 | ReflexHandle
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/reflex-styles.scss:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////
2 | // Document body to handle resizing
3 | // See: https://github.com/leefsmp/Re-Flex/issues/68
4 | /////////////////////////////////////////////////////////
5 | body.reflex-col-resize {
6 | cursor: col-resize;
7 | }
8 |
9 | body.reflex-row-resize {
10 | cursor: row-resize;
11 | }
12 |
13 | /////////////////////////////////////////////////////////
14 | // Re-Flex Container
15 | //
16 | /////////////////////////////////////////////////////////
17 | .reflex-container {
18 | justify-content: flex-start; /* align items in Main Axis */
19 | align-items: stretch; /* align items in Cross Axis */
20 | align-content: stretch;
21 | display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
22 | display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
23 | display: -ms-flexbox; /* TWEENER - IE 10 */
24 | display: -webkit-flex; /* NEW - Chrome */
25 | display: flex;
26 | position: relative;
27 |
28 | height: 100%;
29 | width: 100%;
30 | }
31 |
32 | .reflex-container.horizontal {
33 | flex-direction: column;
34 | min-height: 1px;
35 | }
36 |
37 | .reflex-container.vertical {
38 | flex-direction: row;
39 | min-width: 1px;
40 | }
41 |
42 | /////////////////////////////////////////////////////////
43 | //Re-Flex Element
44 | //
45 | /////////////////////////////////////////////////////////
46 | .reflex-container > .reflex-element {
47 | position: relative;
48 | overflow: auto;
49 | height: 100%;
50 | width: 100%;
51 | }
52 |
53 | .reflex-container.reflex-resizing > .reflex-element {
54 | pointer-events: none;
55 | user-select: none;
56 | }
57 |
58 | .reflex-container > .reflex-element > .reflex-size-aware {
59 | height: 100%;
60 | width: 100%;
61 | }
62 |
63 | /////////////////////////////////////////////////////////
64 | //Re-Flex Splitter
65 | //
66 | /////////////////////////////////////////////////////////
67 | .reflex-container > .reflex-splitter {
68 | background-color: #eeeeee;
69 | z-index: 100;
70 | }
71 |
72 | .reflex-container > .reflex-splitter.active,
73 | .reflex-container > .reflex-splitter:hover {
74 | background-color: #c6c6c6;
75 | transition: all 1s ease;
76 | }
77 |
78 | .horizontal > .reflex-splitter {
79 | border-bottom: 1px solid #c6c6c6;
80 | border-top: 1px solid #c6c6c6;
81 | cursor: row-resize;
82 | width: 100%;
83 | height: 2px;
84 | }
85 |
86 | .reflex-element.horizontal .reflex-handle {
87 | cursor: row-resize;
88 | user-select: none;
89 | }
90 |
91 | .reflex-container.horizontal > .reflex-splitter:hover,
92 | .reflex-container.horizontal > .reflex-splitter.active {
93 | border-bottom: 1px solid #eeeeee;
94 | border-top: 1px solid #eeeeee;
95 | }
96 |
97 | .reflex-container.vertical > .reflex-splitter {
98 | border-right: 1px solid #c6c6c6;
99 | border-left: 1px solid #c6c6c6;
100 | cursor: col-resize;
101 | height: 100%;
102 | width: 2px;
103 | }
104 |
105 | .reflex-element.vertical .reflex-handle {
106 | cursor: col-resize;
107 | user-select: none;
108 | }
109 |
110 | .reflex-container.vertical > .reflex-splitter:hover,
111 | .reflex-container.vertical > .reflex-splitter.active {
112 | border-right: 1px solid #eeeeee;
113 | border-left: 1px solid #eeeeee;
114 | }
115 |
116 | /////////////////////////////////////////////////////////
117 | //Re-Flex Splitter reflex-thin
118 | //
119 | /////////////////////////////////////////////////////////
120 | .reflex-container > .reflex-splitter.reflex-thin {
121 | -moz-box-sizing: border-box;
122 | -webkit-box-sizing: border-box;
123 | box-sizing: border-box;
124 | -moz-background-clip: padding;
125 | -webkit-background-clip: padding;
126 | background-clip: padding-box;
127 | opacity: 0.2;
128 | z-index: 100;
129 | }
130 |
131 | .reflex-container > .reflex-splitter.reflex-thin.active
132 | .reflex-container > .reflex-splitter.reflex-thin:hover {
133 | transition: all 1.5s ease;
134 | opacity: 0.5;
135 | }
136 |
137 | .reflex-container.horizontal > .reflex-splitter.reflex-thin {
138 | border-bottom: 8px solid rgba(255, 255, 255, 0);
139 | border-top: 8px solid rgba(255, 255, 255, 0);
140 | height: 17px !important;
141 | cursor: row-resize;
142 | margin: -8px 0;
143 | width: 100%;
144 | }
145 |
146 | .reflex-container.horizontal > .reflex-splitter.reflex-thin.active,
147 | .reflex-container.horizontal > .reflex-splitter.reflex-thin:hover {
148 | border-bottom: 8px solid rgba(228, 228, 228, 1);
149 | border-top: 8px solid rgba(228, 228, 228, 1);
150 | }
151 |
152 | .reflex-container.vertical > .reflex-splitter.reflex-thin {
153 | border-right: 8px solid rgba(255, 255, 255, 0);
154 | border-left: 8px solid rgba(255, 255, 255, 0);
155 | width: 17px !important;
156 | cursor: col-resize;
157 | margin: 0 -8px;
158 | height: 100%;
159 | }
160 |
161 | .reflex-container.vertical > .reflex-splitter.reflex-thin.active,
162 | .reflex-container.vertical > .reflex-splitter.reflex-thin:hover {
163 | border-right: 8px solid rgba(228, 228, 228, 1);
164 | border-left: 8px solid rgba(228, 228, 228, 1);
165 | }
166 |
--------------------------------------------------------------------------------
/src/lib/utilities.js:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////
2 | // Browser Utils
3 | //
4 | /////////////////////////////////////////////////////////
5 | class Browser {
6 |
7 | // Check if not running on server
8 | static isBrowser () {
9 | return typeof window !== 'undefined';
10 | }
11 |
12 | // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
13 | static isOpera () {
14 | return Browser.isBrowser() && (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0)
15 | }
16 |
17 | // Firefox 1.0+
18 | static isFirefox () {
19 | return Browser.isBrowser() && (typeof InstallTrigger !== 'undefined')
20 | }
21 |
22 | // Safari 3.0+
23 | static isSafari () {
24 |
25 | if (!Browser.isBrowser()) {
26 | return false;
27 | }
28 |
29 | return (/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
30 | }
31 |
32 | // Internet Explorer 6-11
33 | static isIE () {
34 | /*@cc_on!@*/
35 | return Browser.isBrowser() && !!document.documentMode
36 | }
37 |
38 | // Edge 20+
39 | static isEdge () {
40 | return Browser.isBrowser() && (!Browser.isIE() && !!window.StyleMedia)
41 | }
42 |
43 | // Chrome 1+
44 | static isChrome () {
45 | return Browser.isBrowser() && (!!window.chrome && !!window.chrome.webstore)
46 | }
47 |
48 | // Blink engine detection
49 | static isBlink () {
50 | return Browser.isBrowser() && ((Browser.isChrome() || Browser.isOpera()) && !!window.CSS)
51 | }
52 |
53 |
54 | static getUserAgent () {
55 | return typeof navigator === 'undefined' ? '' : navigator.userAgent
56 | }
57 |
58 | static isAndroid () {
59 | return Browser.isBrowser() && Browser.getUserAgent().match(/Android/i)
60 | }
61 |
62 | static isBlackBerry () {
63 | return Browser.isBrowser() && Browser.getUserAgent().match(/BlackBerry/i)
64 | }
65 |
66 | static isIOS () {
67 | return Browser.isBrowser() && Browser.getUserAgent().match(/iPhone|iPad|iPod/i)
68 | }
69 |
70 | static isOpera () {
71 | return Browser.isBrowser() && Browser.getUserAgent().match(/Opera Mini/i)
72 | }
73 |
74 | static isWindows () {
75 | return Browser.isBrowser() && Browser.isWindowsDesktop() || Browser.isWindowsMobile()
76 | }
77 |
78 | static isWindowsMobile () {
79 | return Browser.isBrowser() && Browser.getUserAgent().match(/IEMobile/i)
80 | }
81 |
82 | static isWindowsDesktop () {
83 | return Browser.isBrowser() && Browser.getUserAgent().match(/WPDesktop/i)
84 | }
85 |
86 | static isMobile () {
87 |
88 | return Browser.isBrowser() &&
89 | (Browser.isWindowsMobile() ||
90 | Browser.isBlackBerry() ||
91 | Browser.isAndroid() ||
92 | Browser.isIOS())
93 | }
94 | }
95 |
96 | /////////////////////////////////////////////////////////
97 | // Returns only the props that start with "data-"
98 | //
99 | /////////////////////////////////////////////////////////
100 | const getDataProps = (props) => {
101 | return Object.keys(props).reduce((prev, key) => {
102 | if (key.substr(0, 5) === 'data-') {
103 | return {
104 | ...prev,
105 | [key]: props[key]
106 | }
107 | }
108 | return prev
109 | }, {})
110 | }
111 |
112 | export {
113 | getDataProps,
114 | Browser
115 | }
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | body.reflex-col-resize {
2 | cursor: col-resize; }
3 |
4 | body.reflex-row-resize {
5 | cursor: row-resize; }
6 |
7 | .reflex-container {
8 | justify-content: flex-start;
9 | /* align items in Main Axis */
10 | align-items: stretch;
11 | /* align items in Cross Axis */
12 | align-content: stretch;
13 | /* OLD - iOS 6-, Safari 3.1-6 */
14 | /* OLD - Firefox 19- (buggy but mostly works) */
15 | /* TWEENER - IE 10 */
16 | /* NEW - Chrome */
17 | display: flex;
18 | position: relative;
19 | height: 100%;
20 | width: 100%; }
21 |
22 | .reflex-container.horizontal {
23 | flex-direction: column;
24 | min-height: 1px; }
25 |
26 | .reflex-container.vertical {
27 | flex-direction: row;
28 | min-width: 1px; }
29 |
30 | .reflex-container > .reflex-element {
31 | position: relative;
32 | overflow: auto;
33 | height: 100%;
34 | width: 100%; }
35 |
36 | .reflex-container.reflex-resizing > .reflex-element {
37 | pointer-events: none;
38 | -webkit-user-select: none;
39 | -moz-user-select: none;
40 | user-select: none; }
41 |
42 | .reflex-container > .reflex-element > .reflex-size-aware {
43 | height: 100%;
44 | width: 100%; }
45 |
46 | .reflex-container > .reflex-splitter {
47 | background-color: #eeeeee;
48 | z-index: 100; }
49 |
50 | .reflex-container > .reflex-splitter.active,
51 | .reflex-container > .reflex-splitter:hover {
52 | background-color: #c6c6c6;
53 | transition: all 1s ease; }
54 |
55 | .horizontal > .reflex-splitter {
56 | border-bottom: 1px solid #c6c6c6;
57 | border-top: 1px solid #c6c6c6;
58 | cursor: row-resize;
59 | width: 100%;
60 | height: 2px; }
61 |
62 | .reflex-element.horizontal .reflex-handle {
63 | cursor: row-resize;
64 | -webkit-user-select: none;
65 | -moz-user-select: none;
66 | user-select: none; }
67 |
68 | .reflex-container.horizontal > .reflex-splitter:hover,
69 | .reflex-container.horizontal > .reflex-splitter.active {
70 | border-bottom: 1px solid #eeeeee;
71 | border-top: 1px solid #eeeeee; }
72 |
73 | .reflex-container.vertical > .reflex-splitter {
74 | border-right: 1px solid #c6c6c6;
75 | border-left: 1px solid #c6c6c6;
76 | cursor: col-resize;
77 | height: 100%;
78 | width: 2px; }
79 |
80 | .reflex-element.vertical .reflex-handle {
81 | cursor: col-resize;
82 | -webkit-user-select: none;
83 | -moz-user-select: none;
84 | user-select: none; }
85 |
86 | .reflex-container.vertical > .reflex-splitter:hover,
87 | .reflex-container.vertical > .reflex-splitter.active {
88 | border-right: 1px solid #eeeeee;
89 | border-left: 1px solid #eeeeee; }
90 |
91 | .reflex-container > .reflex-splitter.reflex-thin {
92 | box-sizing: border-box;
93 | -moz-background-clip: padding;
94 | -webkit-background-clip: padding;
95 | background-clip: padding-box;
96 | opacity: 0.2;
97 | z-index: 100; }
98 |
99 | .reflex-container > .reflex-splitter.reflex-thin.active
100 | .reflex-container > .reflex-splitter.reflex-thin:hover {
101 | transition: all 1.5s ease;
102 | opacity: 0.5; }
103 |
104 | .reflex-container.horizontal > .reflex-splitter.reflex-thin {
105 | border-bottom: 8px solid rgba(255, 255, 255, 0);
106 | border-top: 8px solid rgba(255, 255, 255, 0);
107 | height: 17px !important;
108 | cursor: row-resize;
109 | margin: -8px 0;
110 | width: 100%; }
111 |
112 | .reflex-container.horizontal > .reflex-splitter.reflex-thin.active,
113 | .reflex-container.horizontal > .reflex-splitter.reflex-thin:hover {
114 | border-bottom: 8px solid #e4e4e4;
115 | border-top: 8px solid #e4e4e4; }
116 |
117 | .reflex-container.vertical > .reflex-splitter.reflex-thin {
118 | border-right: 8px solid rgba(255, 255, 255, 0);
119 | border-left: 8px solid rgba(255, 255, 255, 0);
120 | width: 17px !important;
121 | cursor: col-resize;
122 | margin: 0 -8px;
123 | height: 100%; }
124 |
125 | .reflex-container.vertical > .reflex-splitter.reflex-thin.active,
126 | .reflex-container.vertical > .reflex-splitter.reflex-thin:hover {
127 | border-right: 8px solid #e4e4e4;
128 | border-left: 8px solid #e4e4e4; }
129 |
130 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxrQkFBa0IsRUFBRTs7QUFFdEI7RUFDRSxrQkFBa0IsRUFBRTs7QUFFdEI7RUFDRSwyQkFBMkI7RUFDM0IsNkJBQTZCO0VBQzdCLG9CQUFvQjtFQUNwQiw4QkFBOEI7RUFDOUIsc0JBQXNCO0VBRXRCLCtCQUErQjtFQUUvQiwrQ0FBK0M7RUFFL0Msb0JBQW9CO0VBRXBCLGlCQUFpQjtFQUNqQixhQUFhO0VBQ2Isa0JBQWtCO0VBQ2xCLFlBQVk7RUFDWixXQUFXLEVBQUU7O0FBRWY7RUFDRSxzQkFBc0I7RUFDdEIsZUFBZSxFQUFFOztBQUVuQjtFQUNFLG1CQUFtQjtFQUNuQixjQUFjLEVBQUU7O0FBRWxCO0VBQ0Usa0JBQWtCO0VBQ2xCLGNBQWM7RUFDZCxZQUFZO0VBQ1osV0FBVyxFQUFFOztBQUVmO0VBQ0Usb0JBQW9CO0VBQ3BCLHlCQUFpQjtLQUFqQixzQkFBaUI7VUFBakIsaUJBQWlCLEVBQUU7O0FBRXJCO0VBQ0UsWUFBWTtFQUNaLFdBQVcsRUFBRTs7QUFFZjtFQUNFLHlCQUF5QjtFQUN6QixZQUFZLEVBQUU7O0FBRWhCOztFQUVFLHlCQUF5QjtFQUN6Qix1QkFBdUIsRUFBRTs7QUFFM0I7RUFDRSxnQ0FBZ0M7RUFDaEMsNkJBQTZCO0VBQzdCLGtCQUFrQjtFQUNsQixXQUFXO0VBQ1gsV0FBVyxFQUFFOztBQUVmO0VBQ0Usa0JBQWtCO0VBQ2xCLHlCQUFpQjtLQUFqQixzQkFBaUI7VUFBakIsaUJBQWlCLEVBQUU7O0FBRXJCOztFQUVFLGdDQUFnQztFQUNoQyw2QkFBNkIsRUFBRTs7QUFFakM7RUFDRSwrQkFBK0I7RUFDL0IsOEJBQThCO0VBQzlCLGtCQUFrQjtFQUNsQixZQUFZO0VBQ1osVUFBVSxFQUFFOztBQUVkO0VBQ0Usa0JBQWtCO0VBQ2xCLHlCQUFpQjtLQUFqQixzQkFBaUI7VUFBakIsaUJBQWlCLEVBQUU7O0FBRXJCOztFQUVFLCtCQUErQjtFQUMvQiw4QkFBOEIsRUFBRTs7QUFFbEM7RUFHRSxzQkFBc0I7RUFDdEIsNkJBQTZCO0VBQzdCLGdDQUFnQztFQUNoQyw0QkFBNEI7RUFDNUIsWUFBWTtFQUNaLFlBQVksRUFBRTs7QUFFaEI7O0VBRUUseUJBQXlCO0VBQ3pCLFlBQVksRUFBRTs7QUFFaEI7RUFDRSwrQ0FBK0M7RUFDL0MsNENBQTRDO0VBQzVDLHVCQUF1QjtFQUN2QixrQkFBa0I7RUFDbEIsY0FBYztFQUNkLFdBQVcsRUFBRTs7QUFFZjs7RUFFRSxnQ0FBZ0M7RUFDaEMsNkJBQTZCLEVBQUU7O0FBRWpDO0VBQ0UsOENBQThDO0VBQzlDLDZDQUE2QztFQUM3QyxzQkFBc0I7RUFDdEIsa0JBQWtCO0VBQ2xCLGNBQWM7RUFDZCxZQUFZLEVBQUU7O0FBRWhCOztFQUVFLCtCQUErQjtFQUMvQiw4QkFBOEIsRUFBRSIsImZpbGUiOiJzdHlsZXMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiYm9keS5yZWZsZXgtY29sLXJlc2l6ZSB7XG4gIGN1cnNvcjogY29sLXJlc2l6ZTsgfVxuXG5ib2R5LnJlZmxleC1yb3ctcmVzaXplIHtcbiAgY3Vyc29yOiByb3ctcmVzaXplOyB9XG5cbi5yZWZsZXgtY29udGFpbmVyIHtcbiAganVzdGlmeS1jb250ZW50OiBmbGV4LXN0YXJ0O1xuICAvKiBhbGlnbiBpdGVtcyBpbiBNYWluIEF4aXMgKi9cbiAgYWxpZ24taXRlbXM6IHN0cmV0Y2g7XG4gIC8qIGFsaWduIGl0ZW1zIGluIENyb3NzIEF4aXMgKi9cbiAgYWxpZ24tY29udGVudDogc3RyZXRjaDtcbiAgZGlzcGxheTogLXdlYmtpdC1ib3g7XG4gIC8qIE9MRCAtIGlPUyA2LSwgU2FmYXJpIDMuMS02ICovXG4gIGRpc3BsYXk6IC1tb3otYm94O1xuICAvKiBPTEQgLSBGaXJlZm94IDE5LSAoYnVnZ3kgYnV0IG1vc3RseSB3b3JrcykgKi9cbiAgZGlzcGxheTogLW1zLWZsZXhib3g7XG4gIC8qIFRXRUVORVIgLSBJRSAxMCAqL1xuICBkaXNwbGF5OiAtd2Via2l0LWZsZXg7XG4gIC8qIE5FVyAtIENocm9tZSAqL1xuICBkaXNwbGF5OiBmbGV4O1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGhlaWdodDogMTAwJTtcbiAgd2lkdGg6IDEwMCU7IH1cblxuLnJlZmxleC1jb250YWluZXIuaG9yaXpvbnRhbCB7XG4gIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gIG1pbi1oZWlnaHQ6IDFweDsgfVxuXG4ucmVmbGV4LWNvbnRhaW5lci52ZXJ0aWNhbCB7XG4gIGZsZXgtZGlyZWN0aW9uOiByb3c7XG4gIG1pbi13aWR0aDogMXB4OyB9XG5cbi5yZWZsZXgtY29udGFpbmVyID4gLnJlZmxleC1lbGVtZW50IHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICBvdmVyZmxvdzogYXV0bztcbiAgaGVpZ2h0OiAxMDAlO1xuICB3aWR0aDogMTAwJTsgfVxuXG4ucmVmbGV4LWNvbnRhaW5lci5yZWZsZXgtcmVzaXppbmcgPiAucmVmbGV4LWVsZW1lbnQge1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgdXNlci1zZWxlY3Q6IG5vbmU7IH1cblxuLnJlZmxleC1jb250YWluZXIgPiAucmVmbGV4LWVsZW1lbnQgPiAucmVmbGV4LXNpemUtYXdhcmUge1xuICBoZWlnaHQ6IDEwMCU7XG4gIHdpZHRoOiAxMDAlOyB9XG5cbi5yZWZsZXgtY29udGFpbmVyID4gLnJlZmxleC1zcGxpdHRlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNlZWVlZWU7XG4gIHotaW5kZXg6IDEwMDsgfVxuXG4ucmVmbGV4LWNvbnRhaW5lciA+IC5yZWZsZXgtc3BsaXR0ZXIuYWN0aXZlLFxuLnJlZmxleC1jb250YWluZXIgPiAucmVmbGV4LXNwbGl0dGVyOmhvdmVyIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2M2YzZjNjtcbiAgdHJhbnNpdGlvbjogYWxsIDFzIGVhc2U7IH1cblxuLmhvcml6b250YWwgPiAucmVmbGV4LXNwbGl0dGVyIHtcbiAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNjNmM2YzY7XG4gIGJvcmRlci10b3A6IDFweCBzb2xpZCAjYzZjNmM2O1xuICBjdXJzb3I6IHJvdy1yZXNpemU7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDJweDsgfVxuXG4ucmVmbGV4LWVsZW1lbnQuaG9yaXpvbnRhbCAucmVmbGV4LWhhbmRsZSB7XG4gIGN1cnNvcjogcm93LXJlc2l6ZTtcbiAgdXNlci1zZWxlY3Q6IG5vbmU7IH1cblxuLnJlZmxleC1jb250YWluZXIuaG9yaXpvbnRhbCA+IC5yZWZsZXgtc3BsaXR0ZXI6aG92ZXIsXG4ucmVmbGV4LWNvbnRhaW5lci5ob3Jpem9udGFsID4gLnJlZmxleC1zcGxpdHRlci5hY3RpdmUge1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2VlZWVlZTtcbiAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlZWVlZWU7IH1cblxuLnJlZmxleC1jb250YWluZXIudmVydGljYWwgPiAucmVmbGV4LXNwbGl0dGVyIHtcbiAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2M2YzZjNjtcbiAgYm9yZGVyLWxlZnQ6IDFweCBzb2xpZCAjYzZjNmM2O1xuICBjdXJzb3I6IGNvbC1yZXNpemU7XG4gIGhlaWdodDogMTAwJTtcbiAgd2lkdGg6IDJweDsgfVxuXG4ucmVmbGV4LWVsZW1lbnQudmVydGljYWwgLnJlZmxleC1oYW5kbGUge1xuICBjdXJzb3I6IGNvbC1yZXNpemU7XG4gIHVzZXItc2VsZWN0OiBub25lOyB9XG5cbi5yZWZsZXgtY29udGFpbmVyLnZlcnRpY2FsID4gLnJlZmxleC1zcGxpdHRlcjpob3Zlcixcbi5yZWZsZXgtY29udGFpbmVyLnZlcnRpY2FsID4gLnJlZmxleC1zcGxpdHRlci5hY3RpdmUge1xuICBib3JkZXItcmlnaHQ6IDFweCBzb2xpZCAjZWVlZWVlO1xuICBib3JkZXItbGVmdDogMXB4IHNvbGlkICNlZWVlZWU7IH1cblxuLnJlZmxleC1jb250YWluZXIgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluIHtcbiAgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gIC1tb3otYmFja2dyb3VuZC1jbGlwOiBwYWRkaW5nO1xuICAtd2Via2l0LWJhY2tncm91bmQtY2xpcDogcGFkZGluZztcbiAgYmFja2dyb3VuZC1jbGlwOiBwYWRkaW5nLWJveDtcbiAgb3BhY2l0eTogMC4yO1xuICB6LWluZGV4OiAxMDA7IH1cblxuLnJlZmxleC1jb250YWluZXIgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluLmFjdGl2ZVxuLnJlZmxleC1jb250YWluZXIgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluOmhvdmVyIHtcbiAgdHJhbnNpdGlvbjogYWxsIDEuNXMgZWFzZTtcbiAgb3BhY2l0eTogMC41OyB9XG5cbi5yZWZsZXgtY29udGFpbmVyLmhvcml6b250YWwgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluIHtcbiAgYm9yZGVyLWJvdHRvbTogOHB4IHNvbGlkIHJnYmEoMjU1LCAyNTUsIDI1NSwgMCk7XG4gIGJvcmRlci10b3A6IDhweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDApO1xuICBoZWlnaHQ6IDE3cHggIWltcG9ydGFudDtcbiAgY3Vyc29yOiByb3ctcmVzaXplO1xuICBtYXJnaW46IC04cHggMDtcbiAgd2lkdGg6IDEwMCU7IH1cblxuLnJlZmxleC1jb250YWluZXIuaG9yaXpvbnRhbCA+IC5yZWZsZXgtc3BsaXR0ZXIucmVmbGV4LXRoaW4uYWN0aXZlLFxuLnJlZmxleC1jb250YWluZXIuaG9yaXpvbnRhbCA+IC5yZWZsZXgtc3BsaXR0ZXIucmVmbGV4LXRoaW46aG92ZXIge1xuICBib3JkZXItYm90dG9tOiA4cHggc29saWQgI2U0ZTRlNDtcbiAgYm9yZGVyLXRvcDogOHB4IHNvbGlkICNlNGU0ZTQ7IH1cblxuLnJlZmxleC1jb250YWluZXIudmVydGljYWwgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluIHtcbiAgYm9yZGVyLXJpZ2h0OiA4cHggc29saWQgcmdiYSgyNTUsIDI1NSwgMjU1LCAwKTtcbiAgYm9yZGVyLWxlZnQ6IDhweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDApO1xuICB3aWR0aDogMTdweCAhaW1wb3J0YW50O1xuICBjdXJzb3I6IGNvbC1yZXNpemU7XG4gIG1hcmdpbjogMCAtOHB4O1xuICBoZWlnaHQ6IDEwMCU7IH1cblxuLnJlZmxleC1jb250YWluZXIudmVydGljYWwgPiAucmVmbGV4LXNwbGl0dGVyLnJlZmxleC10aGluLmFjdGl2ZSxcbi5yZWZsZXgtY29udGFpbmVyLnZlcnRpY2FsID4gLnJlZmxleC1zcGxpdHRlci5yZWZsZXgtdGhpbjpob3ZlciB7XG4gIGJvcmRlci1yaWdodDogOHB4IHNvbGlkICNlNGU0ZTQ7XG4gIGJvcmRlci1sZWZ0OiA4cHggc29saWQgI2U0ZTRlNDsgfVxuIl19 */
--------------------------------------------------------------------------------
/webapps-using-reflex.md:
--------------------------------------------------------------------------------
1 |
2 | ## Web Applications using Re-F|ex
3 |
4 | * [Autodesk Forge RCDB](https://forge-rcdb.autodesk.io/configurator?id=57f3739777c879f48ad54a44)
5 |
6 | 
7 |
8 | * [CodecastJS.com](https://codecastjs.com)
9 |
10 | 
11 |
12 | * [Codier](https://codier.io)
13 |
14 | 
15 |
16 | (Feel free to add your own by submitting a pull request...)
17 |
--------------------------------------------------------------------------------
/webpack/demo/development.webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 |
5 | context: path.join(__dirname, '../..'),
6 |
7 | devtool: 'source-map',
8 |
9 | mode: 'development',
10 |
11 | entry: {
12 | bundle: [
13 | './src/demo/index.js'
14 | ]
15 | },
16 |
17 | output: {
18 | path: path.join(__dirname, '../../dist/demo'),
19 | filename: "[name].js"
20 | },
21 |
22 | plugins: [],
23 |
24 | resolve: {
25 | extensions: ['.js', '.jsx', '.json']
26 | },
27 |
28 | module: {
29 |
30 | rules: [
31 | {
32 | test: /\.jsx?$/,
33 | exclude: /node_modules/,
34 | use: [{
35 | loader: "babel-loader",
36 | options: {
37 | presets: ['@babel/react', '@babel/env'],
38 | plugins: [
39 | 'react-hot-loader/babel',
40 | "@babel/plugin-proposal-nullish-coalescing-operator",
41 | "@babel/plugin-proposal-optional-chaining",
42 | '@babel/plugin-proposal-class-properties',
43 | '@babel/plugin-syntax-dynamic-import',
44 | '@babel/transform-runtime'
45 | ]
46 | }
47 | }]
48 | },
49 | {
50 | test: /\.(css|sass|scss)$/,
51 | use: [{
52 | loader:'style-loader'
53 | }, {
54 | loader: 'css-loader'
55 | }, {
56 | loader: 'postcss-loader',
57 | options: {
58 | plugins: function () {
59 | return [
60 | require('precss'),
61 | require('autoprefixer')
62 | ]
63 | }
64 | }
65 | }, {
66 | loader:'sass-loader'
67 | }]
68 | }
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/webpack/demo/production.webpack.config.js:
--------------------------------------------------------------------------------
1 | var clean = require('clean-webpack-plugin')
2 | var webpack = require('webpack')
3 | var path = require('path')
4 |
5 | module.exports = {
6 |
7 | context: path.join(__dirname, '../..'),
8 | mode: 'production',
9 | devtool: 'none',
10 |
11 | entry: {
12 | bundle: [
13 | './src/demo/index.js'
14 | ]
15 | },
16 |
17 | output: {
18 | path: path.join(__dirname, '../../dist/demo'),
19 | filename: "[name].js"
20 | },
21 |
22 | optimization: {
23 | minimize: true
24 | },
25 |
26 | plugins: [
27 |
28 | new clean(['dist/demo'], {
29 | root: __dirname + '/..',
30 | verbose: true,
31 | dry: false
32 | }),
33 |
34 | new webpack.optimize.MinChunkSizePlugin({
35 | minChunkSize: 51200
36 | }),
37 |
38 | new webpack.DefinePlugin({
39 | 'process.env.NODE_ENV': '"production"'
40 | }),
41 |
42 | new webpack.ProvidePlugin({
43 | Promise: 'es6-promise'
44 | }),
45 |
46 | new webpack.NoEmitOnErrorsPlugin()
47 | ],
48 |
49 | resolve: {
50 | extensions: ['.js', '.jsx', '.json']
51 | },
52 |
53 | stats: {
54 | warnings: false
55 | },
56 |
57 | module: {
58 |
59 | rules: [
60 | {
61 | test: /\.jsx?$/,
62 | exclude: /node_modules/,
63 | use: [{
64 | loader: "babel-loader",
65 | options: {
66 | presets: [
67 | '@babel/react',
68 | ["@babel/env", {
69 | "targets": {
70 | "browsers": [
71 | "last 2 versions",
72 | "ie >= 11"
73 | ]
74 | }
75 | }],
76 | ],
77 | plugins: [
78 | "@babel/plugin-proposal-nullish-coalescing-operator",
79 | "@babel/plugin-proposal-optional-chaining",
80 | '@babel/plugin-proposal-class-properties',
81 | '@babel/plugin-syntax-dynamic-import',
82 | '@babel/transform-runtime'
83 | ]
84 | }
85 | }]
86 | },
87 | {
88 | test: /\.(css|sass|scss)$/,
89 | use: [{
90 | loader:'style-loader'
91 | }, {
92 | loader: 'css-loader'
93 | }, {
94 | loader: 'postcss-loader',
95 | options: {
96 | plugins: function () {
97 | return [
98 | require('precss'),
99 | require('autoprefixer')
100 | ]
101 | }
102 | }
103 | }, {
104 | loader:'sass-loader'
105 | }]
106 | }
107 | ]
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/webpack/lib/development.webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 |
5 | context: path.join(__dirname, '../..'),
6 |
7 | devtool: 'source-map',
8 |
9 | mode: 'development',
10 |
11 | entry: {
12 | 'react-reflex': [
13 | './src/lib/index.js'
14 | ]
15 | },
16 |
17 | output: {
18 | path: path.join(__dirname, '../../dist/umd'),
19 | library: 'react-reflex',
20 | filename: '[name].js',
21 | libraryTarget: 'umd'
22 | },
23 |
24 | module: {
25 | rules: [
26 | {
27 | test: /\.jsx?$/,
28 | exclude: /node_modules/,
29 | use: [{
30 | loader: 'babel-loader',
31 | options: {
32 | presets: ['@babel/react', '@babel/env'],
33 | plugins: [
34 | "react-hot-loader/babel",
35 | "@babel/plugin-proposal-nullish-coalescing-operator",
36 | "@babel/plugin-proposal-optional-chaining",
37 | "@babel/plugin-proposal-class-properties",
38 | "@babel/plugin-syntax-dynamic-import",
39 | '@babel/transform-runtime'
40 | ]
41 | }
42 | }]
43 | },
44 | ]
45 | },
46 |
47 | resolve: {
48 | extensions: ['.js', '.jsx', '.json']
49 | },
50 |
51 | externals: {
52 | react: {
53 | root: 'React',
54 | commonjs: 'react',
55 | commonjs2: 'react',
56 | amd: 'react'
57 | },
58 | "react-dom": {
59 | root: 'ReactDOM',
60 | commonjs: 'react-dom',
61 | commonjs2: 'react-dom',
62 | amd: 'react-dom'
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/webpack/lib/production.webpack.config.js:
--------------------------------------------------------------------------------
1 | var clean = require('clean-webpack-plugin')
2 | var webpack = require('webpack')
3 | var path = require('path')
4 |
5 | module.exports = {
6 |
7 | context: path.join(__dirname, '../..'),
8 |
9 | mode: 'production',
10 |
11 | entry: {
12 | 'react-reflex.min': [
13 | './src/lib/index.js'
14 | ]
15 | },
16 |
17 | output: {
18 | path: path.join(__dirname, '../../dist/umd'),
19 | library: 'react-reflex',
20 | filename: '[name].js',
21 | libraryTarget: 'umd'
22 | },
23 |
24 | optimization: {
25 | minimize: true
26 | },
27 |
28 | plugins: [
29 |
30 | new clean(['dist/umd'], {
31 | root: __dirname + '/..',
32 | verbose: true,
33 | dry: false
34 | }),
35 |
36 | new webpack.optimize.MinChunkSizePlugin({
37 | minChunkSize: 51200
38 | }),
39 |
40 | new webpack.DefinePlugin({
41 | 'process.env.NODE_ENV': '"production"'
42 | }),
43 |
44 | new webpack.NoEmitOnErrorsPlugin()
45 | ],
46 |
47 | module: {
48 | rules: [
49 | {
50 | test: /\.(js|jsx)$/,
51 | exclude: /(node_modules)/,
52 | use: {
53 | loader: 'babel-loader',
54 | options: {
55 | presets: ['@babel/react', '@babel/env'],
56 | plugins: [
57 | "@babel/plugin-proposal-nullish-coalescing-operator",
58 | "@babel/plugin-proposal-optional-chaining",
59 | '@babel/plugin-proposal-class-properties',
60 | '@babel/plugin-syntax-dynamic-import',
61 | '@babel/transform-runtime'
62 | ]
63 | }
64 | }
65 | }
66 | ]
67 | },
68 |
69 | resolve: {
70 | extensions: ['.js', '.jsx', '.json']
71 | },
72 |
73 | externals: {
74 | react: {
75 | root: 'React',
76 | commonjs: 'react',
77 | commonjs2: 'react',
78 | amd: 'react'
79 | },
80 | "react-dom": {
81 | root: 'ReactDOM',
82 | commonjs: 'react-dom',
83 | commonjs2: 'react-dom',
84 | amd: 'react-dom'
85 | }
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------