├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── karma.conf.js
├── package.json
├── rollup.config.js
├── src
└── index.js
└── test
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"loose":true}],
4 | "stage-0"
5 | ],
6 | "plugins": [
7 | ["transform-react-jsx", { "pragma":"h" }]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "eslint:recommended",
4 | "plugins": [
5 | "react"
6 | ],
7 | "env": {
8 | "browser": true,
9 | "mocha": true,
10 | "es6": true,
11 | "node": true
12 | },
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "modules": true,
16 | "jsx": true
17 | }
18 | },
19 | "globals": {
20 | "sinon": true,
21 | "expect": true
22 | },
23 | "rules": {
24 | "react/jsx-uses-react": 2,
25 | "react/jsx-uses-vars": 2,
26 | "no-unused-vars": [1, { "varsIgnorePattern": "^h$" }],
27 | "no-cond-assign": 1,
28 | "no-empty": 0,
29 | "no-console": 1,
30 | "semi": 2,
31 | "camelcase": 0,
32 | "comma-style": 2,
33 | "comma-dangle": [2, "never"],
34 | "indent": [2, "tab", {"SwitchCase": 1}],
35 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
36 | "no-trailing-spaces": [2, { "skipBlankLines": true }],
37 | "max-nested-callbacks": [2, 5],
38 | "no-eval": 2,
39 | "no-implied-eval": 2,
40 | "no-new-func": 2,
41 | "guard-for-in": 2,
42 | "eqeqeq": 0,
43 | "no-else-return": 2,
44 | "no-redeclare": 2,
45 | "no-dupe-keys": 2,
46 | "radix": 2,
47 | "strict": [2, "never"],
48 | "no-shadow": 0,
49 | "callback-return": [1, ["callback", "cb", "next", "done"]],
50 | "no-delete-var": 2,
51 | "no-undef-init": 2,
52 | "no-shadow-restricted-names": 2,
53 | "handle-callback-err": 0,
54 | "no-lonely-if": 2,
55 | "keyword-spacing": 2,
56 | "constructor-super": 2,
57 | "no-this-before-super": 2,
58 | "no-dupe-class-members": 2,
59 | "no-const-assign": 2,
60 | "prefer-spread": 2,
61 | "no-useless-concat": 2,
62 | "no-var": 2,
63 | "object-shorthand": 2,
64 | "prefer-arrow-callback": 2
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /node_modules
3 | /npm-debug.log
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jason Miller
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # preact-views
2 |
3 | [](https://www.npmjs.com/package/preact-views)
4 | [](https://travis-ci.org/developit/preact-views)
5 |
6 | **Named views for [Preact], with easy-as-pie linking between them!**
7 |
8 | `preact-views` provides a `` component that renders its children only when their `name` prop is selected as the current "view". The current view name can be set via a prop, or automatically through the provided `` component.
9 |
10 | > **Note:** `preact-views` is simple and does not do orchestration or routing for you. If you're looking for a URL router, try [preact-router](https://github.com/developit/preact-router).
11 |
12 | #### [See a Real-world Example :arrow_right:](https://jsfiddle.net/developit/jz95kc33/)
13 |
14 | ---
15 |
16 |
17 | ### Complete Example
18 |
19 | ```js
20 | import { Views, Link } from 'preact-views';
21 | import { h, render } from 'preact';
22 |
23 | const Home = () => (
24 |
25 |
Home!
26 | Go Other
27 |
28 | );
29 |
30 | const Other = ({ value=0 }) => (
31 |
32 |
Other.
33 |
Go Home
34 |
value is {value}.
35 |
Increment
36 |
37 | );
38 |
39 | render((
40 |
41 |
42 |
43 |
44 | ), document.body);
45 | ```
46 |
47 | [**See it running :arrow_right:**](https://jsfiddle.net/developit/jz95kc33/)
48 |
49 |
50 | ---
51 |
52 |
53 | ### Simple Example
54 |
55 | ```js
56 | import Views from 'preact-views';
57 | import { h, render } from 'preact';
58 |
59 | render((
60 |
61 | one
62 | two
63 |
64 | ), document.body);
65 |
66 | // renders a div containing the text "one"
67 | ```
68 |
69 |
70 | ---
71 |
72 |
73 | ### License
74 |
75 | [MIT]
76 |
77 |
78 | [Preact]: https://github.com/developit/preact
79 | [MIT]: http://choosealicense.com/licenses/mit/
80 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | config.set({
3 | frameworks: ['mocha', 'chai-sinon'],
4 | reporters: ['mocha'],
5 | browsers: ['PhantomJS'],
6 |
7 | files: ['test/**/*.js'],
8 |
9 | preprocessors: {
10 | '{src,test}/**/*.js': ['webpack', 'sourcemap']
11 | },
12 |
13 | webpack: {
14 | module: {
15 | loaders: [{
16 | test: /\.jsx?$/,
17 | exclude: /node_modules/,
18 | loader: 'babel'
19 | }]
20 | },
21 | resolve: {
22 | alias: {
23 | src: __dirname+'/src'
24 | }
25 | }
26 | },
27 |
28 | webpackMiddleware: {
29 | noInfo: true
30 | }
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preact-views",
3 | "amdName": "preactViews",
4 | "version": "1.0.0",
5 | "description": "Connect your components up to that address bar.",
6 | "main": "dist/preact-views.js",
7 | "jsnext:main": "src/index.js",
8 | "minified:main": "dist/preact-views.min.js",
9 | "module": "src/index.js",
10 | "scripts": {
11 | "clean": "rimraf dist/",
12 | "build": "npm-run-all clean transpile optimize minify size",
13 | "transpile": "rollup -c rollup.config.js -m ${npm_package_main}.map -f umd -n $npm_package_amdName $npm_package_jsnext_main -o $npm_package_main",
14 | "optimize": "uglifyjs $npm_package_main -bc dead_code --pure-funcs _possibleConstructorReturn _classCallCheck -o $npm_package_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_main}.map",
15 | "minify": "uglifyjs $npm_package_main -mc collapse_vars,evaluate,screw_ie8,unsafe,loops=false,keep_fargs=false,pure_getters,unused,dead_code --pure-funcs _possibleConstructorReturn _classCallCheck -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map",
16 | "size": "echo \"gzip size: $(gzip-size $npm_package_minified_main --raw) bytes\"",
17 | "test": "eslint src test && karma start --single-run",
18 | "prepublish": "npm run -s build && npm test",
19 | "release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
20 | },
21 | "files": [
22 | "src",
23 | "dist"
24 | ],
25 | "keywords": [
26 | "preact",
27 | "views"
28 | ],
29 | "author": "Jason Miller ",
30 | "license": "MIT",
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/developit/preact-views.git"
34 | },
35 | "bugs": {
36 | "url": "https://github.com/developit/preact-views/issues"
37 | },
38 | "homepage": "https://github.com/developit/preact-views",
39 | "peerDependencies": {
40 | "preact": "*"
41 | },
42 | "devDependencies": {
43 | "babel-cli": "^6.9.0",
44 | "babel-core": "^6.9.1",
45 | "babel-eslint": "^7.0.0",
46 | "babel-loader": "^6.2.4",
47 | "babel-plugin-transform-class-properties": "^6.9.1",
48 | "babel-plugin-transform-es2015-classes": "^6.9.0",
49 | "babel-plugin-transform-object-assign": "^6.0.0",
50 | "babel-plugin-transform-react-jsx": "^6.8.0",
51 | "babel-preset-es2015": "^6.9.0",
52 | "babel-preset-react": "^6.5.0",
53 | "babel-preset-stage-0": "^6.5.0",
54 | "chai": "^3.5.0",
55 | "copyfiles": "^1.0.0",
56 | "diff": "^3.0.0",
57 | "eslint": "^3.0.0",
58 | "eslint-plugin-react": "^6.0.0",
59 | "gzip-size-cli": "^2.0.0",
60 | "karma": "^1.0.0",
61 | "karma-chai-sinon": "^0.1.5",
62 | "karma-mocha": "^1.0.1",
63 | "karma-mocha-reporter": "^2.0.3",
64 | "karma-phantomjs-launcher": "^1.0.0",
65 | "karma-sourcemap-loader": "^0.3.7",
66 | "karma-webpack": "^2.0.1",
67 | "mkdirp": "^0.5.1",
68 | "mocha": "^3.0.0",
69 | "npm-run-all": "^4.0.1",
70 | "phantomjs-prebuilt": "^2.1.7",
71 | "preact": "^7.2.0",
72 | "pretty-bytes-cli": "^1.0.0",
73 | "rimraf": "^2.5.1",
74 | "rollup": "^0.41.4",
75 | "rollup-plugin-babel": "^2.4.0",
76 | "rollup-plugin-es3": "^1.0.3",
77 | "rollup-plugin-memory": "^2.0.0",
78 | "rollup-plugin-post-replace": "^1.0.0",
79 | "sinon": "^1.17.4",
80 | "sinon-chai": "^2.8.0",
81 | "uglify-js": "^2.6.1",
82 | "webpack": "^1.13.1"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import es3 from 'rollup-plugin-es3';
3 | import replace from 'rollup-plugin-post-replace';
4 | import babel from 'rollup-plugin-babel';
5 | import memory from 'rollup-plugin-memory';
6 |
7 | var babelRc = JSON.parse(fs.readFileSync('.babelrc','utf8')); // eslint-disable-line
8 |
9 | export default {
10 | exports: 'default',
11 | useStrict: false,
12 | globals: {
13 | preact: 'preact'
14 | },
15 | plugins: [
16 | memory({
17 | path: 'src/index',
18 | contents: "export { default } from './index';"
19 | }),
20 |
21 | babel({
22 | babelrc: false,
23 | presets: [
24 | ['es2015', { loose:true, modules:false }]
25 | ].concat(babelRc.presets.slice(1)),
26 | plugins: babelRc.plugins,
27 | exclude: 'node_modules/**'
28 | }),
29 |
30 | // strip Object.freeze()
31 | es3(),
32 |
33 | // remove Babel helpers
34 | replace({
35 | 'throw ': 'return; throw '
36 | })
37 | ]
38 | };
39 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { h, cloneElement, Component } from 'preact';
2 |
3 | /** @example
4 | *
5 | *
6 | *
7 | *
8 | */
9 | export class Views extends Component {
10 | state = {
11 | // can pass default view in as a prop
12 | view: this.props.view,
13 | params: {}
14 | };
15 |
16 | // show a given view, optionally with params (props)
17 | route = (view, params) => {
18 | params = params || {};
19 | this.setState({ view, params });
20 | };
21 |
22 | // if re-rendered with a new "view" prop, set as active view
23 | componentWillReceiveProps({ view }) {
24 | if (view!==this.props.view) this.route(view);
25 | }
26 |
27 | // Expose route() into context so child components can invoke it:
28 | // this.context.route('some-view');
29 | getChildContext() {
30 | return { route: this.route };
31 | }
32 |
33 | // Render the child whose `name` prop matches the current view
34 | render({ children }, { view, params }) {
35 | let child = children.filter( child => child.attributes.name===view )[0];
36 | return child ? cloneElement(child, params) : null;
37 | }
38 | }
39 |
40 |
41 | /** Renders an `` that, when clicked, directs to the view indicated by a `to` prop.
42 | * @param {Object} props
43 | * @param {String} props.to The name of a view to switch to
44 | * @param {Object} [props.params={}] Props to pass to the view
45 | * @example
46 | * Home
47 | * Foo
48 | */
49 | export function Link({ to, params, ...props }, context) {
50 | if (!props.onClick) props.onClick = () => context.route(to, params);
51 | return h('a', props);
52 | }
53 |
54 |
55 | // commonjs is the real king
56 | Views.Views = Views;
57 | Views.Link = Link;
58 | export default Views;
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import { Views, Link } from 'src';
2 | import { h, render, options } from 'preact';
3 |
4 | // sync is easier for simple tests
5 | options.debounceRendering = f => f();
6 |
7 | describe('preact-views', () => {
8 | let scratch = document.createElement('div'),
9 | $ = s => scratch.querySelector(s),
10 | mount = jsx => root = render(jsx, scratch, root),
11 | root;
12 |
13 | afterEach( () => {
14 | mount();
15 | scratch.innerHTML = '';
16 | });
17 |
18 | describe('', () => {
19 | it('should render initial view', () => {
20 | mount(
21 |
22 |
23 |
24 |
25 | );
26 |
27 | expect(scratch.innerHTML).to.equal('');
28 | });
29 |
30 | describe('switching', () => {
31 | const Home = () => (
32 |
33 |
Home!
34 | Go Other
35 |
36 | );
37 |
38 | const Other = ({ value=0 }) => (
39 |
40 |
Other.
41 |
Go Home
42 |
value is {value}.
43 |
Increment
44 |
45 | );
46 |
47 | it('should render when view prop changes', () => {
48 | mount(
49 |
50 |
51 |
52 |
53 | );
54 |
55 | expect(scratch.innerHTML).to.equal(``);
56 |
57 | mount(
58 |
59 |
60 |
61 |
62 | );
63 |
64 | expect(scratch.innerHTML).to.equal(``);
65 | });
66 |
67 | it('should render with props when clicking s', () => {
68 | mount(
69 |
70 |
71 |
72 |
73 | );
74 |
75 | $('a:last-child').click();
76 | $('a:last-child').click();
77 | $('a:last-child').click();
78 |
79 | expect(scratch.innerHTML).to.equal(``);
80 |
81 | $('a').click();
82 |
83 | expect(scratch.innerHTML).to.equal(``);
84 | });
85 | });
86 | });
87 | });
88 |
--------------------------------------------------------------------------------