├── .babelrc ├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── Readme.md ├── examples ├── generic.js ├── hash │ ├── index.html │ ├── index.js │ └── style.css └── pushState │ ├── index.html │ ├── index.js │ └── style.css ├── index.js ├── package.json ├── rollup.config.browser.js ├── rollup.config.example.js ├── rollup.config.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", {"modules": false}], 4 | "react", 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | ["transform-react-jsx", { "pragma": "h" }], 9 | ], 10 | "env": { 11 | "server": { 12 | "presets": ["es2015", "react", "stage-0"], 13 | "plugins": [ 14 | ["transform-react-jsx", { "pragma": "h" }], 15 | ], 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | Hello! Before sending a pull-request please note the following: 3 | 4 | - Please open an issue before adding new features 5 | - Pull-requests should include tests for the changes 6 | - Performance optimizations should provide benchmarks 7 | - Purely subjective stylistic changes will likely be declined 8 | - Unnecessary micro-optimizations will likely be declined 9 | - I don't use NPM scripts sorry :) 10 | 11 | Thanks! 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | bundle.js 4 | .DS_Store 5 | .nyc_output/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 6 5 | cache: 6 | directories: 7 | - node_modules 8 | after_success: 9 | - './node_modules/.bin/nyc report --reporter=lcov > coverage.lcov && ./node_modules/.bin/codecov' 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Adrien Antoine adriantoine@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | This is a port of [react-enroute](https://github.com/tj/react-enroute) for [Preact](https://preactjs.com). It works exactly the same way, I only adapted the code style to mine as I am going to maintain this one. I have also reorganised the examples and added an example using hash history. 2 | 3 | [![Build Status](https://travis-ci.org/adriantoine/preact-enroute.svg?branch=master)](https://travis-ci.org/adriantoine/preact-enroute) 4 | [![codecov](https://codecov.io/gh/adriantoine/preact-enroute/branch/master/graph/badge.svg)](https://codecov.io/gh/adriantoine/preact-enroute) 5 | [![Stable version](https://img.shields.io/npm/v/preact-enroute.svg?style=flat)](https://www.npmjs.com/package/preact-enroute) 6 | [![Gemnasium](https://img.shields.io/gemnasium/adriantoine/preact-enroute.svg)](https://gemnasium.com/github.com/adriantoine/preact-enroute) 7 | 8 | # preact-enroute 9 | 10 | Simple Preact router with a small footprint for modern browsers. This package is not meant to be a drop-in replacement for any router, just a smaller simpler alternative. 11 | 12 | See [path-to-regexp](https://github.com/pillarjs/path-to-regexp) for path matching, this is the same library used by Express. 13 | 14 | If you want to try it, play with it on [this CodePen (using hash history)](http://codepen.io/Alshten/pen/qaENkj), [on WebpackBin](http://www.webpackbin.com/NkS7tXIi-) or run the examples (see below). 15 | 16 | ## Installation 17 | 18 | ``` 19 | $ npm install preact-enroute 20 | ``` 21 | 22 | ## Examples 23 | 24 | No nesting: 25 | 26 | ```js 27 | render( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | , 36 | document.querySelector('#app') 37 | ); 38 | ``` 39 | 40 | Some nesting: 41 | 42 | ```js 43 | render( 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | , 57 | document.querySelector('#app') 58 | ); 59 | ``` 60 | 61 | Moar nesting: 62 | 63 | ```js 64 | render( 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | , 78 | document.querySelector('#app') 79 | ); 80 | ``` 81 | 82 | ## Developing 83 | 84 | Build: 85 | 86 | ``` 87 | $ npm run build 88 | ``` 89 | 90 | Start pushState example: 91 | 92 | ``` 93 | $ npm run example-pushstate 94 | ``` 95 | 96 | Start hash example: 97 | 98 | ``` 99 | $ npm run example-hash 100 | ``` 101 | 102 | Running tests: 103 | 104 | ``` 105 | $ npm test 106 | ``` 107 | -------------------------------------------------------------------------------- /examples/generic.js: -------------------------------------------------------------------------------- 1 | import {h} from 'preact'; 2 | 3 | // note this is just an example, this package does not provide 4 | // a Link equivalent found in react-router, nor does it provide 5 | // bindings for tools like Redux. You'll need to wire these up 6 | // as desired. 7 | function Link({to, children}, {navigate}) { 8 | function click(e) { 9 | e.preventDefault(); 10 | navigate(to); 11 | } 12 | 13 | return ( 14 | {children} 15 | ); 16 | } 17 | 18 | const User = ({users, pets, params: {id}}) => { 19 | const user = users.filter(u => u.id === parseInt(id, 10))[0]; 20 | const userPets = pets.filter(p => p.userId === parseInt(id, 10)); 21 | return ( 22 |
23 |

{user.name} has {userPets.length} pets:

24 | 31 |
32 | ); 33 | }; 34 | 35 | function Pets({pets, children}) { 36 | return ( 37 |
38 |

Pets

39 | 46 | {children} 47 |
48 | ); 49 | } 50 | 51 | const Pet = ({users, pets, params: {id}}) => { 52 | const pet = pets.filter(p => p.id === parseInt(id, 10))[0]; 53 | const user = users.filter(u => u.id === pet.userId)[0]; 54 | return

{pet.name} is a {pet.species} and is owned by {user.name}.

; 55 | }; 56 | 57 | function NotFound() { 58 | return

404 Not Found

; 59 | } 60 | 61 | const Index = ({children}) => { 62 | return ( 63 |
64 |

Pet List

65 |

At least it is not a to-do list. Check out users or pets.

66 | {children} 67 |
68 | ); 69 | }; 70 | 71 | const Users = ({users, children}) => { 72 | return ( 73 |
74 |

Users

75 | 82 | {children} 83 |
84 | ); 85 | }; 86 | 87 | export {Index, Pet, Pets, User, Users, NotFound, Link}; 88 | -------------------------------------------------------------------------------- /examples/hash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/hash/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component, render} from 'preact'; 2 | import {Router, Route} from '../../index'; 3 | import {Index, Pet, Pets, User, Users, NotFound} from '../generic'; 4 | 5 | const getHash = hash => { 6 | if (typeof hash === 'string' && hash.length > 0) { 7 | if (hash.substring(0, 1) === '#') { 8 | return hash.substring(1); 9 | } 10 | return hash; 11 | } 12 | return '/'; 13 | }; 14 | 15 | const state = { 16 | location: getHash(window.location.hash), 17 | users: [ 18 | {id: 1, name: 'Bob'}, 19 | {id: 2, name: 'Joe'}, 20 | ], 21 | pets: [ 22 | {id: 1, userId: 1, name: 'Tobi', species: 'Ferret'}, 23 | {id: 2, userId: 1, name: 'Loki', species: 'Ferret'}, 24 | {id: 3, userId: 1, name: 'Jane', species: 'Ferret'}, 25 | {id: 4, userId: 2, name: 'Manny', species: 'Cat'}, 26 | {id: 5, userId: 2, name: 'Luna', species: 'Cat'}, 27 | ], 28 | }; 29 | 30 | class App extends Component { 31 | constructor() { 32 | super(); 33 | this.state = state; 34 | } 35 | 36 | componentDidMount() { 37 | window.addEventListener('popstate', () => { 38 | this.setState({location: getHash(window.location.hash)}); 39 | }); 40 | } 41 | 42 | getChildContext() { 43 | return { 44 | navigate: path => { 45 | window.location.hash = path; 46 | this.setState({location: path}); 47 | }, 48 | }; 49 | } 50 | 51 | render() { 52 | return ( 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | } 63 | } 64 | 65 | render(, document.body); 66 | -------------------------------------------------------------------------------- /examples/hash/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | box-sizing: border-box; 4 | } 5 | 6 | html, body { 7 | margin: 0; 8 | padding: 50px; 9 | font: 15px/1.6 Helvetica, Arial, sans-serif; 10 | } 11 | 12 | 13 | h1 { 14 | font-size: 1.8rem; 15 | font-weight: 300; 16 | } 17 | 18 | h2 { 19 | font-size: 1.5rem; 20 | font-weight: 300; 21 | } 22 | -------------------------------------------------------------------------------- /examples/pushState/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/pushState/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component, render} from 'preact'; 2 | import {Router, Route} from '../../index'; 3 | import {Index, Pet, Pets, User, Users, NotFound} from '../generic'; 4 | 5 | const state = { 6 | location: window.location.pathname, 7 | users: [ 8 | {id: 1, name: 'Bob'}, 9 | {id: 2, name: 'Joe'}, 10 | ], 11 | pets: [ 12 | {id: 1, userId: 1, name: 'Tobi', species: 'Ferret'}, 13 | {id: 2, userId: 1, name: 'Loki', species: 'Ferret'}, 14 | {id: 3, userId: 1, name: 'Jane', species: 'Ferret'}, 15 | {id: 4, userId: 2, name: 'Manny', species: 'Cat'}, 16 | {id: 5, userId: 2, name: 'Luna', species: 'Cat'}, 17 | ], 18 | }; 19 | 20 | class App extends Component { 21 | constructor() { 22 | super(); 23 | this.state = state; 24 | } 25 | 26 | componentDidMount() { 27 | window.addEventListener('popstate', () => { 28 | this.setState({location: window.location.pathname}); 29 | }); 30 | } 31 | 32 | getChildContext() { 33 | return { 34 | navigate: path => { 35 | history.pushState(null, '', path); 36 | this.setState({location: path}); 37 | }, 38 | }; 39 | } 40 | 41 | render() { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | render(, document.body); 56 | -------------------------------------------------------------------------------- /examples/pushState/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | box-sizing: border-box; 4 | } 5 | 6 | html, body { 7 | margin: 0; 8 | padding: 50px; 9 | font: 15px/1.6 Helvetica, Arial, sans-serif; 10 | } 11 | 12 | 13 | h1 { 14 | font-size: 1.8rem; 15 | font-weight: 300; 16 | } 17 | 18 | h2 { 19 | font-size: 1.5rem; 20 | font-weight: 300; 21 | } 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import enroute from 'enroute'; 3 | 4 | function assert(e, msg) { 5 | if (!e) { 6 | throw new Error(`preact-enroute: ${msg}`); 7 | } 8 | } 9 | 10 | /** 11 | * Router routes things. 12 | */ 13 | 14 | export class Router extends Component { 15 | /** 16 | * Initialize the router. 17 | */ 18 | 19 | constructor(props) { 20 | super(props); 21 | this.routes = {}; 22 | this.addRoutes(props.children); 23 | this.router = enroute(this.routes); 24 | } 25 | 26 | /** 27 | * Add routes. 28 | */ 29 | 30 | addRoutes(routes, parent) { 31 | routes.forEach(r => this.addRoute(r, parent)); 32 | } 33 | 34 | /** 35 | * Add route. 36 | */ 37 | 38 | addRoute(el, parent) { 39 | const {location, ...props} = this.props; 40 | const {path, component} = el.attributes; 41 | const children = el.children; 42 | 43 | assert(typeof path === 'string', `Route ${context(el.attributes)}is missing the "path" property`); 44 | assert(component, `Route ${context(el.attributes)}is missing the "component" property`); 45 | 46 | function render(params, renderProps) { 47 | const finalProps = {...props, ...renderProps, location, params}; 48 | const children = h(component, finalProps); 49 | return parent ? parent.render(params, {children}) : children; 50 | } 51 | 52 | const route = normalizeRoute(path, parent); 53 | if (children) { 54 | this.addRoutes(children, {route, render}); 55 | } 56 | 57 | this.routes[cleanPath(route)] = render; 58 | } 59 | 60 | /** 61 | * Render the matching route. 62 | */ 63 | 64 | render() { 65 | const {location} = this.props; 66 | assert(location, `Router "location" property is missing`); 67 | return this.router(location, {children: null}); 68 | } 69 | } 70 | 71 | /** 72 | * Route does absolutely nothing :). 73 | */ 74 | 75 | export function Route() { 76 | assert(false, 'Route should not be rendered'); 77 | } 78 | 79 | /** 80 | * Context string for route errors based on the props available. 81 | */ 82 | 83 | function context({path, component}) { 84 | if (path) { 85 | return `with path "${path}" `; 86 | } 87 | if (component) { 88 | return `with component ${component.name} `; 89 | } 90 | return ''; 91 | } 92 | 93 | /** 94 | * Normalize route based on the parent. 95 | */ 96 | 97 | function normalizeRoute(path, parent) { 98 | if (path[0] === '/' || path[0] === '') { 99 | return path; // absolute route 100 | } 101 | if (!parent) { 102 | return path; // no need for a join 103 | } 104 | return `${parent.route}/${path}`; // join 105 | } 106 | 107 | /** 108 | * Clean path by stripping subsequent "//"'s. Without this 109 | * the user must be careful when to use "/" or not, which leads 110 | * to bad UX. 111 | */ 112 | 113 | function cleanPath(path) { 114 | return path.replace(/\/\//g, '/'); 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-enroute", 3 | "version": "1.2.3", 4 | "repository": "adriantoine/preact-enroute", 5 | "description": "Small preact router (preact port of tj/react-enroute)", 6 | "author": "Adrien Antoine ", 7 | "main": "build/index.js", 8 | "jsnext:main": "build/es.js", 9 | "browser:main": "build/browser.js", 10 | "license": "MIT", 11 | "scripts": { 12 | "test": "xo && NODE_ENV=server nyc --cache --reporter=text babel-node test/index.js", 13 | "prepublish": "rollup -c", 14 | "example-hash": "rollup -c rollup.config.example.js --input examples/hash/index.js --output examples/hash/bundle.js | http-server examples/hash", 15 | "example-pushstate": "rollup -c rollup.config.example.js --input examples/pushState/index.js --output examples/pushState/bundle.js | http-server examples/pushState" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "preact", 20 | "redux", 21 | "history", 22 | "router", 23 | "enroute", 24 | "small" 25 | ], 26 | "xo": { 27 | "esnext": true, 28 | "space": true, 29 | "extends": "xo-preact", 30 | "ignores": [ 31 | "build/**" 32 | ], 33 | "env": [ 34 | "browser" 35 | ], 36 | "rules": { 37 | "comma-dangle": [ 38 | 2, 39 | "always-multiline" 40 | ], 41 | "react/jsx-filename-extension": 0 42 | } 43 | }, 44 | "devDependencies": { 45 | "babel-cli": "^6.18.0", 46 | "babel-plugin-transform-react-jsx": "^6.8.0", 47 | "babel-preset-es2015": "^6.18.0", 48 | "babel-preset-react": "^6.16.0", 49 | "babel-preset-stage-0": "^6.16.0", 50 | "codecov": "^1.0.1", 51 | "eslint-config-xo-preact": "^1.0.0", 52 | "eslint-config-xo-react": "^0.10.0", 53 | "eslint-plugin-react": "^6.8.0", 54 | "http-server": "^0.9.0", 55 | "nyc": "^10.0.0", 56 | "preact": "^7.1.0", 57 | "preact-assert-equal-jsx": "^1.0.0", 58 | "rollup": "^0.38.2", 59 | "rollup-plugin-babel": "^2.7.1", 60 | "rollup-plugin-commonjs": "^6.0.1", 61 | "rollup-plugin-node-resolve": "^2.0.0", 62 | "rollup-watch": "^2.5.0", 63 | "xo": "^0.17.1" 64 | }, 65 | "dependencies": { 66 | "enroute": "^1.0.1" 67 | }, 68 | "files": [ 69 | "build" 70 | ], 71 | "peerDependencies": { 72 | "preact": "^7.0.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /rollup.config.browser.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import config from './rollup.config'; 4 | 5 | const pkg = require('./package.json'); 6 | 7 | config.plugins.push(commonjs({ 8 | include: 'node_modules/**', 9 | })); 10 | 11 | export default { 12 | entry: path.resolve(__dirname, 'index.js'), 13 | dest: path.resolve(__dirname, pkg['browser:main']), 14 | format: 'iife', 15 | moduleName: 'PreactEnroute', 16 | sourceMap: true, 17 | plugins: config.plugins, 18 | external: ['preact'], 19 | }; 20 | -------------------------------------------------------------------------------- /rollup.config.example.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import config from './rollup.config'; 3 | 4 | config.plugins.push(commonjs({ 5 | include: 'node_modules/**', 6 | })); 7 | 8 | export default { 9 | format: 'iife', 10 | sourceMap: true, 11 | plugins: config.plugins, 12 | external: ['preact'], 13 | }; 14 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import babel from 'rollup-plugin-babel'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | 5 | const pkg = require('./package.json'); 6 | 7 | const external = Object.keys(pkg.dependencies).concat(Object.keys(pkg.peerDependencies)); 8 | 9 | export default { 10 | entry: path.resolve(__dirname, 'index.js'), 11 | plugins: [ 12 | babel(), 13 | nodeResolve({ 14 | jsnext: true, 15 | main: true, 16 | }), 17 | ], 18 | external, 19 | targets: [ 20 | { 21 | dest: path.resolve(__dirname, pkg.main), 22 | format: 'cjs', 23 | sourceMap: true, 24 | }, 25 | { 26 | dest: path.resolve(__dirname, pkg['jsnext:main']), 27 | format: 'es', 28 | sourceMap: true, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* @jsx h */ 2 | import {h} from 'preact'; 3 | import {Router, Route} from '..'; 4 | import assert from 'preact-assert-equal-jsx'; 5 | 6 | function Index({children}) { 7 | return ( 8 |
9 |

Index

10 | {children} 11 |
12 | ); 13 | } 14 | 15 | function Users({children}) { 16 | return ( 17 |
18 |

Users

19 | {children} 20 |
21 | ); 22 | } 23 | 24 | function User({children, params: {userId}}) { 25 | return ( 26 |
27 |

User {userId}

28 | {children} 29 |
30 | ); 31 | } 32 | 33 | function Pets({children}) { 34 | return ( 35 |
36 |

Pets

37 | {children} 38 |
39 | ); 40 | } 41 | 42 | function Pet({params: {petId}}) { 43 | return
pet {petId}
; 44 | } 45 | 46 | function NotFound() { 47 | return
Not Found
; 48 | } 49 | 50 | function List({items}) { 51 | return
    {items.map((item, i) =>
  • {item}
  • )}
; 52 | } 53 | 54 | // Simple index route 55 | assert( 56 | 57 | 58 | , 59 | 60 | ); 61 | 62 | // Props 63 | assert( 64 | 65 | 66 | 67 | 68 | , 69 | 70 | 71 | 72 | ); 73 | 74 | // Nested route 75 | assert( 76 | 77 | 78 | 79 | 80 | , 81 | 82 | ); 83 | 84 | // Deeply nested route 85 | assert( 86 | 87 | 88 | 89 | 90 | 91 | 92 | , 93 | 94 | ); 95 | 96 | // Many deeply nested route 97 | assert( 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | , 109 | 110 | ); 111 | 112 | // Catch-all 113 | assert( 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | , 127 | 128 | ); 129 | 130 | // Nested route but index route 131 | assert( 132 | 133 | 134 | 135 | 136 | , 137 | 138 | ); 139 | 140 | // Shallow routes 141 | assert( 142 | 143 | 144 | 145 | , 146 | 147 | ); 148 | 149 | assert( 150 | 151 | 152 | 153 | , 154 | 155 | ); 156 | 157 | assert( 158 | 159 | 160 | 161 | 162 | , 163 | 164 | ); 165 | 166 | // Many nested routes and params 167 | assert( 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | , 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | ); 189 | --------------------------------------------------------------------------------