├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmrc ├── LICENSE.md ├── README.md ├── dist ├── commonjs │ ├── Polyfills.js │ ├── ReflexContainer.js │ ├── ReflexElement.js │ ├── ReflexEvents.js │ ├── ReflexHandle.js │ ├── ReflexSplitter.js │ ├── index.js │ └── utilities.js ├── demo │ └── bundle.js ├── es │ ├── Polyfills.js │ ├── ReflexContainer.js │ ├── ReflexElement.js │ ├── ReflexEvents.js │ ├── ReflexHandle.js │ ├── ReflexSplitter.js │ ├── index.js │ └── utilities.js ├── index.d.ts └── umd │ ├── react-reflex.js │ ├── react-reflex.js.map │ └── react-reflex.min.js ├── index.d.ts ├── index.html ├── package-lock.json ├── package.json ├── pakmanaged.js ├── resources └── img │ ├── demo.png │ ├── re-flex-banner.png │ ├── re-flex.png │ └── react.png ├── src ├── demo │ ├── demo.jsx │ ├── demo.scss │ └── index.js └── lib │ ├── Polyfills.js │ ├── ReflexContainer.js │ ├── ReflexElement.js │ ├── ReflexEvents.js │ ├── ReflexHandle.js │ ├── ReflexSplitter.js │ ├── index.js │ ├── reflex-styles.scss │ └── utilities.js ├── styles.css ├── webapps-using-reflex.md └── webpack ├── demo ├── development.webpack.config.js └── production.webpack.config.js └── lib ├── development.webpack.config.js └── production.webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": { 4 | "plugins": [ 5 | "@babel/plugin-proposal-object-rest-spread", 6 | "@babel/plugin-transform-runtime", 7 | "@babel/plugin-syntax-dynamic-import", 8 | "@babel/plugin-syntax-import-meta", 9 | "@babel/plugin-proposal-class-properties", 10 | "@babel/plugin-proposal-json-strings", 11 | [ 12 | "@babel/plugin-proposal-decorators", 13 | { 14 | "legacy": true 15 | } 16 | ], 17 | "@babel/plugin-proposal-function-sent", 18 | "@babel/plugin-proposal-export-namespace-from", 19 | "@babel/plugin-proposal-numeric-separator", 20 | "@babel/plugin-proposal-throw-expressions", 21 | "@babel/plugin-proposal-export-default-from", 22 | "@babel/plugin-proposal-logical-assignment-operators", 23 | "@babel/plugin-proposal-optional-chaining", 24 | [ 25 | "@babel/plugin-proposal-pipeline-operator", 26 | { 27 | "proposal": "minimal" 28 | } 29 | ], 30 | "@babel/plugin-proposal-nullish-coalescing-operator", 31 | "@babel/plugin-proposal-do-expressions", 32 | "@babel/plugin-proposal-function-bind" 33 | ], 34 | "presets": [ 35 | "@babel/preset-react", 36 | "@babel/preset-env" 37 | ] 38 | }, 39 | "es": { 40 | "plugins": [ 41 | "@babel/plugin-proposal-object-rest-spread", 42 | "@babel/plugin-transform-runtime", 43 | "@babel/plugin-syntax-dynamic-import", 44 | "@babel/plugin-syntax-import-meta", 45 | "@babel/plugin-proposal-class-properties", 46 | "@babel/plugin-proposal-json-strings", 47 | [ 48 | "@babel/plugin-proposal-decorators", 49 | { 50 | "legacy": true 51 | } 52 | ], 53 | "@babel/plugin-proposal-function-sent", 54 | "@babel/plugin-proposal-export-namespace-from", 55 | "@babel/plugin-proposal-numeric-separator", 56 | "@babel/plugin-proposal-throw-expressions", 57 | "@babel/plugin-proposal-export-default-from", 58 | "@babel/plugin-proposal-logical-assignment-operators", 59 | "@babel/plugin-proposal-optional-chaining", 60 | [ 61 | "@babel/plugin-proposal-pipeline-operator", 62 | { 63 | "proposal": "minimal" 64 | } 65 | ], 66 | "@babel/plugin-proposal-nullish-coalescing-operator", 67 | "@babel/plugin-proposal-do-expressions", 68 | "@babel/plugin-proposal-function-bind" 69 | ], 70 | "presets": [ 71 | "@babel/preset-react" 72 | ] 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // I want to use babel-eslint for parsing! 3 | "parser": "babel-eslint", 4 | 5 | "env": { 6 | "browser": true, 7 | "node": false 8 | }, 9 | 10 | // To give you an idea how to override rule options: 11 | "rules": { 12 | "quotes": [2, "single"], 13 | "strict": [2, "never"], 14 | "eol-last": [0], 15 | "no-mixed-requires": [0], 16 | "no-underscore-dangle": [0], 17 | "no-use-before-define": [2, "nofunc"], 18 | 19 | "no-alert": 0, 20 | 21 | // react 22 | "react/display-name": 0, 23 | "react/jsx-boolean-value": 1, 24 | "react/jsx-quotes": 1, 25 | "react/jsx-no-undef": 1, 26 | "react/jsx-sort-props": 0, 27 | "react/jsx-sort-prop-types": 1, 28 | "react/jsx-uses-react": 1, 29 | "react/jsx-uses-vars": 1, 30 | "react/no-did-mount-set-state": 1, 31 | "react/no-did-update-set-state": 1, 32 | "react/no-multi-comp": 0, 33 | "react/no-unknown-property": 1, 34 | "react/prop-types": 0, 35 | "react/react-in-jsx-scope": 1, 36 | "react/self-closing-comp": 1, 37 | "react/wrap-multilines": 1 38 | }, 39 | 40 | "ecmaFeatures": { 41 | "jsx": true 42 | }, 43 | 44 | "plugins": [ 45 | "react" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | *.log 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leefsmp/Re-Flex/7f7ecbc3979a7bbb940edfa4b0f024ef47813915/.npmrc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Philippe Leefsma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![re-flex](./resources/img/re-flex-banner.png) 2 | 3 | # About Re-F|ex 4 | 5 | Re-F|ex is a React flex-based layout component library which I created because none of the components I found out there could satisfy my requirements. 6 | 7 | It intends to address in a simple way the needs of advanced React Web applications that require resizable layouts. 8 | 9 | Here is a basic demo: 10 | 11 | ```js 12 | import ReactDOM from 'react-dom' 13 | import React from 'react' 14 | 15 | import { 16 | ReflexContainer, 17 | ReflexSplitter, 18 | ReflexElement 19 | } from 'react-reflex' 20 | 21 | import 'react-reflex/styles.css' 22 | 23 | ///////////////////////////////////////////////////////// 24 | // Basic vertical re-flex layout with splitter 25 | // Adding a splitter between two ReflexElements 26 | // will allow the user to resize them 27 | // 28 | ///////////////////////////////////////////////////////// 29 | class ReflexDemo extends React.Component { 30 | 31 | render () { 32 | 33 | return ( 34 | 35 | 36 | 37 |
38 | Left Pane (resizeable) 39 |
40 |
41 | 42 | 43 | 44 | 45 |
46 | Right Pane (resizeable) 47 |
48 |
49 | 50 |
51 | ) 52 | } 53 | } 54 | 55 | ReactDOM.render( 56 | , 57 | document.getElementById('reflex-demo')) 58 | ``` 59 | 60 | ## Installation 61 | 62 | ```sh 63 | npm install react-reflex 64 | ``` 65 | 66 | ES6, CommonJS, and UMD builds are available with each distribution. 67 | 68 | ```js 69 | // You will need to import the styles separately 70 | // You probably want to do this just once during the bootstrapping phase of your application. 71 | import 'react-reflex/styles.css' 72 | 73 | // then you can import the components 74 | import { 75 | ReflexContainer, 76 | ReflexSplitter, 77 | ReflexElement 78 | } from 'react-reflex' 79 | ``` 80 | 81 | You can also use the UMD build 82 | ```html 83 | 84 | 85 | ``` 86 | 87 | ## React Support 88 | 89 | React >= 0.13.x 90 | 91 | ## Browser Support 92 | 93 | Re-F|ex is responsive, mobile friendly and has been tested on the following browsers: 94 | 95 | ![IE](https://cloud.githubusercontent.com/assets/398893/3528325/20373e76-078e-11e4-8e3a-1cb86cf506f0.png) | ![Chrome](https://cloud.githubusercontent.com/assets/398893/3528328/23bc7bc4-078e-11e4-8752-ba2809bf5cce.png) | ![Firefox](https://cloud.githubusercontent.com/assets/398893/3528329/26283ab0-078e-11e4-84d4-db2cf1009953.png) | ![Opera](https://cloud.githubusercontent.com/assets/398893/3528330/27ec9fa8-078e-11e4-95cb-709fd11dac16.png) | ![Safari](https://cloud.githubusercontent.com/assets/398893/3528331/29df8618-078e-11e4-8e3e-ed8ac738693f.png) | ![Edge](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png) 96 | --- | --- | --- | --- | --- | --- | 97 | IE 11+ ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 98 | 99 | ## Documentation & Samples 100 | 101 | Re-F|ex is the most powerful resizeable React layout component out there ... Don't just trust me, try it! 102 | 103 | [Click here for code samples and live demos ...](https://leefsmp.github.io/Re-Flex/index.html) 104 | 105 | ![re-flex-demo](https://cdn.rawgit.com/leefsmp/data/f3ec837d/Re-Flex/demo.gif) 106 | 107 | * Supported properties on `ReflexContainer`: 108 | 109 | * `orientation`: Orientation of the layout container. 110 | Type: `oneOf(['horizontal', 'vertical'])`. 111 | Default value: `horizontal`. 112 | 113 | * `maxRecDepth`: Maximum recursion depth to solve initial flex of layout elements based on user provided values. This prevents infinite recursion in case the constraints solver cannot find suitable dimensions on the elements to satisfy initial inputs. 114 | Type: `number`. 115 | Default value: `100`. 116 | 117 | * `windowResizeAware`: When set to true, this flag will update the layout upon a window resize event to attempt to satisfy `minSize`/`maxSize` constraints on each element. If your elements do not have those constraints, this is not needed. 118 | Type: `bool`. 119 | Default value: `false`. 120 | 121 | * `className`: Space separated classnames to apply custom styles on the component. Type: `string`. 122 | Default value: `empty string ''`. 123 | 124 | * `style`: allows passing inline style to the container. 125 | Type: `object`. 126 | Default value: `{}`. 127 | 128 | * Supported properties on `ReflexElement`: 129 | 130 | * `propagateDimensions`: Setting this to `true` will propagate a dimensions `{height, width}` property to the children. See [Size-aware element demo](https://leefsmp.github.io/Re-Flex/index.html#demo7) for more details. 131 | Type: `bool`. 132 | Default value: `false`. 133 | 134 | * `propagateDimensionsRate`: When resizing with `propagateDimensions={true}`, defines the rate at which the dimensions will be updated on the child elements (in times per second). This can help improving performances when using this approach on heavy components by skipping some rerender steps during resizing. 135 | Type: `number`. 136 | Default value: `100`. 137 | 138 | * `resizeHeight`: Allows to control if `height` will be propagated when `propagateDimensions={true}`. 139 | Type: `bool`. 140 | Default value: `true`. 141 | 142 | * `resizeWidth`: Allows to control if `width` will be propagated when `propagateDimensions={true}`. 143 | Type: `bool`. 144 | Default value: `true`. 145 | 146 | * `size`: Allows to control the size in pixel of an element. The main use-case is to allow to perform animations programmatically on an element (shrinking/expanding). See [Controlled elements demo](https://leefsmp.github.io/Re-Flex/index.html#demo6) for more details. 147 | Type: `number`. 148 | Default value: `true`. 149 | 150 | * `minSize`: Creates a constraint on the minimum size in pixel to which the element can be resized to by the user. 151 | Type: `number`. 152 | Default value: `true`. 153 | 154 | * `maxSize`: Creates a constraint on the maximum size in pixel to which the element can be resized to by the user. 155 | Type: `number`. 156 | Default value: `true`. 157 | 158 | * `flex`: Specifiy the initial `flex` of an element. By default all element will get evenly displayed inside a layout, unless some of them have `minSize`, `maxSize` or `size` specified. 159 | Type: `number`. 160 | Default value: `true`. 161 | 162 | * `direction`: Allows to control in which direction(s) the element will shrink/expand when its `size` property is modified. See [Controlled elements demo](https://leefsmp.github.io/Re-Flex/index.html#demo6) for more details. 163 | Type: `-1, 1 or [-1, 1]`. 164 | Default value: `1`. 165 | 166 | * `onStartResize`: Event fired when user initiates layout resizing. 167 | Type: `function({domElement, component})`. 168 | Default value: `undefined`. 169 | 170 | * `onStopResize`: Event fired when user finishes layout resizing. 171 | Type: `function({domElement, component})`. 172 | Default value: `undefined`. 173 | 174 | * `onResize`: Event fired at each resize step when user resizes layout. 175 | Type: `function({domElement, component})`. 176 | Default value: `undefined`. 177 | 178 | * `className`: Space separated classnames to apply custom styles on the component. 179 | Type: `string`. 180 | Default value: `empty string ''`. 181 | 182 | * `style`: allows passing inline style to the container. 183 | Type: `object`. 184 | Default value: `{}`. 185 | 186 | * Supported properties on `ReflexSplitter`: 187 | 188 | * `propagate`: Propagate the drag when reszing a layout across multiple splitters. Layou must have at least 3 elements with therefore 2 splitters for this properties to be relevant. 189 | Type: `bool`. 190 | Default value: `false`. 191 | 192 | * `onStartResize`: Event fired when user initiates layout resizing. 193 | Type: `function({domElement, component})`. 194 | Default value: `undefined`. 195 | 196 | * `onStopResize`: Event fired when user finishes layout resizing. 197 | Type: `function({domElement, component})`. 198 | Default value: `undefined`. 199 | 200 | * `onResize`: Event fired at each resize step when user resizes layout. 201 | Type: `function({domElement, component})`. 202 | Default value: `undefined`. 203 | 204 | * `className`: Space separated classnames to apply custom styles on the component. 205 | Type: `string`. 206 | Default value: `empty string ''`. 207 | 208 | * `style`: allows passing inline style to the container. 209 | Default value: `{}`. 210 | Type: `object`. 211 | 212 | 213 | 214 | ## Development 215 | 216 | * Build the lib: `npm run build` | `npm run build-dev` (dev mode non-minified with source-map) 217 | * Build the demo: `npm run build-demo` | `npm run build-demo-dev` (dev mode non-minified with source-map + webpack watch) 218 | 219 | ## Web Applications using Re-F|ex 220 | 221 | [See here...](./webapps-using-reflex.md) 222 | 223 | (Feel free to add your own by submitting a pull request...) 224 | 225 | ## About the Author 226 | 227 | [https://twitter.com/F3lipek](https://twitter.com/F3lipek) 228 | -------------------------------------------------------------------------------- /dist/commonjs/Polyfills.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | if (!Array.prototype.includes) { 4 | Object.defineProperty(Array.prototype, 'includes', { 5 | value: function value(valueToFind, fromIndex) { 6 | if (this == null) { 7 | throw new TypeError('"this" is null or not defined'); 8 | } 9 | 10 | // 1. Let O be ? ToObject(this value). 11 | var o = Object(this); 12 | 13 | // 2. Let len be ? ToLength(? Get(O, "length")). 14 | var len = o.length >>> 0; 15 | 16 | // 3. If len is 0, return false. 17 | if (len === 0) { 18 | return false; 19 | } 20 | 21 | // 4. Let n be ? ToInteger(fromIndex). 22 | // (If fromIndex is undefined, this step produces the value 0.) 23 | var n = fromIndex | 0; 24 | 25 | // 5. If n ≥ 0, then 26 | // a. Let k be n. 27 | // 6. Else n < 0, 28 | // a. Let k be len + n. 29 | // b. If k < 0, let k be 0. 30 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 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 | if (!Math.sign) { 52 | Math.sign = function (x) { 53 | return (x > 0) - (x < 0) || +x; 54 | }; 55 | } -------------------------------------------------------------------------------- /dist/commonjs/ReflexElement.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | var _typeof = require("@babel/runtime/helpers/typeof"); 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.default = void 0; 9 | var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); 10 | var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); 11 | var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); 12 | var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); 13 | var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 14 | var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 15 | var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); 16 | var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); 17 | var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); 18 | var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); 19 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 20 | var _ReflexHandle = _interopRequireDefault(require("./ReflexHandle")); 21 | var _utilities = require("./utilities"); 22 | var _lodash = _interopRequireDefault(require("lodash.throttle")); 23 | var _reactMeasure = _interopRequireDefault(require("react-measure")); 24 | var _propTypes = _interopRequireDefault(require("prop-types")); 25 | var _react = _interopRequireDefault(require("react")); 26 | function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return exports; }; var exports = {}, Op = Object.prototype, hasOwn = Op.hasOwnProperty, defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }, $Symbol = "function" == typeof Symbol ? Symbol : {}, iteratorSymbol = $Symbol.iterator || "@@iterator", asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function define(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }), obj[key]; } try { define({}, ""); } catch (err) { define = function define(obj, key, value) { return obj[key] = value; }; } function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, generator = Object.create(protoGenerator.prototype), context = new Context(tryLocsList || []); return defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }), generator; } function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } } exports.wrap = wrap; var ContinueSentinel = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var IteratorPrototype = {}; define(IteratorPrototype, iteratorSymbol, function () { return this; }); var getProto = Object.getPrototypeOf, NativeIteratorPrototype = getProto && getProto(getProto(values([]))); NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function (method) { define(prototype, method, function (arg) { return this._invoke(method, arg); }); }); } function AsyncIterator(generator, PromiseImpl) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); if ("throw" !== record.type) { var result = record.arg, value = result.value; return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { invoke("next", value, resolve, reject); }, function (err) { invoke("throw", err, resolve, reject); }) : PromiseImpl.resolve(value).then(function (unwrapped) { result.value = unwrapped, resolve(result); }, function (error) { return invoke("throw", error, resolve, reject); }); } reject(record.arg); } var previousPromise; defineProperty(this, "_invoke", { value: function value(method, arg) { function callInvokeWithMethodAndArg() { return new PromiseImpl(function (resolve, reject) { invoke(method, arg, resolve, reject); }); } return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(innerFn, self, context) { var state = "suspendedStart"; return function (method, arg) { if ("executing" === state) throw new Error("Generator is already running"); if ("completed" === state) { if ("throw" === method) throw arg; return doneResult(); } for (context.method = method, context.arg = arg;;) { var delegate = context.delegate; if (delegate) { var delegateResult = maybeInvokeDelegate(delegate, context); if (delegateResult) { if (delegateResult === ContinueSentinel) continue; return delegateResult; } } if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { if ("suspendedStart" === state) throw state = "completed", context.arg; context.dispatchException(context.arg); } else "return" === context.method && context.abrupt("return", context.arg); state = "executing"; var record = tryCatch(innerFn, self, context); if ("normal" === record.type) { if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; return { value: record.arg, done: context.done }; } "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } }; } function maybeInvokeDelegate(delegate, context) { var methodName = context.method, method = delegate.iterator[methodName]; if (undefined === method) return context.delegate = null, "throw" === methodName && delegate.iterator.return && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method) || "return" !== methodName && (context.method = "throw", context.arg = new TypeError("The iterator does not provide a '" + methodName + "' method")), ContinueSentinel; var record = tryCatch(method, delegate.iterator, context.arg); if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; var info = record.arg; return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); } function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); } function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal", delete record.arg, entry.completion = record; } function Context(tryLocsList) { this.tryEntries = [{ tryLoc: "root" }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); } function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) return iteratorMethod.call(iterable); if ("function" == typeof iterable.next) return iterable; if (!isNaN(iterable.length)) { var i = -1, next = function next() { for (; ++i < iterable.length;) if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; return next.value = undefined, next.done = !0, next; }; return next.next = next; } } return { next: doneResult }; } function doneResult() { return { value: undefined, done: !0 }; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), defineProperty(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { var ctor = "function" == typeof genFun && genFun.constructor; return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); }, exports.mark = function (genFun) { return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; }, exports.awrap = function (arg) { return { __await: arg }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { return this; }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { void 0 === PromiseImpl && (PromiseImpl = Promise); var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { return result.done ? result.value : iter.next(); }); }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { return this; }), define(Gp, "toString", function () { return "[object Generator]"; }), exports.keys = function (val) { var object = Object(val), keys = []; for (var key in object) keys.push(key); return keys.reverse(), function next() { for (; keys.length;) { var key = keys.pop(); if (key in object) return next.value = key, next.done = !1, next; } return next.done = !0, next; }; }, exports.values = values, Context.prototype = { constructor: Context, reset: function reset(skipTempReset) { if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); }, stop: function stop() { this.done = !0; var rootRecord = this.tryEntries[0].completion; if ("throw" === rootRecord.type) throw rootRecord.arg; return this.rval; }, dispatchException: function dispatchException(exception) { if (this.done) throw exception; var context = this; function handle(loc, caught) { return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; } for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i], record = entry.completion; if ("root" === entry.tryLoc) return handle("end"); if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"), hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } else if (hasCatch) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); } else { if (!hasFinally) throw new Error("try statement without catch or finally"); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } } } }, abrupt: function abrupt(type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); var record = finallyEntry ? finallyEntry.completion : {}; return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); }, complete: function complete(record, afterLoc) { if ("throw" === record.type) throw record.arg; return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; }, finish: function finish(finallyLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; } }, catch: function _catch(tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc === tryLoc) { var record = entry.completion; if ("throw" === record.type) { var thrown = record.arg; resetTryEntry(entry); } return thrown; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(iterable, resultName, nextLoc) { return this.delegate = { iterator: values(iterable), resultName: resultName, nextLoc: nextLoc }, "next" === this.method && (this.arg = undefined), ContinueSentinel; } }, exports; } 27 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } 28 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 29 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } 30 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; } 31 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 32 | var toArray = function toArray(obj) { 33 | return obj ? Array.isArray(obj) ? obj : [obj] : []; 34 | }; 35 | var SizeAwareReflexElement = /*#__PURE__*/function (_React$Component) { 36 | (0, _inherits2.default)(SizeAwareReflexElement, _React$Component); 37 | var _super = _createSuper(SizeAwareReflexElement); 38 | function SizeAwareReflexElement(props) { 39 | var _this; 40 | (0, _classCallCheck2.default)(this, SizeAwareReflexElement); 41 | _this = _super.call(this, props); 42 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onResize", function (rect) { 43 | var _this$props = _this.props, 44 | resizeHeight = _this$props.resizeHeight, 45 | resizeWidth = _this$props.resizeWidth; 46 | var _rect$bounds = rect.bounds, 47 | height = _rect$bounds.height, 48 | width = _rect$bounds.width; 49 | _this.setDimensions((0, _objectSpread2.default)({}, resizeHeight && { 50 | height: height 51 | }, resizeWidth && { 52 | width: width 53 | })); 54 | }); 55 | _this.setDimensions = (0, _lodash.default)(function (dimensions) { 56 | _this.setState(dimensions); 57 | }, _this.props.propagateDimensionsRate / 1000); 58 | _this.state = { 59 | height: "100%", 60 | width: "100%" 61 | }; 62 | return _this; 63 | } 64 | (0, _createClass2.default)(SizeAwareReflexElement, [{ 65 | key: "renderChildren", 66 | value: function renderChildren() { 67 | var _this2 = this; 68 | var propagateDimensions = this.props.propagateDimensions; 69 | var validChildren = toArray(this.props.children).filter(function (child) { 70 | return !!child; 71 | }); 72 | return _react.default.Children.map(validChildren, function (child) { 73 | if (_this2.props.withHandle || _ReflexHandle.default.isA(child)) { 74 | return _react.default.cloneElement(child, (0, _objectSpread2.default)({ 75 | dimensions: propagateDimensions && _this2.state 76 | }, child.props, { 77 | index: _this2.props.index - 1, 78 | events: _this2.props.events 79 | })); 80 | } 81 | if (propagateDimensions) { 82 | return _react.default.cloneElement(child, (0, _objectSpread2.default)({}, child.props, { 83 | dimensions: _this2.state 84 | })); 85 | } 86 | return child; 87 | }); 88 | } 89 | }, { 90 | key: "render", 91 | value: function render() { 92 | var _this3 = this; 93 | return /*#__PURE__*/_react.default.createElement(_reactMeasure.default, { 94 | bounds: true, 95 | onResize: this.onResize 96 | }, function (_ref) { 97 | var measureRef = _ref.measureRef; 98 | return /*#__PURE__*/_react.default.createElement("div", { 99 | ref: measureRef, 100 | className: "reflex-size-aware" 101 | }, /*#__PURE__*/_react.default.createElement("div", { 102 | style: _this3.state 103 | }, _this3.renderChildren())); 104 | }); 105 | } 106 | }]); 107 | return SizeAwareReflexElement; 108 | }(_react.default.Component); 109 | var ReflexElement = /*#__PURE__*/function (_React$Component2) { 110 | (0, _inherits2.default)(ReflexElement, _React$Component2); 111 | var _super2 = _createSuper(ReflexElement); 112 | function ReflexElement(props) { 113 | var _this4; 114 | (0, _classCallCheck2.default)(this, ReflexElement); 115 | _this4 = _super2.call(this, props); 116 | _this4.state = { 117 | size: props.size 118 | }; 119 | return _this4; 120 | } 121 | (0, _createClass2.default)(ReflexElement, [{ 122 | key: "componentDidUpdate", 123 | value: function () { 124 | var _componentDidUpdate = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(prevProps, prevState, snapshot) { 125 | var directions, _iterator, _step, direction; 126 | return _regeneratorRuntime().wrap(function _callee$(_context) { 127 | while (1) switch (_context.prev = _context.next) { 128 | case 0: 129 | if (!(prevState.size !== this.state.size)) { 130 | _context.next = 19; 131 | break; 132 | } 133 | directions = toArray(this.props.direction); 134 | _iterator = _createForOfIteratorHelper(directions); 135 | _context.prev = 3; 136 | _iterator.s(); 137 | case 5: 138 | if ((_step = _iterator.n()).done) { 139 | _context.next = 11; 140 | break; 141 | } 142 | direction = _step.value; 143 | _context.next = 9; 144 | return this.props.events.emit('element.size', { 145 | index: this.props.index, 146 | size: this.props.size, 147 | direction: direction 148 | }); 149 | case 9: 150 | _context.next = 5; 151 | break; 152 | case 11: 153 | _context.next = 16; 154 | break; 155 | case 13: 156 | _context.prev = 13; 157 | _context.t0 = _context["catch"](3); 158 | _iterator.e(_context.t0); 159 | case 16: 160 | _context.prev = 16; 161 | _iterator.f(); 162 | return _context.finish(16); 163 | case 19: 164 | case "end": 165 | return _context.stop(); 166 | } 167 | }, _callee, this, [[3, 13, 16, 19]]); 168 | })); 169 | function componentDidUpdate(_x, _x2, _x3) { 170 | return _componentDidUpdate.apply(this, arguments); 171 | } 172 | return componentDidUpdate; 173 | }() 174 | }, { 175 | key: "renderChildren", 176 | value: function renderChildren() { 177 | var _this5 = this; 178 | var validChildren = toArray(this.props.children).filter(function (child) { 179 | return !!child; 180 | }); 181 | return _react.default.Children.map(validChildren, function (child) { 182 | if (_this5.props.withHandle || _ReflexHandle.default.isA(child)) { 183 | return _react.default.cloneElement(child, (0, _objectSpread2.default)({}, child.props, { 184 | index: _this5.props.index - 1, 185 | events: _this5.props.events 186 | })); 187 | } 188 | return child; 189 | }); 190 | } 191 | }, { 192 | key: "render", 193 | value: function render() { 194 | var className = [].concat((0, _toConsumableArray2.default)(this.props.className.split(' ')), [this.props.orientation, 'reflex-element']).join(' ').trim(); 195 | var style = (0, _objectSpread2.default)({}, this.props.style, { 196 | flexGrow: this.props.flex, 197 | flexShrink: 1, 198 | flexBasis: '0%' 199 | }); 200 | return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({}, (0, _utilities.getDataProps)(this.props), { 201 | ref: this.props.innerRef, 202 | className: className, 203 | style: style 204 | }), this.props.propagateDimensions ? /*#__PURE__*/_react.default.createElement(SizeAwareReflexElement, this.props) : this.renderChildren()); 205 | } 206 | }], [{ 207 | key: "getDerivedStateFromProps", 208 | value: function getDerivedStateFromProps(nextProps, prevState) { 209 | if (nextProps.size !== prevState.size) { 210 | return (0, _objectSpread2.default)({}, prevState, { 211 | size: nextProps.size 212 | }); 213 | } 214 | return null; 215 | } 216 | }]); 217 | return ReflexElement; 218 | }(_react.default.Component); 219 | (0, _defineProperty2.default)(ReflexElement, "propTypes", { 220 | propagateDimensions: _propTypes.default.bool, 221 | resizeHeight: _propTypes.default.bool, 222 | resizeWidth: _propTypes.default.bool, 223 | className: _propTypes.default.string, 224 | size: _propTypes.default.number 225 | }); 226 | (0, _defineProperty2.default)(ReflexElement, "defaultProps", { 227 | propagateDimensionsRate: 100, 228 | propagateDimensions: false, 229 | resizeHeight: true, 230 | resizeWidth: true, 231 | direction: [1], 232 | className: '' 233 | }); 234 | var _default = _react.default.forwardRef(function (props, ref) { 235 | return /*#__PURE__*/_react.default.createElement(ReflexElement, (0, _extends2.default)({ 236 | innerRef: ref 237 | }, props)); 238 | }); 239 | exports.default = _default; -------------------------------------------------------------------------------- /dist/commonjs/ReflexEvents.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.default = void 0; 8 | var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 9 | var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 10 | /////////////////////////////////////////////////////////// 11 | // ReflexEvents 12 | // By Philippe Leefsma 13 | // December 2016 14 | // 15 | /////////////////////////////////////////////////////////// 16 | var ReflexEvents = /*#__PURE__*/function () { 17 | function ReflexEvents() { 18 | (0, _classCallCheck2.default)(this, ReflexEvents); 19 | this._events = {}; 20 | } 21 | 22 | ///////////////////////////////////////////////////////// 23 | // Supports multiple events space-separated 24 | // 25 | ///////////////////////////////////////////////////////// 26 | (0, _createClass2.default)(ReflexEvents, [{ 27 | key: "on", 28 | value: function on(events, fct) { 29 | var _this = this; 30 | events.split(' ').forEach(function (event) { 31 | _this._events[event] = _this._events[event] || []; 32 | _this._events[event].push(fct); 33 | }); 34 | return this; 35 | } 36 | 37 | ///////////////////////////////////////////////////////// 38 | // Supports multiple events space-separated 39 | // 40 | ///////////////////////////////////////////////////////// 41 | }, { 42 | key: "off", 43 | value: function off(events, fct) { 44 | var _this2 = this; 45 | if (events == undefined) { 46 | this._events = {}; 47 | return; 48 | } 49 | events.split(' ').forEach(function (event) { 50 | if (event in _this2._events === false) return; 51 | if (fct) { 52 | _this2._events[event].splice(_this2._events[event].indexOf(fct), 1); 53 | } else { 54 | _this2._events[event] = []; 55 | } 56 | }); 57 | return this; 58 | } 59 | }, { 60 | key: "emit", 61 | value: function emit(event /* , args... */) { 62 | if (this._events[event] === undefined) return; 63 | var tmpArray = this._events[event].slice(); 64 | for (var i = 0; i < tmpArray.length; ++i) { 65 | var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1)); 66 | if (result !== undefined) { 67 | return result; 68 | } 69 | } 70 | return undefined; 71 | } 72 | }]); 73 | return ReflexEvents; 74 | }(); 75 | var _default = ReflexEvents; 76 | exports.default = _default; -------------------------------------------------------------------------------- /dist/commonjs/ReflexHandle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.default = void 0; 8 | var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); 9 | var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); 10 | var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 11 | var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 12 | var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); 13 | var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); 14 | var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); 15 | var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); 16 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 17 | var _utilities = require("./utilities"); 18 | var _propTypes = _interopRequireDefault(require("prop-types")); 19 | var _react = _interopRequireDefault(require("react")); 20 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; } 21 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 22 | var ReflexHandle = /*#__PURE__*/function (_React$Component) { 23 | (0, _inherits2.default)(ReflexHandle, _React$Component); 24 | var _super = _createSuper(ReflexHandle); 25 | function ReflexHandle(props) { 26 | var _this; 27 | (0, _classCallCheck2.default)(this, ReflexHandle); 28 | _this = _super.call(this, props); 29 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "ref", _react.default.createRef()); 30 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseMove", function (event) { 31 | if (_this.state.active) { 32 | var domElement = _this.ref.current; 33 | _this.props.events.emit('resize', { 34 | index: _this.props.index, 35 | domElement: domElement, 36 | event: event 37 | }); 38 | if (_this.props.onResize) { 39 | _this.props.onResize({ 40 | component: (0, _assertThisInitialized2.default)(_this), 41 | domElement: domElement 42 | }); 43 | } 44 | event.stopPropagation(); 45 | event.preventDefault(); 46 | } 47 | }); 48 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseDown", function (event) { 49 | _this.setState({ 50 | active: true 51 | }); 52 | if (_this.props.onStartResize) { 53 | // cancels resize from controller 54 | // if needed by returning true 55 | // to onStartResize 56 | if (_this.props.onStartResize({ 57 | domElement: _this.ref.current, 58 | component: (0, _assertThisInitialized2.default)(_this) 59 | })) { 60 | return; 61 | } 62 | } 63 | _this.props.events.emit('startResize', { 64 | index: _this.props.index, 65 | event: event 66 | }); 67 | }); 68 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseUp", function (event) { 69 | if (_this.state.active) { 70 | _this.setState({ 71 | active: false 72 | }); 73 | if (_this.props.onStopResize) { 74 | _this.props.onStopResize({ 75 | domElement: _this.ref.current, 76 | component: (0, _assertThisInitialized2.default)(_this) 77 | }); 78 | } 79 | _this.props.events.emit('stopResize', { 80 | index: _this.props.index, 81 | event: event 82 | }); 83 | } 84 | }); 85 | _this.state = { 86 | active: false 87 | }; 88 | _this.document = props.document; 89 | return _this; 90 | } 91 | (0, _createClass2.default)(ReflexHandle, [{ 92 | key: "componentDidMount", 93 | value: function componentDidMount() { 94 | if (!this.document) { 95 | return; 96 | } 97 | this.document.addEventListener('touchend', this.onMouseUp); 98 | this.document.addEventListener('mouseup', this.onMouseUp); 99 | this.document.addEventListener('mousemove', this.onMouseMove, { 100 | passive: false 101 | }); 102 | this.document.addEventListener('touchmove', this.onMouseMove, { 103 | passive: false 104 | }); 105 | } 106 | }, { 107 | key: "componentWillUnmount", 108 | value: function componentWillUnmount() { 109 | if (!this.document) { 110 | return; 111 | } 112 | this.document.removeEventListener('mouseup', this.onMouseUp); 113 | this.document.removeEventListener('touchend', this.onMouseUp); 114 | this.document.removeEventListener('mousemove', this.onMouseMove); 115 | this.document.removeEventListener('touchmove', this.onMouseMove); 116 | if (this.state.active) { 117 | this.props.events.emit('stopResize', { 118 | index: this.props.index, 119 | event: null 120 | }); 121 | } 122 | } 123 | }, { 124 | key: "render", 125 | value: function render() { 126 | var className = [].concat((0, _toConsumableArray2.default)(this.props.className.split(' ')), [this.state.active ? 'active' : '', 'reflex-handle']).join(' ').trim(); 127 | return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({}, (0, _utilities.getDataProps)(this.props), { 128 | onTouchStart: this.onMouseDown, 129 | onMouseDown: this.onMouseDown, 130 | style: this.props.style, 131 | className: className, 132 | id: this.props.id, 133 | ref: this.ref 134 | }), this.props.children); 135 | } 136 | }], [{ 137 | key: "isA", 138 | value: function isA(element) { 139 | if (!element) { 140 | return false; 141 | } 142 | //https://github.com/leefsmp/Re-Flex/issues/49 143 | return process.env.NODE_ENV === 'development' ? element.type === /*#__PURE__*/_react.default.createElement(ReflexHandle, null).type : element.type === ReflexHandle; 144 | } 145 | }]); 146 | return ReflexHandle; 147 | }(_react.default.Component); 148 | exports.default = ReflexHandle; 149 | (0, _defineProperty2.default)(ReflexHandle, "propTypes", { 150 | children: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.node), _propTypes.default.node]), 151 | onStartResize: _propTypes.default.func, 152 | onStopResize: _propTypes.default.func, 153 | className: _propTypes.default.string, 154 | propagate: _propTypes.default.bool, 155 | onResize: _propTypes.default.func, 156 | style: _propTypes.default.object 157 | }); 158 | (0, _defineProperty2.default)(ReflexHandle, "defaultProps", { 159 | document: typeof document === 'undefined' ? null : document, 160 | onStartResize: null, 161 | onStopResize: null, 162 | propagate: false, 163 | onResize: null, 164 | className: '', 165 | style: {} 166 | }); -------------------------------------------------------------------------------- /dist/commonjs/ReflexSplitter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.default = void 0; 8 | var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); 9 | var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); 10 | var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 11 | var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 12 | var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); 13 | var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); 14 | var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); 15 | var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); 16 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 17 | var _utilities = require("./utilities"); 18 | var _propTypes = _interopRequireDefault(require("prop-types")); 19 | var _react = _interopRequireDefault(require("react")); 20 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; } 21 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 22 | var ReflexSplitter = /*#__PURE__*/function (_React$Component) { 23 | (0, _inherits2.default)(ReflexSplitter, _React$Component); 24 | var _super = _createSuper(ReflexSplitter); 25 | function ReflexSplitter(props) { 26 | var _this; 27 | (0, _classCallCheck2.default)(this, ReflexSplitter); 28 | _this = _super.call(this, props); 29 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "ref", _react.default.createRef()); 30 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseMove", function (event) { 31 | if (_this.state.active) { 32 | var domElement = _this.ref.current; 33 | _this.props.events.emit('resize', { 34 | index: _this.props.index, 35 | domElement: domElement, 36 | event: event 37 | }); 38 | if (_this.props.onResize) { 39 | _this.props.onResize({ 40 | component: (0, _assertThisInitialized2.default)(_this), 41 | domElement: domElement 42 | }); 43 | } 44 | event.stopPropagation(); 45 | event.preventDefault(); 46 | } 47 | }); 48 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseDown", function (event) { 49 | _this.setState({ 50 | active: true 51 | }); 52 | if (_this.props.onStartResize) { 53 | // cancels resize from controller 54 | // if needed by returning true 55 | // to onStartResize 56 | if (_this.props.onStartResize({ 57 | domElement: _this.ref.current, 58 | component: (0, _assertThisInitialized2.default)(_this) 59 | })) { 60 | return; 61 | } 62 | } 63 | _this.props.events.emit('startResize', { 64 | index: _this.props.index, 65 | event: event 66 | }); 67 | }); 68 | (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onMouseUp", function (event) { 69 | if (_this.state.active) { 70 | _this.setState({ 71 | active: false 72 | }); 73 | if (_this.props.onStopResize) { 74 | _this.props.onStopResize({ 75 | domElement: _this.ref.current, 76 | component: (0, _assertThisInitialized2.default)(_this) 77 | }); 78 | } 79 | _this.props.events.emit('stopResize', { 80 | index: _this.props.index, 81 | event: event 82 | }); 83 | } 84 | }); 85 | _this.state = { 86 | active: false 87 | }; 88 | _this.document = props.document; 89 | return _this; 90 | } 91 | (0, _createClass2.default)(ReflexSplitter, [{ 92 | key: "componentDidMount", 93 | value: function componentDidMount() { 94 | if (!this.document) { 95 | return; 96 | } 97 | this.document.addEventListener('touchend', this.onMouseUp); 98 | this.document.addEventListener('mouseup', this.onMouseUp); 99 | this.document.addEventListener('mousemove', this.onMouseMove, { 100 | passive: false 101 | }); 102 | this.document.addEventListener('touchmove', this.onMouseMove, { 103 | passive: false 104 | }); 105 | } 106 | }, { 107 | key: "componentWillUnmount", 108 | value: function componentWillUnmount() { 109 | if (!this.document) { 110 | return; 111 | } 112 | this.document.removeEventListener('mouseup', this.onMouseUp); 113 | this.document.removeEventListener('touchend', this.onMouseUp); 114 | this.document.removeEventListener('mousemove', this.onMouseMove); 115 | this.document.removeEventListener('touchmove', this.onMouseMove); 116 | if (this.state.active) { 117 | this.props.events.emit('stopResize', { 118 | index: this.props.index, 119 | event: null 120 | }); 121 | } 122 | } 123 | }, { 124 | key: "render", 125 | value: function render() { 126 | var className = [_utilities.Browser.isMobile() ? 'reflex-thin' : ''].concat((0, _toConsumableArray2.default)(this.props.className.split(' ')), [this.state.active ? 'active' : '', 'reflex-splitter']).join(' ').trim(); 127 | return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({}, (0, _utilities.getDataProps)(this.props), { 128 | onTouchStart: this.onMouseDown, 129 | onMouseDown: this.onMouseDown, 130 | style: this.props.style, 131 | className: className, 132 | id: this.props.id, 133 | ref: this.ref 134 | }), this.props.children); 135 | } 136 | }], [{ 137 | key: "isA", 138 | value: 139 | ///////////////////////////////////////////////////////// 140 | // Determines if element is a splitter 141 | // or wraps a splitter 142 | // 143 | ///////////////////////////////////////////////////////// 144 | function isA(element) { 145 | if (!element) { 146 | return false; 147 | } 148 | //https://github.com/leefsmp/Re-Flex/issues/49 149 | return element.type === /*#__PURE__*/_react.default.createElement(ReflexSplitter, null).type; 150 | } 151 | }]); 152 | return ReflexSplitter; 153 | }(_react.default.Component); 154 | exports.default = ReflexSplitter; 155 | (0, _defineProperty2.default)(ReflexSplitter, "propTypes", { 156 | children: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.node), _propTypes.default.node]), 157 | onStartResize: _propTypes.default.func, 158 | onStopResize: _propTypes.default.func, 159 | className: _propTypes.default.string, 160 | propagate: _propTypes.default.bool, 161 | onResize: _propTypes.default.func, 162 | style: _propTypes.default.object 163 | }); 164 | (0, _defineProperty2.default)(ReflexSplitter, "defaultProps", { 165 | document: typeof document !== 'undefined' ? document : null, 166 | onStartResize: null, 167 | onStopResize: null, 168 | propagate: false, 169 | onResize: null, 170 | className: '', 171 | style: {} 172 | }); -------------------------------------------------------------------------------- /dist/commonjs/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | Object.defineProperty(exports, "ReflexContainer", { 8 | enumerable: true, 9 | get: function get() { 10 | return _ReflexContainer.default; 11 | } 12 | }); 13 | Object.defineProperty(exports, "ReflexElement", { 14 | enumerable: true, 15 | get: function get() { 16 | return _ReflexElement.default; 17 | } 18 | }); 19 | Object.defineProperty(exports, "ReflexHandle", { 20 | enumerable: true, 21 | get: function get() { 22 | return _ReflexHandle.default; 23 | } 24 | }); 25 | Object.defineProperty(exports, "ReflexSplitter", { 26 | enumerable: true, 27 | get: function get() { 28 | return _ReflexSplitter.default; 29 | } 30 | }); 31 | var _ReflexContainer = _interopRequireDefault(require("./ReflexContainer")); 32 | var _ReflexSplitter = _interopRequireDefault(require("./ReflexSplitter")); 33 | var _ReflexElement = _interopRequireDefault(require("./ReflexElement")); 34 | var _ReflexHandle = _interopRequireDefault(require("./ReflexHandle")); -------------------------------------------------------------------------------- /dist/commonjs/utilities.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.getDataProps = exports.Browser = void 0; 8 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 9 | var _objectSpread3 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); 10 | var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 11 | var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 12 | ///////////////////////////////////////////////////////// 13 | // Browser Utils 14 | // 15 | ///////////////////////////////////////////////////////// 16 | var Browser = /*#__PURE__*/function () { 17 | function Browser() { 18 | (0, _classCallCheck2.default)(this, Browser); 19 | } 20 | (0, _createClass2.default)(Browser, null, [{ 21 | key: "isBrowser", 22 | value: 23 | // Check if not running on server 24 | function isBrowser() { 25 | return typeof window !== 'undefined'; 26 | } 27 | 28 | // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera) 29 | }, { 30 | key: "isOpera", 31 | value: function isOpera() { 32 | return Browser.isBrowser() && Browser.getUserAgent().match(/Opera Mini/i); 33 | } 34 | }, { 35 | key: "isFirefox", 36 | value: 37 | // Firefox 1.0+ 38 | function isFirefox() { 39 | return Browser.isBrowser() && typeof InstallTrigger !== 'undefined'; 40 | } 41 | 42 | // Safari 3.0+ 43 | }, { 44 | key: "isSafari", 45 | value: function isSafari() { 46 | if (!Browser.isBrowser()) { 47 | return false; 48 | } 49 | return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 50 | } 51 | 52 | // Internet Explorer 6-11 53 | }, { 54 | key: "isIE", 55 | value: function isIE() { 56 | /*@cc_on!@*/ 57 | return Browser.isBrowser() && !!document.documentMode; 58 | } 59 | 60 | // Edge 20+ 61 | }, { 62 | key: "isEdge", 63 | value: function isEdge() { 64 | return Browser.isBrowser() && !Browser.isIE() && !!window.StyleMedia; 65 | } 66 | 67 | // Chrome 1+ 68 | }, { 69 | key: "isChrome", 70 | value: function isChrome() { 71 | return Browser.isBrowser() && !!window.chrome && !!window.chrome.webstore; 72 | } 73 | 74 | // Blink engine detection 75 | }, { 76 | key: "isBlink", 77 | value: function isBlink() { 78 | return Browser.isBrowser() && (Browser.isChrome() || Browser.isOpera()) && !!window.CSS; 79 | } 80 | }, { 81 | key: "getUserAgent", 82 | value: function getUserAgent() { 83 | return typeof navigator === 'undefined' ? '' : navigator.userAgent; 84 | } 85 | }, { 86 | key: "isAndroid", 87 | value: function isAndroid() { 88 | return Browser.isBrowser() && Browser.getUserAgent().match(/Android/i); 89 | } 90 | }, { 91 | key: "isBlackBerry", 92 | value: function isBlackBerry() { 93 | return Browser.isBrowser() && Browser.getUserAgent().match(/BlackBerry/i); 94 | } 95 | }, { 96 | key: "isIOS", 97 | value: function isIOS() { 98 | return Browser.isBrowser() && Browser.getUserAgent().match(/iPhone|iPad|iPod/i); 99 | } 100 | }, { 101 | key: "isWindows", 102 | value: function isWindows() { 103 | return Browser.isBrowser() && Browser.isWindowsDesktop() || Browser.isWindowsMobile(); 104 | } 105 | }, { 106 | key: "isWindowsMobile", 107 | value: function isWindowsMobile() { 108 | return Browser.isBrowser() && Browser.getUserAgent().match(/IEMobile/i); 109 | } 110 | }, { 111 | key: "isWindowsDesktop", 112 | value: function isWindowsDesktop() { 113 | return Browser.isBrowser() && Browser.getUserAgent().match(/WPDesktop/i); 114 | } 115 | }, { 116 | key: "isMobile", 117 | value: function isMobile() { 118 | return Browser.isBrowser() && (Browser.isWindowsMobile() || Browser.isBlackBerry() || Browser.isAndroid() || Browser.isIOS()); 119 | } 120 | }]); 121 | return Browser; 122 | }(); ///////////////////////////////////////////////////////// 123 | // Returns only the props that start with "data-" 124 | // 125 | ///////////////////////////////////////////////////////// 126 | exports.Browser = Browser; 127 | var getDataProps = function getDataProps(props) { 128 | return Object.keys(props).reduce(function (prev, key) { 129 | if (key.substr(0, 5) === 'data-') { 130 | return (0, _objectSpread3.default)({}, prev, (0, _defineProperty2.default)({}, key, props[key])); 131 | } 132 | return prev; 133 | }, {}); 134 | }; 135 | exports.getDataProps = getDataProps; -------------------------------------------------------------------------------- /dist/es/Polyfills.js: -------------------------------------------------------------------------------- 1 | if (!Array.prototype.includes) { 2 | Object.defineProperty(Array.prototype, 'includes', { 3 | value: function (valueToFind, fromIndex) { 4 | if (this == null) { 5 | throw new TypeError('"this" is null or not defined'); 6 | } 7 | 8 | // 1. Let O be ? ToObject(this value). 9 | var o = Object(this); 10 | 11 | // 2. Let len be ? ToLength(? Get(O, "length")). 12 | var len = o.length >>> 0; 13 | 14 | // 3. If len is 0, return false. 15 | if (len === 0) { 16 | return false; 17 | } 18 | 19 | // 4. Let n be ? ToInteger(fromIndex). 20 | // (If fromIndex is undefined, this step produces the value 0.) 21 | var n = fromIndex | 0; 22 | 23 | // 5. If n ≥ 0, then 24 | // a. Let k be n. 25 | // 6. Else n < 0, 26 | // a. Let k be len + n. 27 | // b. If k < 0, let k be 0. 28 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 29 | function sameValueZero(x, y) { 30 | return x === y || typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y); 31 | } 32 | 33 | // 7. Repeat, while k < len 34 | while (k < len) { 35 | // a. Let elementK be the result of ? Get(O, ! ToString(k)). 36 | // b. If SameValueZero(valueToFind, elementK) is true, return true. 37 | if (sameValueZero(o[k], valueToFind)) { 38 | return true; 39 | } 40 | // c. Increase k by 1. 41 | k++; 42 | } 43 | 44 | // 8. Return false 45 | return false; 46 | } 47 | }); 48 | } 49 | if (!Math.sign) { 50 | Math.sign = function (x) { 51 | return (x > 0) - (x < 0) || +x; 52 | }; 53 | } -------------------------------------------------------------------------------- /dist/es/ReflexContainer.js: -------------------------------------------------------------------------------- 1 | import _extends from "@babel/runtime/helpers/extends"; 2 | import _objectSpread from "@babel/runtime/helpers/objectSpread"; 3 | import _defineProperty from "@babel/runtime/helpers/defineProperty"; 4 | /////////////////////////////////////////////////////////// 5 | // ReflexContainer 6 | // By Philippe Leefsma 7 | // December 2016 8 | // 9 | /////////////////////////////////////////////////////////// 10 | import ReflexSplitter from './ReflexSplitter'; 11 | import ReflexEvents from './ReflexEvents'; 12 | import { getDataProps } from './utilities'; 13 | import PropTypes from 'prop-types'; 14 | import React from 'react'; 15 | import './Polyfills'; 16 | export default class ReflexContainer extends React.Component { 17 | ///////////////////////////////////////////////////////// 18 | // orientation: Orientation of the layout container 19 | // valid values are ['horizontal', 'vertical'] 20 | // maxRecDepth: Maximun recursion depth to solve initial flex 21 | // of layout elements based on user provided values 22 | // className: Space separated classnames to apply custom styles 23 | // to the layout container 24 | // style: allows passing inline style to the container 25 | ///////////////////////////////////////////////////////// 26 | 27 | constructor(props) { 28 | super(props); 29 | _defineProperty(this, "onWindowResize", () => { 30 | this.setState({ 31 | flexData: this.computeFlexData() 32 | }); 33 | }); 34 | _defineProperty(this, "onStartResize", data => { 35 | const pos = data.event.changedTouches ? data.event.changedTouches[0] : data.event; 36 | switch (this.props.orientation) { 37 | case 'horizontal': 38 | document.body.classList.add('reflex-row-resize'); 39 | this.previousPos = pos.clientY; 40 | break; 41 | case 'vertical': 42 | default: 43 | document.body.classList.add('reflex-col-resize'); 44 | this.previousPos = pos.clientX; 45 | break; 46 | } 47 | this.elements = [this.children[data.index - 1], this.children[data.index + 1]]; 48 | this.emitElementsEvent(this.elements, 'onStartResize'); 49 | }); 50 | _defineProperty(this, "onResize", data => { 51 | const pos = data.event.changedTouches ? data.event.changedTouches[0] : data.event; 52 | const offset = this.getOffset(pos, data.domElement); 53 | switch (this.props.orientation) { 54 | case 'horizontal': 55 | this.previousPos = pos.clientY; 56 | break; 57 | case 'vertical': 58 | default: 59 | this.previousPos = pos.clientX; 60 | break; 61 | } 62 | if (offset) { 63 | const availableOffset = this.computeAvailableOffset(data.index, offset); 64 | if (availableOffset) { 65 | this.elements = this.dispatchOffset(data.index, availableOffset); 66 | this.adjustFlex(this.elements); 67 | this.setState({ 68 | resizing: true 69 | }, () => { 70 | this.emitElementsEvent(this.elements, 'onResize'); 71 | }); 72 | } 73 | } 74 | }); 75 | _defineProperty(this, "onStopResize", data => { 76 | document.body.classList.remove('reflex-row-resize'); 77 | document.body.classList.remove('reflex-col-resize'); 78 | const resizedRefs = this.elements ? this.elements.map(element => { 79 | return element.ref; 80 | }) : []; 81 | const elements = this.children.filter(child => { 82 | return !ReflexSplitter.isA(child) && resizedRefs.includes(child.ref); 83 | }); 84 | this.emitElementsEvent(elements, 'onStopResize'); 85 | this.setState({ 86 | resizing: false 87 | }); 88 | }); 89 | _defineProperty(this, "onElementSize", data => { 90 | return new Promise(resolve => { 91 | try { 92 | const idx = data.index; 93 | const size = this.getSize(this.children[idx]); 94 | const offset = data.size - size; 95 | const dir = data.direction; 96 | const splitterIdx = idx + dir; 97 | const availableOffset = this.computeAvailableOffset(splitterIdx, dir * offset); 98 | this.elements = null; 99 | if (availableOffset) { 100 | this.elements = this.dispatchOffset(splitterIdx, availableOffset); 101 | this.adjustFlex(this.elements); 102 | } 103 | this.setState(this.state, () => { 104 | this.emitElementsEvent(this.elements, 'onResize'); 105 | resolve(); 106 | }); 107 | } catch (ex) { 108 | // TODO handle exception ... 109 | console.log(ex); 110 | } 111 | }); 112 | }); 113 | this.events = new ReflexEvents(); 114 | this.children = []; 115 | this.state = { 116 | flexData: [] 117 | }; 118 | this.ref = React.createRef(); 119 | } 120 | componentDidMount() { 121 | const flexData = this.computeFlexData(); 122 | const { 123 | windowResizeAware 124 | } = this.props; 125 | if (windowResizeAware) { 126 | window.addEventListener('resize', this.onWindowResize); 127 | } 128 | this.setState({ 129 | windowResizeAware, 130 | flexData 131 | }); 132 | this.events.on('element.size', this.onElementSize); 133 | this.events.on('startResize', this.onStartResize); 134 | this.events.on('stopResize', this.onStopResize); 135 | this.events.on('resize', this.onResize); 136 | } 137 | componentWillUnmount() { 138 | this.events.off(); 139 | window.removeEventListener('resize', this.onWindowResize); 140 | } 141 | getValidChildren(props = this.props) { 142 | return this.toArray(props.children).filter(child => { 143 | return !!child; 144 | }); 145 | } 146 | componentDidUpdate(prevProps, prevState) { 147 | const children = this.getValidChildren(this.props); 148 | if (children.length !== this.state.flexData.length || prevProps.orientation !== this.props.orientation || this.flexHasChanged(prevProps)) { 149 | const flexData = this.computeFlexData(children, this.props); 150 | this.setState({ 151 | flexData 152 | }); 153 | } 154 | if (this.props.windowResizeAware !== this.state.windowResizeAware) { 155 | !this.props.windowResizeAware ? window.removeEventListener('resize', this.onWindowResize) : window.addEventListener('resize', this.onWindowResize); 156 | this.setState({ 157 | windowResizeAware: this.props.windowResizeAware 158 | }); 159 | } 160 | } 161 | 162 | // UNSAFE_componentWillReceiveProps(props) { 163 | 164 | // const children = this.getValidChildren(props) 165 | 166 | // if (children.length !== this.state.flexData.length || 167 | // props.orientation !== this.props.orientation || 168 | // this.flexHasChanged(props)) 169 | // { 170 | // const flexData = this.computeFlexData( 171 | // children, props) 172 | 173 | // this.setState({ 174 | // flexData 175 | // }); 176 | // } 177 | 178 | // if (props.windowResizeAware !== this.state.windowResizeAware) { 179 | // !props.windowResizeAware 180 | // ? window.removeEventListener('resize', this.onWindowResize) 181 | // : window.addEventListener('resize', this.onWindowResize) 182 | // this.setState({ 183 | // windowResizeAware: props.windowResizeAware 184 | // }) 185 | // } 186 | // } 187 | 188 | ///////////////////////////////////////////////////////// 189 | // attempts to preserve current flex on window resize 190 | // 191 | ///////////////////////////////////////////////////////// 192 | 193 | ///////////////////////////////////////////////////////// 194 | // Check if flex has changed: this allows updating the 195 | // component when different flex is passed as property 196 | // to one or several children 197 | // 198 | ///////////////////////////////////////////////////////// 199 | flexHasChanged(prevProps) { 200 | const prevChildrenFlex = this.getValidChildren(prevProps).map(child => { 201 | return child.props.flex || 0; 202 | }); 203 | const childrenFlex = this.getValidChildren().map(child => { 204 | return child.props.flex || 0; 205 | }); 206 | return !childrenFlex.every((flex, idx) => { 207 | return flex === prevChildrenFlex[idx]; 208 | }); 209 | } 210 | 211 | ///////////////////////////////////////////////////////// 212 | // Returns size of a ReflexElement 213 | // 214 | ///////////////////////////////////////////////////////// 215 | getSize(element) { 216 | var _element$ref, _domElement$offsetHei, _domElement$offsetWid; 217 | const domElement = element === null || element === void 0 ? void 0 : (_element$ref = element.ref) === null || _element$ref === void 0 ? void 0 : _element$ref.current; 218 | switch (this.props.orientation) { 219 | case 'horizontal': 220 | return (_domElement$offsetHei = domElement === null || domElement === void 0 ? void 0 : domElement.offsetHeight) !== null && _domElement$offsetHei !== void 0 ? _domElement$offsetHei : 0; 221 | case 'vertical': 222 | default: 223 | return (_domElement$offsetWid = domElement === null || domElement === void 0 ? void 0 : domElement.offsetWidth) !== null && _domElement$offsetWid !== void 0 ? _domElement$offsetWid : 0; 224 | } 225 | } 226 | 227 | ///////////////////////////////////////////////////////// 228 | // Computes offset from pointer position 229 | // 230 | ///////////////////////////////////////////////////////// 231 | getOffset(pos, domElement) { 232 | const { 233 | top, 234 | bottom, 235 | left, 236 | right 237 | } = domElement.getBoundingClientRect(); 238 | switch (this.props.orientation) { 239 | case 'horizontal': 240 | { 241 | const offset = pos.clientY - this.previousPos; 242 | if (offset > 0) { 243 | if (pos.clientY >= top) { 244 | return offset; 245 | } 246 | } else { 247 | if (pos.clientY <= bottom) { 248 | return offset; 249 | } 250 | } 251 | break; 252 | } 253 | case 'vertical': 254 | default: 255 | { 256 | const offset = pos.clientX - this.previousPos; 257 | if (offset > 0) { 258 | if (pos.clientX > left) { 259 | return offset; 260 | } 261 | } else { 262 | if (pos.clientX < right) { 263 | return offset; 264 | } 265 | } 266 | } 267 | break; 268 | } 269 | return 0; 270 | } 271 | 272 | ///////////////////////////////////////////////////////// 273 | // Handles startResize event 274 | // 275 | ///////////////////////////////////////////////////////// 276 | 277 | ///////////////////////////////////////////////////////// 278 | // Adjusts flex after a dispatch to make sure 279 | // total flex of modified elements remains the same 280 | // 281 | ///////////////////////////////////////////////////////// 282 | adjustFlex(elements) { 283 | const diffFlex = elements.reduce((sum, element) => { 284 | const idx = element.props.index; 285 | const previousFlex = element.props.flex; 286 | const nextFlex = this.state.flexData[idx].flex; 287 | return sum + (previousFlex - nextFlex) / elements.length; 288 | }, 0); 289 | elements.forEach(element => { 290 | this.state.flexData[element.props.index].flex += diffFlex; 291 | }); 292 | } 293 | 294 | ///////////////////////////////////////////////////////// 295 | // Returns available offset for a given raw offset value 296 | // This checks how much the panes can be stretched and 297 | // shrink, then returns the min 298 | // 299 | ///////////////////////////////////////////////////////// 300 | computeAvailableOffset(idx, offset) { 301 | const stretch = this.computeAvailableStretch(idx, offset); 302 | const shrink = this.computeAvailableShrink(idx, offset); 303 | const availableOffset = Math.min(stretch, shrink) * Math.sign(offset); 304 | return availableOffset; 305 | } 306 | 307 | ///////////////////////////////////////////////////////// 308 | // Returns true if the next splitter than the one at idx 309 | // can propagate the drag. This can happen if that 310 | // next element is actually a splitter and it has 311 | // propagate=true property set 312 | // 313 | ///////////////////////////////////////////////////////// 314 | checkPropagate(idx, direction) { 315 | if (direction > 0) { 316 | if (idx < this.children.length - 2) { 317 | const child = this.children[idx + 2]; 318 | const typeCheck = ReflexSplitter.isA(child); 319 | return typeCheck && child.props.propagate; 320 | } 321 | } else { 322 | if (idx > 2) { 323 | const child = this.children[idx - 2]; 324 | const typeCheck = ReflexSplitter.isA(child); 325 | return typeCheck && child.props.propagate; 326 | } 327 | } 328 | return false; 329 | } 330 | 331 | ///////////////////////////////////////////////////////// 332 | // Recursively computes available stretch at splitter 333 | // idx for given raw offset 334 | // 335 | ///////////////////////////////////////////////////////// 336 | computeAvailableStretch(idx, offset) { 337 | var _child$props$maxSize; 338 | const childIdx = offset < 0 ? idx + 1 : idx - 1; 339 | const child = this.children[childIdx]; 340 | const size = this.getSize(child); 341 | const maxSize = (_child$props$maxSize = child === null || child === void 0 ? void 0 : child.props.maxSize) !== null && _child$props$maxSize !== void 0 ? _child$props$maxSize : 0; 342 | const availableStretch = maxSize - size; 343 | if (availableStretch < Math.abs(offset)) { 344 | if (this.checkPropagate(idx, -1 * offset)) { 345 | const nextOffset = Math.sign(offset) * (Math.abs(offset) - availableStretch); 346 | return availableStretch + this.computeAvailableStretch(offset < 0 ? idx + 2 : idx - 2, nextOffset); 347 | } 348 | } 349 | return Math.min(availableStretch, Math.abs(offset)); 350 | } 351 | 352 | ///////////////////////////////////////////////////////// 353 | // Recursively computes available shrink at splitter 354 | // idx for given raw offset 355 | // 356 | ///////////////////////////////////////////////////////// 357 | computeAvailableShrink(idx, offset) { 358 | var _child$props$minSize; 359 | const childIdx = offset > 0 ? idx + 1 : idx - 1; 360 | const child = this.children[childIdx]; 361 | const size = this.getSize(child); 362 | const minSize = Math.max((_child$props$minSize = child === null || child === void 0 ? void 0 : child.props.minSize) !== null && _child$props$minSize !== void 0 ? _child$props$minSize : 0, 0); 363 | const availableShrink = size - minSize; 364 | if (availableShrink < Math.abs(offset)) { 365 | if (this.checkPropagate(idx, offset)) { 366 | const nextOffset = Math.sign(offset) * (Math.abs(offset) - availableShrink); 367 | return availableShrink + this.computeAvailableShrink(offset > 0 ? idx + 2 : idx - 2, nextOffset); 368 | } 369 | } 370 | return Math.min(availableShrink, Math.abs(offset)); 371 | } 372 | 373 | ///////////////////////////////////////////////////////// 374 | // Returns flex value for unit pixel 375 | // 376 | ///////////////////////////////////////////////////////// 377 | computePixelFlex(orientation = this.props.orientation) { 378 | if (!this.ref.current) { 379 | console.warn('Unable to locate ReflexContainer dom node'); 380 | return 0.0; 381 | } 382 | switch (orientation) { 383 | case 'horizontal': 384 | if (this.ref.current.offsetHeight === 0.0) { 385 | console.warn('Found ReflexContainer with height=0, ' + 'this will cause invalid behavior...'); 386 | console.warn(this.ref.current); 387 | return 0.0; 388 | } 389 | return 1.0 / this.ref.current.offsetHeight; 390 | case 'vertical': 391 | default: 392 | if (this.ref.current.offsetWidth === 0.0) { 393 | console.warn('Found ReflexContainer with width=0, ' + 'this will cause invalid behavior...'); 394 | console.warn(this.ref.current); 395 | return 0.0; 396 | } 397 | return 1.0 / this.ref.current.offsetWidth; 398 | } 399 | } 400 | 401 | ///////////////////////////////////////////////////////// 402 | // Adds offset to a given ReflexElement 403 | // 404 | ///////////////////////////////////////////////////////// 405 | addOffset(element, offset) { 406 | const size = this.getSize(element); 407 | const idx = element.props.index; 408 | const newSize = Math.max(size + offset, 0); 409 | const currentFlex = this.state.flexData[idx].flex; 410 | const newFlex = currentFlex > 0 ? currentFlex * newSize / size : this.computePixelFlex() * newSize; 411 | this.state.flexData[idx].flex = !isFinite(newFlex) || isNaN(newFlex) ? 0 : newFlex; 412 | } 413 | 414 | ///////////////////////////////////////////////////////// 415 | // Recursively dispatches stretch offset across 416 | // children elements starting at splitter idx 417 | // 418 | ///////////////////////////////////////////////////////// 419 | dispatchStretch(idx, offset) { 420 | const childIdx = offset < 0 ? idx + 1 : idx - 1; 421 | if (childIdx < 0 || childIdx > this.children.length - 1) { 422 | return []; 423 | } 424 | const child = this.children[childIdx]; 425 | const size = this.getSize(child); 426 | const newSize = Math.min(child.props.maxSize, size + Math.abs(offset)); 427 | const dispatchedStretch = newSize - size; 428 | this.addOffset(child, dispatchedStretch); 429 | if (dispatchedStretch < Math.abs(offset)) { 430 | const nextIdx = idx - Math.sign(offset) * 2; 431 | const nextOffset = Math.sign(offset) * (Math.abs(offset) - dispatchedStretch); 432 | return [child, ...this.dispatchStretch(nextIdx, nextOffset)]; 433 | } 434 | return [child]; 435 | } 436 | 437 | ///////////////////////////////////////////////////////// 438 | // Recursively dispatches shrink offset across 439 | // children elements starting at splitter idx 440 | // 441 | ///////////////////////////////////////////////////////// 442 | dispatchShrink(idx, offset) { 443 | const childIdx = offset > 0 ? idx + 1 : idx - 1; 444 | if (childIdx < 0 || childIdx > this.children.length - 1) { 445 | return []; 446 | } 447 | const child = this.children[childIdx]; 448 | const size = this.getSize(child); 449 | const newSize = Math.max(child.props.minSize, size - Math.abs(offset)); 450 | const dispatchedShrink = newSize - size; 451 | this.addOffset(child, dispatchedShrink); 452 | if (Math.abs(dispatchedShrink) < Math.abs(offset)) { 453 | const nextIdx = idx + Math.sign(offset) * 2; 454 | const nextOffset = Math.sign(offset) * (Math.abs(offset) + dispatchedShrink); 455 | return [child, ...this.dispatchShrink(nextIdx, nextOffset)]; 456 | } 457 | return [child]; 458 | } 459 | 460 | ///////////////////////////////////////////////////////// 461 | // Dispatch offset at splitter idx 462 | // 463 | ///////////////////////////////////////////////////////// 464 | dispatchOffset(idx, offset) { 465 | return [...this.dispatchStretch(idx, offset), ...this.dispatchShrink(idx, offset)]; 466 | } 467 | 468 | ///////////////////////////////////////////////////////// 469 | // Emits given if event for each given element 470 | // if present in the component props 471 | // 472 | ///////////////////////////////////////////////////////// 473 | emitElementsEvent(elements, event) { 474 | this.toArray(elements).forEach(component => { 475 | if (component.props[event]) { 476 | component.props[event]({ 477 | domElement: component.ref.current, 478 | component 479 | }); 480 | } 481 | }); 482 | } 483 | 484 | ///////////////////////////////////////////////////////// 485 | // Computes initial flex data based on provided flex 486 | // properties. By default each ReflexElement gets 487 | // evenly arranged within its container 488 | // 489 | ///////////////////////////////////////////////////////// 490 | computeFlexData(children = this.getValidChildren(), props = this.props) { 491 | const pixelFlex = this.computePixelFlex(props.orientation); 492 | const computeFreeFlex = flexData => { 493 | return flexData.reduce((sum, entry) => { 494 | if (!ReflexSplitter.isA(entry) && entry.constrained) { 495 | return sum - entry.flex; 496 | } 497 | return sum; 498 | }, 1.0); 499 | }; 500 | const computeFreeElements = flexData => { 501 | return flexData.reduce((sum, entry) => { 502 | if (!ReflexSplitter.isA(entry) && !entry.constrained) { 503 | return sum + 1; 504 | } 505 | return sum; 506 | }, 0.0); 507 | }; 508 | const flexDataInit = children.map(child => { 509 | const props = child.props; 510 | return { 511 | maxFlex: (props.maxSize || Number.MAX_VALUE) * pixelFlex, 512 | sizeFlex: (props.size || Number.MAX_VALUE) * pixelFlex, 513 | minFlex: (props.minSize || 1) * pixelFlex, 514 | constrained: props.flex !== undefined, 515 | flex: props.flex || 0, 516 | type: child.type 517 | }; 518 | }); 519 | const computeFlexDataRec = (flexDataIn, depth = 0) => { 520 | let hasContrain = false; 521 | const freeElements = computeFreeElements(flexDataIn); 522 | const freeFlex = computeFreeFlex(flexDataIn); 523 | const flexDataOut = flexDataIn.map(entry => { 524 | if (ReflexSplitter.isA(entry)) { 525 | return entry; 526 | } 527 | const proposedFlex = !entry.constrained ? freeFlex / freeElements : entry.flex; 528 | const constrainedFlex = Math.min(entry.sizeFlex, Math.min(entry.maxFlex, Math.max(entry.minFlex, proposedFlex))); 529 | const constrained = entry.constrained || constrainedFlex !== proposedFlex; 530 | hasContrain = hasContrain || constrained; 531 | return _objectSpread({}, entry, { 532 | flex: constrainedFlex, 533 | constrained 534 | }); 535 | }); 536 | return hasContrain && depth < this.props.maxRecDepth ? computeFlexDataRec(flexDataOut, depth + 1) : flexDataOut; 537 | }; 538 | const flexData = computeFlexDataRec(flexDataInit); 539 | return flexData.map(entry => { 540 | return { 541 | flex: !ReflexSplitter.isA(entry) ? entry.flex : 0.0, 542 | ref: React.createRef() 543 | }; 544 | }); 545 | } 546 | 547 | ///////////////////////////////////////////////////////// 548 | // Utility method to ensure given argument is 549 | // returned as an array 550 | // 551 | ///////////////////////////////////////////////////////// 552 | toArray(obj) { 553 | return obj ? Array.isArray(obj) ? obj : [obj] : []; 554 | } 555 | 556 | ///////////////////////////////////////////////////////// 557 | // Render container. This will clone all original child 558 | // components in order to pass some internal properties 559 | // used to handle resizing logic 560 | // 561 | ///////////////////////////////////////////////////////// 562 | render() { 563 | const className = [this.state.resizing ? 'reflex-resizing' : '', ...this.props.className.split(' '), this.props.orientation, 'reflex-container'].join(' ').trim(); 564 | this.children = React.Children.map(this.getValidChildren(), (child, index) => { 565 | if (index > this.state.flexData.length - 1) { 566 | return /*#__PURE__*/React.createElement("div", null); 567 | } 568 | const flexData = this.state.flexData[index]; 569 | const newProps = _objectSpread({}, child.props, { 570 | maxSize: child.props.maxSize || Number.MAX_VALUE, 571 | orientation: this.props.orientation, 572 | minSize: child.props.minSize || 1, 573 | events: this.events, 574 | flex: flexData.flex, 575 | ref: flexData.ref, 576 | index 577 | }); 578 | return React.cloneElement(child, newProps); 579 | }); 580 | return /*#__PURE__*/React.createElement("div", _extends({}, getDataProps(this.props), { 581 | style: this.props.style, 582 | className: className, 583 | ref: this.ref 584 | }), this.children); 585 | } 586 | } 587 | _defineProperty(ReflexContainer, "propTypes", { 588 | windowResizeAware: PropTypes.bool, 589 | orientation: PropTypes.oneOf(['horizontal', 'vertical']), 590 | maxRecDepth: PropTypes.number, 591 | className: PropTypes.string, 592 | style: PropTypes.object 593 | }); 594 | _defineProperty(ReflexContainer, "defaultProps", { 595 | orientation: 'horizontal', 596 | windowResizeAware: false, 597 | maxRecDepth: 100, 598 | className: '', 599 | style: {} 600 | }); -------------------------------------------------------------------------------- /dist/es/ReflexElement.js: -------------------------------------------------------------------------------- 1 | import _extends from "@babel/runtime/helpers/extends"; 2 | import _objectSpread from "@babel/runtime/helpers/objectSpread"; 3 | import _defineProperty from "@babel/runtime/helpers/defineProperty"; 4 | /////////////////////////////////////////////////////////// 5 | // ReflexElement 6 | // By Philippe Leefsma 7 | // December 2016 8 | // 9 | /////////////////////////////////////////////////////////// 10 | import ReflexHandle from './ReflexHandle'; 11 | import { getDataProps } from './utilities'; 12 | import throttle from 'lodash.throttle'; 13 | import Measure from 'react-measure'; 14 | import PropTypes from 'prop-types'; 15 | import React from 'react'; 16 | const toArray = obj => { 17 | return obj ? Array.isArray(obj) ? obj : [obj] : []; 18 | }; 19 | class SizeAwareReflexElement extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | _defineProperty(this, "onResize", rect => { 23 | const { 24 | resizeHeight, 25 | resizeWidth 26 | } = this.props; 27 | const { 28 | height, 29 | width 30 | } = rect.bounds; 31 | this.setDimensions(_objectSpread({}, resizeHeight && { 32 | height 33 | }, resizeWidth && { 34 | width 35 | })); 36 | }); 37 | this.setDimensions = throttle(dimensions => { 38 | this.setState(dimensions); 39 | }, this.props.propagateDimensionsRate / 1000); 40 | this.state = { 41 | height: "100%", 42 | width: "100%" 43 | }; 44 | } 45 | renderChildren() { 46 | const { 47 | propagateDimensions 48 | } = this.props; 49 | const validChildren = toArray(this.props.children).filter(child => { 50 | return !!child; 51 | }); 52 | return React.Children.map(validChildren, child => { 53 | if (this.props.withHandle || ReflexHandle.isA(child)) { 54 | return React.cloneElement(child, _objectSpread({ 55 | dimensions: propagateDimensions && this.state 56 | }, child.props, { 57 | index: this.props.index - 1, 58 | events: this.props.events 59 | })); 60 | } 61 | if (propagateDimensions) { 62 | return React.cloneElement(child, _objectSpread({}, child.props, { 63 | dimensions: this.state 64 | })); 65 | } 66 | return child; 67 | }); 68 | } 69 | render() { 70 | return /*#__PURE__*/React.createElement(Measure, { 71 | bounds: true, 72 | onResize: this.onResize 73 | }, ({ 74 | measureRef 75 | }) => { 76 | return /*#__PURE__*/React.createElement("div", { 77 | ref: measureRef, 78 | className: "reflex-size-aware" 79 | }, /*#__PURE__*/React.createElement("div", { 80 | style: this.state 81 | }, this.renderChildren())); 82 | }); 83 | } 84 | } 85 | class ReflexElement extends React.Component { 86 | constructor(props) { 87 | super(props); 88 | this.state = { 89 | size: props.size 90 | }; 91 | } 92 | static getDerivedStateFromProps(nextProps, prevState) { 93 | if (nextProps.size !== prevState.size) { 94 | return _objectSpread({}, prevState, { 95 | size: nextProps.size 96 | }); 97 | } 98 | return null; 99 | } 100 | async componentDidUpdate(prevProps, prevState, snapshot) { 101 | if (prevState.size !== this.state.size) { 102 | const directions = toArray(this.props.direction); 103 | for (let direction of directions) { 104 | await this.props.events.emit('element.size', { 105 | index: this.props.index, 106 | size: this.props.size, 107 | direction 108 | }); 109 | } 110 | } 111 | } 112 | renderChildren() { 113 | const validChildren = toArray(this.props.children).filter(child => { 114 | return !!child; 115 | }); 116 | return React.Children.map(validChildren, child => { 117 | if (this.props.withHandle || ReflexHandle.isA(child)) { 118 | return React.cloneElement(child, _objectSpread({}, child.props, { 119 | index: this.props.index - 1, 120 | events: this.props.events 121 | })); 122 | } 123 | return child; 124 | }); 125 | } 126 | render() { 127 | const className = [...this.props.className.split(' '), this.props.orientation, 'reflex-element'].join(' ').trim(); 128 | const style = _objectSpread({}, this.props.style, { 129 | flexGrow: this.props.flex, 130 | flexShrink: 1, 131 | flexBasis: '0%' 132 | }); 133 | return /*#__PURE__*/React.createElement("div", _extends({}, getDataProps(this.props), { 134 | ref: this.props.innerRef, 135 | className: className, 136 | style: style 137 | }), this.props.propagateDimensions ? /*#__PURE__*/React.createElement(SizeAwareReflexElement, this.props) : this.renderChildren()); 138 | } 139 | } 140 | _defineProperty(ReflexElement, "propTypes", { 141 | propagateDimensions: PropTypes.bool, 142 | resizeHeight: PropTypes.bool, 143 | resizeWidth: PropTypes.bool, 144 | className: PropTypes.string, 145 | size: PropTypes.number 146 | }); 147 | _defineProperty(ReflexElement, "defaultProps", { 148 | propagateDimensionsRate: 100, 149 | propagateDimensions: false, 150 | resizeHeight: true, 151 | resizeWidth: true, 152 | direction: [1], 153 | className: '' 154 | }); 155 | export default React.forwardRef((props, ref) => { 156 | return /*#__PURE__*/React.createElement(ReflexElement, _extends({ 157 | innerRef: ref 158 | }, props)); 159 | }); -------------------------------------------------------------------------------- /dist/es/ReflexEvents.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // ReflexEvents 3 | // By Philippe Leefsma 4 | // December 2016 5 | // 6 | /////////////////////////////////////////////////////////// 7 | class ReflexEvents { 8 | constructor() { 9 | this._events = {}; 10 | } 11 | 12 | ///////////////////////////////////////////////////////// 13 | // Supports multiple events space-separated 14 | // 15 | ///////////////////////////////////////////////////////// 16 | on(events, fct) { 17 | events.split(' ').forEach(event => { 18 | this._events[event] = this._events[event] || []; 19 | this._events[event].push(fct); 20 | }); 21 | return this; 22 | } 23 | 24 | ///////////////////////////////////////////////////////// 25 | // Supports multiple events space-separated 26 | // 27 | ///////////////////////////////////////////////////////// 28 | off(events, fct) { 29 | if (events == undefined) { 30 | this._events = {}; 31 | return; 32 | } 33 | events.split(' ').forEach(event => { 34 | if (event in this._events === false) return; 35 | if (fct) { 36 | this._events[event].splice(this._events[event].indexOf(fct), 1); 37 | } else { 38 | this._events[event] = []; 39 | } 40 | }); 41 | return this; 42 | } 43 | emit(event /* , args... */) { 44 | if (this._events[event] === undefined) return; 45 | var tmpArray = this._events[event].slice(); 46 | for (var i = 0; i < tmpArray.length; ++i) { 47 | var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1)); 48 | if (result !== undefined) { 49 | return result; 50 | } 51 | } 52 | return undefined; 53 | } 54 | } 55 | export default ReflexEvents; -------------------------------------------------------------------------------- /dist/es/ReflexHandle.js: -------------------------------------------------------------------------------- 1 | import _extends from "@babel/runtime/helpers/extends"; 2 | import _defineProperty from "@babel/runtime/helpers/defineProperty"; 3 | /////////////////////////////////////////////////////////// 4 | // ReflexHandle 5 | // By Philippe Leefsma 6 | // June 2018 7 | // 8 | /////////////////////////////////////////////////////////// 9 | import { getDataProps } from './utilities'; 10 | import PropTypes from 'prop-types'; 11 | import React from 'react'; 12 | export default class ReflexHandle extends React.Component { 13 | static isA(element) { 14 | if (!element) { 15 | return false; 16 | } 17 | //https://github.com/leefsmp/Re-Flex/issues/49 18 | return process.env.NODE_ENV === 'development' ? element.type === /*#__PURE__*/React.createElement(ReflexHandle, null).type : element.type === ReflexHandle; 19 | } 20 | constructor(props) { 21 | super(props); 22 | _defineProperty(this, "ref", React.createRef()); 23 | _defineProperty(this, "onMouseMove", event => { 24 | if (this.state.active) { 25 | const domElement = this.ref.current; 26 | this.props.events.emit('resize', { 27 | index: this.props.index, 28 | domElement, 29 | event 30 | }); 31 | if (this.props.onResize) { 32 | this.props.onResize({ 33 | component: this, 34 | domElement 35 | }); 36 | } 37 | event.stopPropagation(); 38 | event.preventDefault(); 39 | } 40 | }); 41 | _defineProperty(this, "onMouseDown", event => { 42 | this.setState({ 43 | active: true 44 | }); 45 | if (this.props.onStartResize) { 46 | // cancels resize from controller 47 | // if needed by returning true 48 | // to onStartResize 49 | if (this.props.onStartResize({ 50 | domElement: this.ref.current, 51 | component: this 52 | })) { 53 | return; 54 | } 55 | } 56 | this.props.events.emit('startResize', { 57 | index: this.props.index, 58 | event 59 | }); 60 | }); 61 | _defineProperty(this, "onMouseUp", event => { 62 | if (this.state.active) { 63 | this.setState({ 64 | active: false 65 | }); 66 | if (this.props.onStopResize) { 67 | this.props.onStopResize({ 68 | domElement: this.ref.current, 69 | component: this 70 | }); 71 | } 72 | this.props.events.emit('stopResize', { 73 | index: this.props.index, 74 | event 75 | }); 76 | } 77 | }); 78 | this.state = { 79 | active: false 80 | }; 81 | this.document = props.document; 82 | } 83 | componentDidMount() { 84 | if (!this.document) { 85 | return; 86 | } 87 | this.document.addEventListener('touchend', this.onMouseUp); 88 | this.document.addEventListener('mouseup', this.onMouseUp); 89 | this.document.addEventListener('mousemove', this.onMouseMove, { 90 | passive: false 91 | }); 92 | this.document.addEventListener('touchmove', this.onMouseMove, { 93 | passive: false 94 | }); 95 | } 96 | componentWillUnmount() { 97 | if (!this.document) { 98 | return; 99 | } 100 | this.document.removeEventListener('mouseup', this.onMouseUp); 101 | this.document.removeEventListener('touchend', this.onMouseUp); 102 | this.document.removeEventListener('mousemove', this.onMouseMove); 103 | this.document.removeEventListener('touchmove', this.onMouseMove); 104 | if (this.state.active) { 105 | this.props.events.emit('stopResize', { 106 | index: this.props.index, 107 | event: null 108 | }); 109 | } 110 | } 111 | render() { 112 | const className = [...this.props.className.split(' '), this.state.active ? 'active' : '', 'reflex-handle'].join(' ').trim(); 113 | return /*#__PURE__*/React.createElement("div", _extends({}, getDataProps(this.props), { 114 | onTouchStart: this.onMouseDown, 115 | onMouseDown: this.onMouseDown, 116 | style: this.props.style, 117 | className: className, 118 | id: this.props.id, 119 | ref: this.ref 120 | }), this.props.children); 121 | } 122 | } 123 | _defineProperty(ReflexHandle, "propTypes", { 124 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), 125 | onStartResize: PropTypes.func, 126 | onStopResize: PropTypes.func, 127 | className: PropTypes.string, 128 | propagate: PropTypes.bool, 129 | onResize: PropTypes.func, 130 | style: PropTypes.object 131 | }); 132 | _defineProperty(ReflexHandle, "defaultProps", { 133 | document: typeof document === 'undefined' ? null : document, 134 | onStartResize: null, 135 | onStopResize: null, 136 | propagate: false, 137 | onResize: null, 138 | className: '', 139 | style: {} 140 | }); -------------------------------------------------------------------------------- /dist/es/ReflexSplitter.js: -------------------------------------------------------------------------------- 1 | import _extends from "@babel/runtime/helpers/extends"; 2 | import _defineProperty from "@babel/runtime/helpers/defineProperty"; 3 | /////////////////////////////////////////////////////////// 4 | // ReflexSplitter 5 | // By Philippe Leefsma 6 | // December 2016 7 | // 8 | /////////////////////////////////////////////////////////// 9 | import { Browser, getDataProps } from './utilities'; 10 | import PropTypes from 'prop-types'; 11 | import React from 'react'; 12 | export default class ReflexSplitter extends React.Component { 13 | ///////////////////////////////////////////////////////// 14 | // Determines if element is a splitter 15 | // or wraps a splitter 16 | // 17 | ///////////////////////////////////////////////////////// 18 | static isA(element) { 19 | if (!element) { 20 | return false; 21 | } 22 | //https://github.com/leefsmp/Re-Flex/issues/49 23 | return element.type === /*#__PURE__*/React.createElement(ReflexSplitter, null).type; 24 | } 25 | constructor(props) { 26 | super(props); 27 | _defineProperty(this, "ref", React.createRef()); 28 | _defineProperty(this, "onMouseMove", event => { 29 | if (this.state.active) { 30 | const domElement = this.ref.current; 31 | this.props.events.emit('resize', { 32 | index: this.props.index, 33 | domElement, 34 | event 35 | }); 36 | if (this.props.onResize) { 37 | this.props.onResize({ 38 | component: this, 39 | domElement 40 | }); 41 | } 42 | event.stopPropagation(); 43 | event.preventDefault(); 44 | } 45 | }); 46 | _defineProperty(this, "onMouseDown", event => { 47 | this.setState({ 48 | active: true 49 | }); 50 | if (this.props.onStartResize) { 51 | // cancels resize from controller 52 | // if needed by returning true 53 | // to onStartResize 54 | if (this.props.onStartResize({ 55 | domElement: this.ref.current, 56 | component: this 57 | })) { 58 | return; 59 | } 60 | } 61 | this.props.events.emit('startResize', { 62 | index: this.props.index, 63 | event 64 | }); 65 | }); 66 | _defineProperty(this, "onMouseUp", event => { 67 | if (this.state.active) { 68 | this.setState({ 69 | active: false 70 | }); 71 | if (this.props.onStopResize) { 72 | this.props.onStopResize({ 73 | domElement: this.ref.current, 74 | component: this 75 | }); 76 | } 77 | this.props.events.emit('stopResize', { 78 | index: this.props.index, 79 | event 80 | }); 81 | } 82 | }); 83 | this.state = { 84 | active: false 85 | }; 86 | this.document = props.document; 87 | } 88 | componentDidMount() { 89 | if (!this.document) { 90 | return; 91 | } 92 | this.document.addEventListener('touchend', this.onMouseUp); 93 | this.document.addEventListener('mouseup', this.onMouseUp); 94 | this.document.addEventListener('mousemove', this.onMouseMove, { 95 | passive: false 96 | }); 97 | this.document.addEventListener('touchmove', this.onMouseMove, { 98 | passive: false 99 | }); 100 | } 101 | componentWillUnmount() { 102 | if (!this.document) { 103 | return; 104 | } 105 | this.document.removeEventListener('mouseup', this.onMouseUp); 106 | this.document.removeEventListener('touchend', this.onMouseUp); 107 | this.document.removeEventListener('mousemove', this.onMouseMove); 108 | this.document.removeEventListener('touchmove', this.onMouseMove); 109 | if (this.state.active) { 110 | this.props.events.emit('stopResize', { 111 | index: this.props.index, 112 | event: null 113 | }); 114 | } 115 | } 116 | render() { 117 | const className = [Browser.isMobile() ? 'reflex-thin' : '', ...this.props.className.split(' '), this.state.active ? 'active' : '', 'reflex-splitter'].join(' ').trim(); 118 | return /*#__PURE__*/React.createElement("div", _extends({}, getDataProps(this.props), { 119 | onTouchStart: this.onMouseDown, 120 | onMouseDown: this.onMouseDown, 121 | style: this.props.style, 122 | className: className, 123 | id: this.props.id, 124 | ref: this.ref 125 | }), this.props.children); 126 | } 127 | } 128 | _defineProperty(ReflexSplitter, "propTypes", { 129 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), 130 | onStartResize: PropTypes.func, 131 | onStopResize: PropTypes.func, 132 | className: PropTypes.string, 133 | propagate: PropTypes.bool, 134 | onResize: PropTypes.func, 135 | style: PropTypes.object 136 | }); 137 | _defineProperty(ReflexSplitter, "defaultProps", { 138 | document: typeof document !== 'undefined' ? document : null, 139 | onStartResize: null, 140 | onStopResize: null, 141 | propagate: false, 142 | onResize: null, 143 | className: '', 144 | style: {} 145 | }); -------------------------------------------------------------------------------- /dist/es/index.js: -------------------------------------------------------------------------------- 1 | import ReflexContainer from './ReflexContainer'; 2 | import ReflexSplitter from './ReflexSplitter'; 3 | import ReflexElement from './ReflexElement'; 4 | import ReflexHandle from './ReflexHandle'; 5 | export { ReflexContainer, ReflexSplitter, ReflexElement, ReflexHandle }; -------------------------------------------------------------------------------- /dist/es/utilities.js: -------------------------------------------------------------------------------- 1 | import _objectSpread from "@babel/runtime/helpers/objectSpread"; 2 | ///////////////////////////////////////////////////////// 3 | // Browser Utils 4 | // 5 | ///////////////////////////////////////////////////////// 6 | class Browser { 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 | if (!Browser.isBrowser()) { 25 | return false; 26 | } 27 | return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 28 | } 29 | 30 | // Internet Explorer 6-11 31 | static isIE() { 32 | /*@cc_on!@*/ 33 | return Browser.isBrowser() && !!document.documentMode; 34 | } 35 | 36 | // Edge 20+ 37 | static isEdge() { 38 | return Browser.isBrowser() && !Browser.isIE() && !!window.StyleMedia; 39 | } 40 | 41 | // Chrome 1+ 42 | static isChrome() { 43 | return Browser.isBrowser() && !!window.chrome && !!window.chrome.webstore; 44 | } 45 | 46 | // Blink engine detection 47 | static isBlink() { 48 | return Browser.isBrowser() && (Browser.isChrome() || Browser.isOpera()) && !!window.CSS; 49 | } 50 | static getUserAgent() { 51 | return typeof navigator === 'undefined' ? '' : navigator.userAgent; 52 | } 53 | static isAndroid() { 54 | return Browser.isBrowser() && Browser.getUserAgent().match(/Android/i); 55 | } 56 | static isBlackBerry() { 57 | return Browser.isBrowser() && Browser.getUserAgent().match(/BlackBerry/i); 58 | } 59 | static isIOS() { 60 | return Browser.isBrowser() && Browser.getUserAgent().match(/iPhone|iPad|iPod/i); 61 | } 62 | static isOpera() { 63 | return Browser.isBrowser() && Browser.getUserAgent().match(/Opera Mini/i); 64 | } 65 | static isWindows() { 66 | return Browser.isBrowser() && Browser.isWindowsDesktop() || Browser.isWindowsMobile(); 67 | } 68 | static isWindowsMobile() { 69 | return Browser.isBrowser() && Browser.getUserAgent().match(/IEMobile/i); 70 | } 71 | static isWindowsDesktop() { 72 | return Browser.isBrowser() && Browser.getUserAgent().match(/WPDesktop/i); 73 | } 74 | static isMobile() { 75 | return Browser.isBrowser() && (Browser.isWindowsMobile() || Browser.isBlackBerry() || Browser.isAndroid() || Browser.isIOS()); 76 | } 77 | } 78 | 79 | ///////////////////////////////////////////////////////// 80 | // Returns only the props that start with "data-" 81 | // 82 | ///////////////////////////////////////////////////////// 83 | const getDataProps = props => { 84 | return Object.keys(props).reduce((prev, key) => { 85 | if (key.substr(0, 5) === 'data-') { 86 | return _objectSpread({}, prev, { 87 | [key]: props[key] 88 | }); 89 | } 90 | return prev; 91 | }, {}); 92 | }; 93 | export { getDataProps, Browser }; -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export type StyleAndClassAndChildren = { 4 | className?: string; 5 | style?: React.CSSProperties; 6 | children?: React.ReactNode; 7 | }; 8 | 9 | export type ReflexContainerProps = { 10 | orientation?: "horizontal" | "vertical"; 11 | maxRecDepth?: number; 12 | windowResizeAware?: boolean; 13 | } & StyleAndClassAndChildren; 14 | 15 | export class ReflexContainer extends React.Component { } 16 | 17 | export type PosNeg = -1 | 1; 18 | 19 | export type HandlerProps = { 20 | domElement: Element | Text, 21 | component: React.ComponentElement 22 | }; 23 | 24 | export type ReflexElementProps = { 25 | propagateDimensions?: boolean; 26 | propagateDimensionsRate?: number; 27 | resizeHeight?: boolean; 28 | resizeWidth?: boolean; 29 | size?: number; 30 | minSize?: number; 31 | maxSize?: number; 32 | flex?: number; 33 | direction?: PosNeg | [PosNeg, PosNeg]; 34 | onStartResize?: (args: HandlerProps) => void; 35 | onStopResize?: (args: HandlerProps) => void; 36 | onResize?: (args: HandlerProps) => void; 37 | name?: string; 38 | } & StyleAndClassAndChildren; 39 | 40 | export class ReflexElement extends React.Component { } 41 | 42 | export type ReflexSplitterProps = { 43 | propagate?: boolean; 44 | onStartResize?: (args: HandlerProps) => void; 45 | onStopResize?: (args: HandlerProps) => void; 46 | onResize?: (args: HandlerProps) => void; 47 | } & StyleAndClassAndChildren; 48 | 49 | export class ReflexSplitter extends React.Component { } 50 | 51 | export type ReflexHandleProps = { 52 | onStartResize?: (args: HandlerProps) => void; 53 | onStopResize?: (args: HandlerProps) => void; 54 | propagate?: boolean; 55 | onResize?: (args: HandlerProps) => void; 56 | } & StyleAndClassAndChildren; 57 | 58 | export class ReflexHandle extends React.Component { } 59 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export type StyleAndClassAndChildren = { 4 | className?: string; 5 | style?: React.CSSProperties; 6 | children?: React.ReactNode; 7 | }; 8 | 9 | export type ReflexContainerProps = { 10 | orientation?: "horizontal" | "vertical"; 11 | maxRecDepth?: number; 12 | windowResizeAware?: boolean; 13 | } & StyleAndClassAndChildren; 14 | 15 | export class ReflexContainer extends React.Component { } 16 | 17 | export type PosNeg = -1 | 1; 18 | 19 | export type HandlerProps = { 20 | domElement: Element | Text, 21 | component: React.ComponentElement 22 | }; 23 | 24 | export type ReflexElementProps = { 25 | propagateDimensions?: boolean; 26 | propagateDimensionsRate?: number; 27 | resizeHeight?: boolean; 28 | resizeWidth?: boolean; 29 | size?: number; 30 | minSize?: number; 31 | maxSize?: number; 32 | flex?: number; 33 | direction?: PosNeg | [PosNeg, PosNeg]; 34 | onStartResize?: (args: HandlerProps) => void; 35 | onStopResize?: (args: HandlerProps) => void; 36 | onResize?: (args: HandlerProps) => void; 37 | name?: string; 38 | } & StyleAndClassAndChildren; 39 | 40 | export class ReflexElement extends React.Component { } 41 | 42 | export type ReflexSplitterProps = { 43 | propagate?: boolean; 44 | onStartResize?: (args: HandlerProps) => void; 45 | onStopResize?: (args: HandlerProps) => void; 46 | onResize?: (args: HandlerProps) => void; 47 | } & StyleAndClassAndChildren; 48 | 49 | export class ReflexSplitter extends React.Component { } 50 | 51 | export type ReflexHandleProps = { 52 | onStartResize?: (args: HandlerProps) => void; 53 | onStopResize?: (args: HandlerProps) => void; 54 | propagate?: boolean; 55 | onResize?: (args: HandlerProps) => void; 56 | } & StyleAndClassAndChildren; 57 | 58 | export class ReflexHandle extends React.Component { } 59 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Re-F|ex Demo 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |

15 | 1 - Basic Re-Flex example: 16 |

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 |

32 | 2 - Basic Re-Flex example with splitter: 33 |

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 |

48 | 3 - Re-Flex example with splitter propagation (2x): 49 |

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 |

72 | 4 - Re-Flex example with splitter propagation (3x): 73 |

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 |

87 | 5 - Advanced Re-Flex example: 88 |

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 |

113 | 6 - Controlled elements Re-Flex example: 114 |

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 |

135 | 7 - Size-aware element Re-Flex example: 136 |

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 |

156 | 8 - Storage Re-Flex example: 157 |

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 |

173 | 9 - Collapsible panels Re-Flex example: 174 |

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 |

189 | 10 - Handle Element Re-Flex example: 190 |

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 | ![forge-rcdb](https://github.com/Autodesk-Forge/forge-rcdb.nodejs/blob/master/resources/img/misc/configurator.png) 7 | 8 | * [CodecastJS.com](https://codecastjs.com) 9 | 10 | ![CodecastJS](https://codecastjs.com/images/editor-screen-shot.png) 11 | 12 | * [Codier](https://codier.io) 13 | 14 | ![Codier](https://i.imgur.com/G3pZIa9.png) 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 | --------------------------------------------------------------------------------