├── .flowconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── 01_basic_spec.js ├── 02_setter_spec.js └── __snapshots__ │ └── 02_setter_spec.js.snap ├── dist └── index.js ├── example ├── index.html └── main.js ├── flow-typed └── index.js ├── package-lock.json ├── package.json └── src ├── index.d.ts └── index.js /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | npm-debug.log 4 | node_modules 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 7 5 | - 8 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased] 4 | 5 | ## [1.6.0] - 2019-01-06 6 | ### Changed 7 | - no JSX in the library code 8 | 9 | ## [1.5.0] - 2018-10-18 10 | ### Added 11 | - naive type definition for TypeScript 12 | 13 | ## [1.4.0] - 2018-10-18 14 | ### Changed 15 | - update devDependencies 16 | 17 | ## [1.3.0] - 2018-09-17 18 | ### Changed 19 | - update devDependencies 20 | 21 | ## [1.2.0] - 2018-09-17 22 | ### Added 23 | - flow type annotation 24 | 25 | ## [1.1.0] - 2017-10-26 26 | ### Changed 27 | - update devDependencies 28 | 29 | ## [1.0.0] - 2017-09-13 30 | ### Added 31 | - options to configure state setters 32 | - fix the API with v1.0.0 33 | 34 | ## [0.2.0] - 2017-09-08 35 | ### Changed 36 | - update eslint packages 37 | - move to jest from mocha/chai 38 | - update React and use PureComponent 39 | - update babel packages 40 | - update webpack packages 41 | 42 | ## [0.1.2] - 2017-09-08 43 | ### Fixed 44 | - updated dist files 45 | 46 | ## [0.1.1] - 2016-05-05 47 | ### Fixed 48 | - shoudComponentUpdate uses shallowequal for better performance 49 | 50 | ## [0.1.0] - 2016-05-03 51 | ### Added 52 | - Initial release 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 Daishi Kato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-compose-state 2 | =================== 3 | 4 | [![Build Status](https://travis-ci.org/dai-shi/react-compose-state.svg?branch=master)](https://travis-ci.org/dai-shi/react-compose-state) 5 | [![npm version](https://badge.fury.io/js/react-compose-state.svg)](https://badge.fury.io/js/react-compose-state) 6 | [![bundle size](https://badgen.net/bundlephobia/minzip/react-compose-state)](https://bundlephobia.com/result?p=react-compose-state) 7 | 8 | A helper function to attach state to 9 | stateless function components. 10 | 11 | Background 12 | ---------- 13 | 14 | Since React v0.14, stateless function components are supported, 15 | which allows you to write a component as a pure function. 16 | This is very handy with ES2015 and JSX. Here is an example. 17 | 18 | ```javascript 19 | const Hello = ({ name }) => (
Hello, {name}!
); 20 | ``` 21 | 22 | It is recommended to use stateless components as much as possible, 23 | and once you are used to it, you might want to avoid writing class-based components. 24 | Class-based components are powerful and you can mange lifecycles of components, 25 | but the state is one of what is required often, especially if using an external store (like Flux) is not an option. 26 | 27 | This package provides an easy way to add state to statelss components. 28 | This avoids the use of `this` which is also known as thisless javascript. 29 | 30 | Install 31 | ------- 32 | 33 | ```bash 34 | npm install react-compose-state --save 35 | ``` 36 | 37 | Usage 38 | ----- 39 | 40 | One liner: 41 | 42 | ```javascript 43 | import React from 'react'; 44 | import { composeWithState } from 'react-compose-state'; 45 | 46 | const Counter = composeWithState({ counter: 1 })(({ counter, setCounter }) => ( 47 |
48 | Count: {counter} 49 | 50 |
51 | )); 52 | ``` 53 | 54 | With PropTypes: 55 | 56 | ```javascript 57 | import React from 'react'; 58 | import PropTypes from 'prop-types'; 59 | import { composeWithState } from 'react-compose-state'; 60 | 61 | const Counter = ({ counter, setCounter }) => ( 62 |
63 | Count: {counter} 64 | 65 |
66 | )); 67 | 68 | Counter.propTypes = { 69 | counter: PropTypes.number.isRequired, 70 | setCounter: PropTypes.func.isRequired, 71 | } 72 | 73 | const initialState = { counter: 1 }; 74 | 75 | export default composeWithState(initialState)(Counter); 76 | ``` 77 | 78 | With options: 79 | 80 | ```javascript 81 | import React from 'react'; 82 | import PropTypes from 'prop-types'; 83 | import { composeWithState } from 'react-compose-state'; 84 | 85 | const Counter = ({ counter, updateCounter }) => ( 86 |
87 | Count: {counter} 88 | 89 |
90 | )); 91 | 92 | Counter.propTypes = { 93 | counter: PropTypes.number.isRequired, 94 | updateCounter: PropTypes.func.isRequired, 95 | } 96 | 97 | const initialState = { counter: 1 }; 98 | const options = { 99 | setters: { 100 | counter: 'updateCounter', 101 | }, 102 | }; 103 | 104 | export default composeWithState(initialState, options)(Counter); 105 | ``` 106 | 107 | Example 108 | ------- 109 | 110 | The [example](example) folder contains a working example. 111 | You can run it with 112 | 113 | ```bash 114 | PORT=8080 npm run example 115 | ``` 116 | 117 | and open in your web browser. 118 | -------------------------------------------------------------------------------- /__tests__/01_basic_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import renderer from 'react-test-renderer'; 6 | import { composeWithState } from '../src/index'; 7 | 8 | describe('basic spec', () => { 9 | it('should have a compose function', () => { 10 | expect(composeWithState).toBeDefined(); 11 | }); 12 | 13 | it('should compose a component without state', () => { 14 | const BaseComponent = () => (

Base

); 15 | const ComposedComponent = composeWithState()(BaseComponent); 16 | 17 | const actual = renderer.create().toJSON(); 18 | const expected = renderer.create(

Base

).toJSON(); 19 | 20 | expect(actual).toEqual(expected); 21 | }); 22 | 23 | it('should compose a component with state', () => { 24 | const BaseComponent = ({ text }) => (

{text}

); 25 | BaseComponent.propTypes = { 26 | text: PropTypes.string.isRequired, 27 | }; 28 | const initialState = { text: 'hello' }; 29 | const ComposedComponent = composeWithState(initialState)(BaseComponent); 30 | 31 | const actual = renderer.create().toJSON(); 32 | const expected = renderer.create(

hello

).toJSON(); 33 | 34 | expect(actual).toEqual(expected); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /__tests__/02_setter_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import { configure, mount } from 'enzyme'; 6 | import toJson from 'enzyme-to-json'; 7 | import Adapter from 'enzyme-adapter-react-15'; 8 | 9 | import { composeWithState } from '../src/index'; 10 | 11 | configure({ adapter: new Adapter() }); 12 | 13 | describe('setter spec', () => { 14 | it('should update state with default setter', () => { 15 | const BaseComponent = ({ text, setText }) => ( 16 |
17 |

{text}

18 | 19 |
20 | ); 21 | BaseComponent.propTypes = { 22 | text: PropTypes.string.isRequired, 23 | setText: PropTypes.func.isRequired, 24 | }; 25 | const initialState = { text: 'hello' }; 26 | const ComposedComponent = composeWithState(initialState)(BaseComponent); 27 | 28 | const wrapper = mount(); 29 | expect(toJson(wrapper)).toMatchSnapshot(); 30 | wrapper.find('button').simulate('click'); 31 | expect(toJson(wrapper)).toMatchSnapshot(); 32 | }); 33 | 34 | it('should update state with custom setter', () => { 35 | const BaseComponent = ({ text, updateText }) => ( 36 |
37 |

{text}

38 | 39 |
40 | ); 41 | BaseComponent.propTypes = { 42 | text: PropTypes.string.isRequired, 43 | updateText: PropTypes.func.isRequired, 44 | }; 45 | const initialState = { text: 'hello' }; 46 | const options = { setters: { text: 'updateText' } }; 47 | const ComposedComponent = composeWithState(initialState, options)(BaseComponent); 48 | 49 | const wrapper = mount(); 50 | expect(toJson(wrapper)).toMatchSnapshot(); 51 | wrapper.find('button').simulate('click'); 52 | expect(toJson(wrapper)).toMatchSnapshot(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/02_setter_spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`setter spec should update state with custom setter 1`] = ` 4 | <_class> 5 | 9 |
10 |

11 | hello 12 |

13 | 19 |
20 |
21 | 22 | `; 23 | 24 | exports[`setter spec should update state with custom setter 2`] = ` 25 | <_class> 26 | 30 |
31 |

32 | replaced 33 |

34 | 40 |
41 |
42 | 43 | `; 44 | 45 | exports[`setter spec should update state with default setter 1`] = ` 46 | <_class> 47 | 51 |
52 |

53 | hello 54 |

55 | 61 |
62 |
63 | 64 | `; 65 | 66 | exports[`setter spec should update state with default setter 2`] = ` 67 | <_class> 68 | 72 |
73 |

74 | replaced 75 |

76 | 82 |
83 |
84 | 85 | `; 86 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.composeWithState = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 11 | 12 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 13 | 14 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 15 | 16 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 17 | 18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 19 | 20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 21 | 22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 23 | 24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 25 | 26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 27 | 28 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 29 | 30 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 31 | 32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 33 | 34 | /*:: 35 | import type { StatelessFunctionalComponent } from 'react'; 36 | */ 37 | var isFunction = function isFunction(fn) { 38 | return typeof fn === 'function'; 39 | }; 40 | 41 | var capitalize = function capitalize(str) { 42 | return str.charAt(0).toUpperCase() + str.slice(1); 43 | }; 44 | 45 | var composeWithState = function composeWithState(initialState 46 | /*: (Object | (props: Object) => Object)*/ 47 | ) { 48 | var options 49 | /*: Object */ 50 | = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 51 | return function (BaseComponent 52 | /*: StatelessFunctionalComponent */ 53 | ) { 54 | return ( 55 | /*#__PURE__*/ 56 | function (_React$PureComponent) { 57 | _inherits(_class, _React$PureComponent); 58 | 59 | function _class(props 60 | /*: Object */ 61 | ) { 62 | var _this; 63 | 64 | _classCallCheck(this, _class); 65 | 66 | _this = _possibleConstructorReturn(this, _getPrototypeOf(_class).call(this, props)); 67 | 68 | if (isFunction(initialState)) { 69 | var initializeState = initialState 70 | /*: (props: Object) => Object */ 71 | ; 72 | _this.state = initializeState(props); 73 | } else { 74 | _this.state = initialState; 75 | } 76 | 77 | if (!_this.state) { 78 | _this.state = {}; 79 | } 80 | 81 | _this.stateSetters = {}; 82 | var _options$setters = options.setters, 83 | setters = _options$setters === void 0 ? {} : _options$setters; 84 | Object.keys(_this.state).forEach(function (key) { 85 | var setter = setters[key] || "set".concat(capitalize(key)); 86 | 87 | _this.stateSetters[setter] = function (val) { 88 | _this.setState(_defineProperty({}, key, val)); 89 | }; 90 | }); 91 | return _this; 92 | } 93 | /*:: 94 | stateSetters: { 95 | [string]: string => void, 96 | }; 97 | */ 98 | 99 | 100 | _createClass(_class, [{ 101 | key: "render", 102 | value: function render() { 103 | return _react.default.createElement(BaseComponent, _objectSpread({}, this.props, this.state, this.stateSetters)); 104 | } 105 | }]); 106 | 107 | return _class; 108 | }(_react.default.PureComponent 109 | /*:: */ 110 | ) 111 | ); 112 | }; 113 | }; 114 | 115 | exports.composeWithState = composeWithState; -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | react-compose-state example 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import { composeWithState } from '../src/index'; 7 | 8 | const Counter = composeWithState({ counter: 1 })(({ counter, setCounter }) => ( 9 |
10 | 11 | Count: 12 | {counter} 13 | 14 | 15 | 16 |
17 | )); 18 | 19 | const TextBox = composeWithState({ text: '' })(({ text, setText }) => ( 20 |
21 | 22 | Text: 23 | {text} 24 | 25 | setText(event.target.value)} /> 26 |
27 | )); 28 | 29 | const App = () => ( 30 |
31 |

Counter

32 | 33 | 34 |

TextBox

35 | 36 | 37 |
38 | ); 39 | 40 | ReactDOM.render(, document.getElementById('content')); 41 | -------------------------------------------------------------------------------- /flow-typed/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare function composeWithState(initialState: Object | (props: Object) => Object, options?: Object): any; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-compose-state", 3 | "description": "A helper function to attach state to stateless function components", 4 | "version": "1.6.1", 5 | "author": "Daishi Kato", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/dai-shi/react-compose-state.git" 9 | }, 10 | "main": "./dist/index.js", 11 | "source": "./src/index.js", 12 | "types": "./src/index.d.ts", 13 | "scripts": { 14 | "compile": "babel src -d dist", 15 | "stop-flow": "flow stop", 16 | "test": "npm run eslint && npm run stop-flow && npm run flow && npm run jest", 17 | "eslint": "eslint src example __tests__", 18 | "flow": "flow", 19 | "jest": "jest", 20 | "example": "webpack-dev-server --port ${PORT:-8080} --entry ./example/main.js --output-filename bundle.js --content-base example --module-bind 'js=babel-loader' --mode development" 21 | }, 22 | "keywords": [ 23 | "react", 24 | "container", 25 | "state", 26 | "compose", 27 | "stateless", 28 | "thisless", 29 | "pure" 30 | ], 31 | "license": "MIT", 32 | "dependencies": {}, 33 | "devDependencies": { 34 | "@babel/cli": "^7.2.3", 35 | "@babel/core": "^7.2.2", 36 | "@babel/preset-env": "^7.2.3", 37 | "@babel/preset-react": "^7.0.0", 38 | "babel-core": "^7.0.0-bridge.0", 39 | "babel-eslint": "^10.0.1", 40 | "babel-loader": "^8.0.5", 41 | "enzyme": "^3.8.0", 42 | "enzyme-adapter-react-15": "^1.2.0", 43 | "enzyme-to-json": "^3.3.5", 44 | "eslint": "^5.12.0", 45 | "eslint-config-airbnb": "^17.1.0", 46 | "eslint-plugin-import": "^2.14.0", 47 | "eslint-plugin-jsx-a11y": "^6.1.2", 48 | "eslint-plugin-react": "^7.12.3", 49 | "flow-bin": "^0.89.0", 50 | "jest": "^23.6.0", 51 | "prop-types": "^15.6.2", 52 | "react": "^15.6.2", 53 | "react-dom": "^15.6.2", 54 | "react-test-renderer": "^15.6.2", 55 | "webpack": "^4.28.3", 56 | "webpack-cli": "^3.2.0", 57 | "webpack-dev-server": "^3.1.14" 58 | }, 59 | "peerDependencies": { 60 | "react": ">=15.6.2" 61 | }, 62 | "eslintConfig": { 63 | "parser": "babel-eslint", 64 | "extends": [ 65 | "airbnb" 66 | ], 67 | "rules": { 68 | "import/prefer-default-export": 0, 69 | "react/jsx-filename-extension": [ 70 | 2, 71 | { 72 | "extensions": [ 73 | ".js", 74 | ".jsx" 75 | ] 76 | } 77 | ], 78 | "import/no-extraneous-dependencies": [ 79 | 2, 80 | { 81 | "devDependencies": true 82 | } 83 | ], 84 | "spaced-comment": 0, 85 | "arrow-parens": 0 86 | } 87 | }, 88 | "babel": { 89 | "presets": [ 90 | "@babel/preset-env", 91 | "@babel/preset-react" 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | type Mapper = (input: I) => O; 2 | type Updater = (f: ((v: T) => T) | T) => void; 3 | type Options = { setters: any }; 4 | type GetterProps = {[K in keyof S]: S[K]}; 5 | type SetterProps = any; 6 | export type ComposeWithState

= 7 | (initialState: S | Mapper, options?: Options) => 8 | (base: React.ComponentType

) => 9 | React.ComponentType

& SetterProps>; 10 | export const composeWithState: ComposeWithState; 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | /*:: 5 | import type { StatelessFunctionalComponent } from 'react'; 6 | */ 7 | 8 | const isFunction = fn => (typeof fn === 'function'); 9 | const capitalize = str => (str.charAt(0).toUpperCase() + str.slice(1)); 10 | 11 | export const composeWithState = ( 12 | initialState /*: (Object | (props: Object) => Object)*/, 13 | options /*: Object */ = {}, 14 | ) => (BaseComponent /*: StatelessFunctionalComponent */) => ( 15 | class extends React.PureComponent/*:: */ { 16 | constructor(props /*: Object */) { 17 | super(props); 18 | if (isFunction(initialState)) { 19 | const initializeState = (initialState /*: (props: Object) => Object */); 20 | this.state = initializeState(props); 21 | } else { 22 | this.state = initialState; 23 | } 24 | if (!this.state) { 25 | this.state = {}; 26 | } 27 | this.stateSetters = {}; 28 | const { setters = {} } = options; 29 | Object.keys(this.state).forEach((key) => { 30 | const setter = setters[key] || `set${capitalize(key)}`; 31 | this.stateSetters[setter] = (val) => { 32 | this.setState({ [key]: val }); 33 | }; 34 | }); 35 | } 36 | 37 | /*:: 38 | stateSetters: { 39 | [string]: string => void, 40 | }; 41 | */ 42 | 43 | render() { 44 | return React.createElement(BaseComponent, { 45 | ...this.props, 46 | ...this.state, 47 | ...this.stateSetters, 48 | }); 49 | } 50 | } 51 | ); 52 | --------------------------------------------------------------------------------