├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── examples ├── preact-playground │ ├── README.md │ ├── package.json │ └── src │ │ ├── config.js │ │ ├── files │ │ ├── .gitignore │ │ ├── assets │ │ │ ├── favicon.ico │ │ │ └── icons │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ └── mstile-150x150.png │ │ └── manifest.json │ │ ├── index.js │ │ ├── package.json │ │ ├── proppy-redux │ │ ├── actions │ │ │ └── counter.js │ │ ├── components │ │ │ └── PlayWithStore.js │ │ ├── constants │ │ │ └── index.js │ │ ├── reducers │ │ │ ├── counter.js │ │ │ └── index.js │ │ └── store │ │ │ └── index.js │ │ ├── proppy-rx │ │ └── PlayWithStream.js │ │ ├── proppy │ │ ├── PlayCompose.js │ │ ├── PlayDidSubscribe.js │ │ ├── PlayEmit.js │ │ ├── PlayMap.js │ │ ├── PlayOnChange.js │ │ ├── PlayShouldUpdate.js │ │ ├── PlayWithHandlers.js │ │ ├── PlayWithObservable.js │ │ ├── PlayWithProps.js │ │ ├── PlayWithReducer.js │ │ ├── PlayWithState.js │ │ ├── PlayWithStateHandlers.js │ │ └── PlayWithTimer.js │ │ └── style │ │ └── index.scss └── react-playground │ ├── README.md │ ├── package.json │ ├── public │ └── index.html │ └── src │ ├── config.js │ ├── index.js │ ├── proppy-redux │ ├── actions │ │ └── counter.js │ ├── components │ │ └── PlayWithStore.js │ ├── constants │ │ └── index.js │ ├── reducers │ │ ├── counter.js │ │ └── index.js │ └── store │ │ └── index.js │ ├── proppy-rx │ └── PlayWithStream.js │ └── proppy │ ├── PlayCompose.js │ ├── PlayDidSubscribe.js │ ├── PlayEmit.js │ ├── PlayMap.js │ ├── PlayOnChange.js │ ├── PlayShouldUpdate.js │ ├── PlayWithHandlers.js │ ├── PlayWithObservable.js │ ├── PlayWithProps.js │ ├── PlayWithReducer.js │ ├── PlayWithState.js │ ├── PlayWithStateHandlers.js │ └── PlayWithTimer.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── proppy-frint-react │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── ProppyProvider.tsx │ │ ├── index.spec.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── proppy-preact │ ├── .gitignore │ ├── .npmignore │ ├── ProppyProvider.js │ ├── README.md │ ├── attach.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── ProppyContext.ts │ │ ├── ProppyProvider.tsx │ │ ├── ProppySubscription.tsx │ │ ├── attach.spec.tsx │ │ ├── attach.tsx │ │ ├── index.spec.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── proppy-react │ ├── .gitignore │ ├── .npmignore │ ├── ProppyProvider.js │ ├── README.md │ ├── attach.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── ProppyContext.ts │ │ ├── ProppyProvider.tsx │ │ ├── ProppySubscription.tsx │ │ ├── attach.spec.tsx │ │ ├── attach.tsx │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── proppy-redux │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── withStore.spec.ts │ │ └── withStore.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── withStore.js ├── proppy-rx │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── from.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── from.spec.ts │ │ ├── from.ts │ │ ├── index.ts │ │ ├── withStream.spec.ts │ │ └── withStream.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── withStream.js ├── proppy-vue │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── attach.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── attach.spec.ts │ │ ├── attach.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js └── proppy │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── compose.js │ ├── create.js │ ├── didSubscribe.js │ ├── emit.js │ ├── handleReceivedProps.js │ ├── jest.config.js │ ├── map.js │ ├── onChange.js │ ├── package.json │ ├── shouldUpdate.js │ ├── src │ ├── compose.spec.ts │ ├── compose.ts │ ├── create.ts │ ├── didSubscribe.spec.ts │ ├── didSubscribe.ts │ ├── emit.spec.ts │ ├── emit.ts │ ├── handleReceivedProps.spec.ts │ ├── handleReceivedProps.ts │ ├── index.ts │ ├── map.spec.ts │ ├── map.ts │ ├── onChange.spec.ts │ ├── onChange.ts │ ├── shouldUpdate.spec.ts │ ├── shouldUpdate.ts │ ├── types.ts │ ├── willDestroy.spec.ts │ ├── willDestroy.ts │ ├── withHandlers.spec.ts │ ├── withHandlers.ts │ ├── withObservable.spec.ts │ ├── withObservable.ts │ ├── withProps.spec.ts │ ├── withProps.ts │ ├── withReducer.spec.ts │ ├── withReducer.ts │ ├── withState.spec.ts │ ├── withState.ts │ ├── withStateHandlers.spec.ts │ ├── withStateHandlers.ts │ ├── withTimer.spec.ts │ └── withTimer.ts │ ├── tsconfig.json │ ├── webpack.config.js │ ├── willDestroy.js │ ├── withHandlers.js │ ├── withObservable.js │ ├── withProps.js │ ├── withReducer.js │ ├── withState.js │ ├── withStateHandlers.js │ └── withTimer.js ├── site ├── assets │ ├── css │ │ ├── content.scss │ │ ├── flow.scss │ │ ├── footer.scss │ │ ├── global.scss │ │ ├── header.scss │ │ └── main.scss │ ├── img │ │ ├── banner-trimmed-900.png │ │ ├── banner-trimmed.png │ │ ├── banner.png │ │ ├── js-logo.png │ │ ├── logo-text-80.png │ │ ├── logo-text.png │ │ ├── logo-transparent-128.png │ │ ├── logo-transparent.png │ │ ├── preact-logo.png │ │ ├── proppy-flow.gif │ │ ├── proppy-og.png │ │ ├── proppy-square-128.png │ │ ├── proppy-square.png │ │ ├── react-logo.svg │ │ ├── redux-logo.png │ │ ├── rxjs-logo.png │ │ └── vue-logo.png │ └── js │ │ └── site.js ├── bin │ └── build.js ├── content │ ├── README.md │ └── docs │ │ ├── README.md │ │ ├── api.md │ │ ├── bundling.md │ │ ├── changelog.md │ │ ├── contributing.md │ │ ├── examples │ │ ├── react-counter.md │ │ ├── react-fetch-request.md │ │ ├── react-redux.md │ │ ├── react-rxjs.md │ │ └── vue-counter.md │ │ ├── factory-instance.md │ │ ├── faq.md │ │ ├── introduction.md │ │ ├── lifecycle.md │ │ ├── packages │ │ ├── proppy-preact.md │ │ ├── proppy-react.md │ │ ├── proppy-redux.md │ │ ├── proppy-rx.md │ │ ├── proppy-vue.md │ │ └── proppy.md │ │ ├── playground.md │ │ ├── props.md │ │ ├── providers.md │ │ ├── quickstart.md │ │ └── testing.md ├── data │ ├── defaults.json │ └── sidebarLinks.json ├── layouts │ ├── default.html │ └── home.html └── partials │ ├── assets_body.html │ ├── assets_head.html │ ├── docsSidebar.html │ ├── footer.html │ └── navLinks.html └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [Makefile] 14 | indent_style = tab 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dist 4 | coverage 5 | npm-debug.log 6 | lerna-debug.log 7 | yarn.lock 8 | _site 9 | .changelog 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 'lts/*' 5 | 6 | script: 7 | - 'npm run bootstrap' 8 | - 'set -e' 9 | - 'npm run lint' 10 | - 'npm run test' 11 | 12 | after_success: 13 | - 'npm run cover' 14 | 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | proppyjs.com 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at proppyjs-conduct@googlegroups.com 59 | All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. 60 | Further details of specific enforcement policies may be posted separately. 61 | 62 | Project maintainers who do not follow or enforce the Code of Conduct in good 63 | faith may face temporary or permanent repercussions as determined by other 64 | members of the project's leadership. 65 | 66 | ## Attribution 67 | 68 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 69 | available at [http://contributor-covenant.org/version/1/4][version] 70 | 71 | [homepage]: http://contributor-covenant.org 72 | [version]: http://contributor-covenant.org/version/1/4/ 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ProppyJS 2 | 3 | 4 | 5 | - [Code of Conduct](#code-of-conduct) 6 | - [Open Development](#open-development) 7 | - [Bugs and Issues](#bugs-and-issues) 8 | - [Proposals](#proposals) 9 | - [Sending a pull request](#sending-a-pull-request) 10 | - [Get in Touch](#get-in-touch) 11 | - [License](#license) 12 | 13 | 14 | 15 | ## Code of Conduct 16 | 17 | We have adopted a Code of Conduct, that we expect all the participants of our project to adhere to. You can find the full text [here](https://github.com/fahad19/proppy/blob/master/CODE_OF_CONDUCT.md). 18 | 19 | ## Open Development 20 | 21 | Development of the project is done publicly on [GitHub](https://github.com/fahad19/proppy). Core team members and also other external contributors send Pull Requests and they all go through the same review process. 22 | 23 | ## Bugs and Issues 24 | 25 | Bugs can be reported as [GitHub Issues](https://github.com/fahad19/proppy/issues). 26 | 27 | Before submitting any issue, please make sure they are not a duplicate of any other [existing issues](https://github.com/fahad19/proppy/issues). 28 | 29 | ## Proposals 30 | 31 | For every new feature, package or a change, we post [proposals](https://github.com/fahad19/proppy/issues?q=is%3Aissue+is%3Aopen+label%3Aproposal) under GitHub Issues publicly. 32 | 33 | Members from the project and other interested parties can then comment on the proposal, discussing about the strengths and weaknesses, and the proposal gets refined/updated based on feedback. 34 | 35 | Once the spec is ready and finalized, the development begins. 36 | 37 | In your Proposal description, make sure you answer these questions: 38 | 39 | * What is the current situation? 40 | * What is the proposed change? 41 | 42 | ## Sending a pull request 43 | 44 | If you are looking for ways to contribute with code or documentation to the project directly, you can help us out by contributing to issues with [`help wanted`](https://github.com/fahad19/proppy/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) label on GitHub. 45 | 46 | We already have automated checks for tests and linting for all Pull Requests, but you can always run these commands locally to make sure everything is working as expected: 47 | 48 | ``` 49 | $ npm install 50 | 51 | $ npm run bootstrap 52 | $ npm run lint 53 | $ npm test 54 | ``` 55 | 56 | If your Pull Request results in a change in the API, then please update the [examples](https://github.com/fahad19/proppy/tree/master/examples) in the same repository accordingly, as well as the API documentation (`README.md` of every package). 57 | 58 | ## Get in Touch 59 | 60 | * [Twitter](https://twitter.com/fahad19) 61 | * [GitHub](https://github.com/fahad19/proppy) 62 | 63 | ## License 64 | 65 | By contributing to ProppyJS, you agree that your contributions will be licensed under its MIT license. 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present Fahad Ibnay Heylaal 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FROM_TAG := "" 2 | TO_TAG := "" 3 | GITHUB_API_TOKEN := "" 4 | 5 | ## 6 | # Releases 7 | release: 8 | npm run dist 9 | ./node_modules/.bin/lerna publish --force-publish=* 10 | 11 | release-canary: 12 | npm run dist 13 | ./node_modules/.bin/lerna publish --canary 14 | 15 | ## 16 | # Changelog 17 | changelog: 18 | git checkout master 19 | git pull origin master 20 | GITHUB_AUTH=$(GITHUB_API_TOKEN) ./node_modules/.bin/lerna-changelog --tag-from $(FROM_TAG) --tag-to $(TO_TAG) 21 | 22 | push-changelog: 23 | git checkout master 24 | git pull origin master 25 | git add CHANGELOG.md 26 | git commit -m 'changelog updated.' 27 | git push origin master 28 | 29 | ## 30 | # Packages 31 | list-packages: 32 | ./node_modules/.bin/lerna ls 33 | 34 | list-updated: 35 | ./node_modules/.bin/lerna updated 36 | 37 | list-dists: 38 | @echo "bytes \\t kilobytes \\t file" 39 | @echo "--- \\t\\t --- \\t\\t ---" 40 | @ls -alh ./packages/proppy*/dist/*.min.js.gz | awk '{print $$9 }' | while read LINE; do\ 41 | SIZE="$$(cat $${LINE} | wc -c | bc)";\ 42 | SIZE_IN_KB=$$(echo "scale=1; $${SIZE} / 1024" | bc);\ 43 | echo "$${SIZE}K \\t\\t $${SIZE_IN_KB}K \\t\\t $${LINE}";\ 44 | done 45 | 46 | ## 47 | # Site 48 | site-build: 49 | node ./site/bin/build.js 50 | 51 | mkdir -p ./_site/css 52 | ./node_modules/.bin/node-sass --include-path ./node_modules ./site/assets/css/main.scss ./_site/css/site.css 53 | 54 | mkdir -p ./_site/js 55 | cp -r ./site/assets/js/ ./_site/js/ 56 | cp -rf ./site/assets/img ./_site/img 57 | 58 | site-watch: 59 | make site-build 60 | fswatch -or './site' | xargs -I{} make site-build 61 | 62 | site-serve-only: 63 | echo "Starting server at http://localhost:6001" 64 | ./node_modules/.bin/live-server --port=6001 ./_site/ 65 | 66 | site-serve: 67 | make site-build 68 | make site-serve-only 69 | 70 | site-publish: 71 | rm -rf ./_site 72 | make site-build 73 | make site-publish-only 74 | 75 | site-publish-only: 76 | rm -rf ./_site/.git 77 | 78 | cp -f CNAME ./_site/CNAME 79 | 80 | (cd ./_site && git init) 81 | (cd ./_site && git commit --allow-empty -m 'update site') 82 | (cd ./_site && git checkout -b gh-pages) 83 | (cd ./_site && touch .nojekyll) 84 | (cd ./_site && git add .) 85 | (cd ./_site && git commit -am 'update site') 86 | (cd ./_site && git push git@github.com:fahad19/proppy gh-pages --force) 87 | -------------------------------------------------------------------------------- /examples/preact-playground/README.md: -------------------------------------------------------------------------------- 1 | # Preact Playground 2 | 3 | Examples of ProppyJS using Preact. 4 | 5 | ## Demo 6 | 7 | You can access this example on [CodeSandbox](https://codesandbox.io/s/github/fahad19/proppy/tree/master/examples/preact-playground). 8 | 9 | ## Local development 10 | 11 | ``` 12 | $ npm install 13 | $ npm start 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/preact-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppyjs-preact-playground-wip", 3 | "description": null, 4 | "version": "0.0.16", 5 | "dependencies": { 6 | "history": "^4.7.0", 7 | "preact": "8.2.5", 8 | "preact-compat": "3.17.0", 9 | "proppy": "^1.2.1", 10 | "proppy-preact": "^1.0.0", 11 | "proppy-redux": "^1.0.0", 12 | "proppy-rx": "^1.0.0", 13 | "react-router": "^4.0.0", 14 | "react-router-dom": "^4.0.0", 15 | "redux": "^4.0.0", 16 | "rxjs": "^6.0.0" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^4.5.0", 20 | "eslint-config-synacor": "^1.1.0", 21 | "if-env": "^1.0.0", 22 | "less": "^2.7.2", 23 | "less-loader": "^4.0.5", 24 | "node-sass": "^4.5.3", 25 | "preact-cli": "^1.4.1", 26 | "sass-loader": "^6.0.6", 27 | "stylus": "^0.54.5", 28 | "stylus-loader": "^3.0.1" 29 | }, 30 | "scripts": { 31 | "test": "eslint . && preact test", 32 | "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev", 33 | "build": "preact build", 34 | "serve": "preact build && preact serve", 35 | "dev": "preact watch" 36 | }, 37 | "eslintConfig": { 38 | "extends": "eslint-config-synacor" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/preact-playground/src/config.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | // proppy 4 | import PlayWithState from "./proppy/PlayWithState"; 5 | import PlayWithProps from "./proppy/PlayWithProps"; 6 | import PlayCompose from "./proppy/PlayCompose"; 7 | import PlayWithReducer from "./proppy/PlayWithReducer"; 8 | import PlayWithHandlers from "./proppy/PlayWithHandlers"; 9 | import PlayWithStateHandlers from "./proppy/PlayWithStateHandlers"; 10 | import PlayWithObservable from "./proppy/PlayWithObservable"; 11 | import PlayWithTimer from "./proppy/PlayWithTimer"; 12 | import PlayEmit from "./proppy/PlayEmit"; 13 | import PlayOnChange from "./proppy/PlayOnChange"; 14 | import PlayMap from "./proppy/PlayMap"; 15 | import PlayShouldUpdate from "./proppy/PlayShouldUpdate"; 16 | import PlayDidSubscribe from "./proppy/PlayDidSubscribe"; 17 | 18 | // proppy-redux 19 | import PlayWithStore from "./proppy-redux/components/PlayWithStore"; 20 | 21 | // proppy-rx 22 | import PlayWithStream from "./proppy-rx/PlayWithStream"; 23 | 24 | export const routesConfig = [ 25 | { 26 | name: "proppy", 27 | routes: [ 28 | { path: "/withProps", render: () => }, 29 | { path: "/withState", render: () => }, 30 | { path: "/compose", render: () => }, 31 | { path: "/withReducer", render: () => }, 32 | { path: "/withHandlers", render: () => }, 33 | { path: "/withStateHandlers", render: () => }, 34 | { path: "/withObservable", render: () => }, 35 | { path: "/withTimer", render: () => }, 36 | { path: "/emit", render: () => }, 37 | { path: "/onChange", render: () => }, 38 | { path: "/map", render: () => }, 39 | { path: "/shouldUpdate", render: () => }, 40 | { path: "/didSubscribe", render: () => } 41 | ] 42 | }, 43 | { 44 | name: "proppy-redux", 45 | routes: [{ path: "/withStore", render: () => }] 46 | }, 47 | { 48 | name: "proppy-rx", 49 | routes: [ 50 | { path: "/withStream", render: props => } 51 | ] 52 | } 53 | ]; 54 | -------------------------------------------------------------------------------- /examples/preact-playground/src/files/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | gh-pages 3 | 4 | # Esdoc dirs 5 | out 6 | gh-pages 7 | 8 | # Docker 9 | *.dockerfile 10 | *.dockerignore 11 | 12 | # NPM config 13 | .npmrc 14 | 15 | # Avoid lock files in libraries 16 | yarn.lock 17 | package-lock.json 18 | 19 | # Created by https://www.gitignore.io/api/node,windows,osx,linux,vim 20 | 21 | ### Linux ### 22 | *~ 23 | 24 | # temporary files which can be created if a process still has a handle open of a deleted file 25 | .fuse_hidden* 26 | 27 | # KDE directory preferences 28 | .directory 29 | 30 | # Linux trash folder which might appear on any partition or disk 31 | .Trash-* 32 | 33 | # .nfs files are created when an open file is removed but is still being accessed 34 | .nfs* 35 | 36 | ### Node ### 37 | # Logs 38 | logs 39 | *.log 40 | npm-debug.log* 41 | yarn-debug.log* 42 | yarn-error.log* 43 | 44 | # Runtime data 45 | pids 46 | *.pid 47 | *.seed 48 | *.pid.lock 49 | 50 | # Directory for instrumented libs generated by jscoverage/JSCover 51 | lib-cov 52 | 53 | # Coverage directory used by tools like istanbul 54 | coverage 55 | 56 | # nyc test coverage 57 | .nyc_output 58 | 59 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 60 | .grunt 61 | 62 | # Bower dependency directory (https://bower.io/) 63 | bower_components 64 | 65 | # node-waf configuration 66 | .lock-wscript 67 | 68 | # Compiled binary addons (http://nodejs.org/api/addons.html) 69 | build/Release 70 | 71 | # Dependency directories 72 | node_modules/ 73 | jspm_packages/ 74 | 75 | # Typescript v1 declaration files 76 | typings/ 77 | 78 | # Optional npm cache directory 79 | .npm 80 | 81 | # Optional eslint cache 82 | .eslintcache 83 | 84 | # Optional REPL history 85 | .node_repl_history 86 | 87 | # Output of 'npm pack' 88 | *.tgz 89 | 90 | # Yarn Integrity file 91 | .yarn-integrity 92 | 93 | # dotenv environment variables file 94 | .env 95 | 96 | 97 | ### OSX ### 98 | *.DS_Store 99 | .AppleDouble 100 | .LSOverride 101 | 102 | # Icon must end with two \r 103 | Icon 104 | 105 | 106 | # Thumbnails 107 | ._* 108 | 109 | # Files that might appear in the root of a volume 110 | .DocumentRevisions-V100 111 | .fseventsd 112 | .Spotlight-V100 113 | .TemporaryItems 114 | .Trashes 115 | .VolumeIcon.icns 116 | .com.apple.timemachine.donotpresent 117 | 118 | # Directories potentially created on remote AFP share 119 | .AppleDB 120 | .AppleDesktop 121 | Network Trash Folder 122 | Temporary Items 123 | .apdisk 124 | 125 | ### Vim ### 126 | # swap 127 | [._]*.s[a-v][a-z] 128 | [._]*.sw[a-p] 129 | [._]s[a-v][a-z] 130 | [._]sw[a-p] 131 | # session 132 | Session.vim 133 | # temporary 134 | .netrwhist 135 | # auto-generated tag files 136 | tags 137 | 138 | ### Windows ### 139 | # Windows thumbnail cache files 140 | Thumbs.db 141 | ehthumbs.db 142 | ehthumbs_vista.db 143 | 144 | # Folder config file 145 | Desktop.ini 146 | 147 | # Recycle Bin used on file shares 148 | $RECYCLE.BIN/ 149 | 150 | # Windows Installer files 151 | *.cab 152 | *.msi 153 | *.msm 154 | *.msp 155 | 156 | # Windows shortcuts 157 | *.lnk 158 | # es-module.patch 159 | *.patch 160 | 161 | # End of https://www.gitignore.io/api/node,windows,osx,linux,vim -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/favicon.ico -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/examples/preact-playground/src/files/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /examples/preact-playground/src/files/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Preact PWA", 3 | "short_name": "Preact PWA", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#fff", 8 | "theme_color": "#673ab8", 9 | "icons": [{ 10 | "src": "/assets/icons/android-chrome-192x192.png", 11 | "type": "image/png", 12 | "sizes": "192x192" 13 | }, 14 | { 15 | "src": "/assets/icons/android-chrome-512x512.png", 16 | "type": "image/png", 17 | "sizes": "512x512" 18 | }] 19 | } 20 | -------------------------------------------------------------------------------- /examples/preact-playground/src/index.js: -------------------------------------------------------------------------------- 1 | import "./style"; 2 | import { h, Component, render } from "preact"; 3 | import { ProppyProvider } from "proppy-preact"; 4 | import createHistory from "history/createBrowserHistory"; 5 | import { Router, Route, Switch } from "react-router-dom"; 6 | 7 | import { routesConfig } from "./config"; 8 | import store from "./proppy-redux/store"; 9 | 10 | const history = createHistory(); 11 | const providers = { 12 | store, 13 | history 14 | }; 15 | 16 | export function App() { 17 | return ( 18 | 19 | 20 |
21 | 37 | 38 | 39 | {routesConfig.map(config => 40 | config.routes.map(route => ) 41 | )} 42 | 43 |
44 |

ProppyJS Playground with Preact

45 |
46 |
47 | 48 |
49 | 50 |

51 | Visit proppyjs.com for more info 52 |

53 |
54 |
55 |
56 | ); 57 | } 58 | 59 | if (typeof window !== "undefined") { 60 | render(, document.getElementById("root")); 61 | } 62 | -------------------------------------------------------------------------------- /examples/preact-playground/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppyjs-preact-playground-wip", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "index.js", 7 | "dependencies": { 8 | "history": "^4.7.0", 9 | "preact": "8.2.5", 10 | "preact-compat": "3.17.0", 11 | "proppy": "^1.2.1", 12 | "proppy-preact": "^1.0.0", 13 | "proppy-redux": "^1.0.0", 14 | "proppy-rx": "^1.0.0", 15 | "react-router": "^4.0.0", 16 | "react-router-dom": "^4.0.0", 17 | "redux": "^4.0.0", 18 | "rxjs": "^6.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from "../constants"; 2 | 3 | export function incrementCounter() { 4 | return { 5 | type: INCREMENT_COUNTER 6 | }; 7 | } 8 | 9 | export function decrementCounter() { 10 | return { 11 | type: DECREMENT_COUNTER 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/components/PlayWithStore.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withStore } from "proppy-redux"; 3 | import { attach } from "proppy-preact"; 4 | 5 | import { incrementCounter, decrementCounter } from "../actions/counter"; 6 | 7 | function mapState(state) { 8 | return { 9 | counter: state.counter.value 10 | }; 11 | } 12 | 13 | const mapDispatch = { 14 | increment: incrementCounter, 15 | decrement: decrementCounter 16 | }; 17 | 18 | const P = withStore(mapState, mapDispatch); 19 | 20 | function MyComponent({ counter, increment, decrement }) { 21 | return ( 22 |
23 |

ProppyJS: withStore

24 | 25 |

Counter: {counter}

26 | 27 | 28 | 29 |
30 | ); 31 | } 32 | 33 | export default attach(P)(MyComponent); 34 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/constants/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER = "INCREMENT_COUNTER"; 2 | export const DECREMENT_COUNTER = "DECREMENT_COUNTER"; 3 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from "../constants"; 2 | 3 | const INITIAL_STATE = { 4 | value: 0 5 | }; 6 | 7 | export default function counterReducer(state = INITIAL_STATE, action) { 8 | switch (action.type) { 9 | case INCREMENT_COUNTER: 10 | return Object.assign( 11 | {}, 12 | { 13 | value: state.value + 1 14 | } 15 | ); 16 | case DECREMENT_COUNTER: 17 | return Object.assign( 18 | {}, 19 | { 20 | value: state.value - 1 21 | } 22 | ); 23 | default: 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | 3 | import counter from "./counter"; 4 | 5 | export default combineReducers({ 6 | counter 7 | }); 8 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-redux/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | 3 | import rootReducer from "../reducers"; 4 | 5 | export default createStore(rootReducer); 6 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy-rx/PlayWithStream.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withStream } from "proppy-rx"; 3 | import { map } from "rxjs/operators"; 4 | import { attach } from "proppy-preact"; 5 | 6 | const P = withStream(props$ => 7 | props$.pipe( 8 | map(props => ({ 9 | url: props.match.url 10 | })) 11 | ) 12 | ); 13 | 14 | function MyComponent({ url }) { 15 | return ( 16 |
17 |

ProppyJS: withStream

18 | 19 |

URL: {url}

20 |
21 | ); 22 | } 23 | 24 | export default attach(P)(MyComponent); 25 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayCompose.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, withState } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ foo: "foo value" }), 7 | withProps((parentProps, providers) => ({ 8 | bar: `bar with ${parentProps.foo}` 9 | })), 10 | withState("counter", "setCounter", 0) 11 | ); 12 | 13 | function MyComponent({ foo, bar, counter, setCounter }) { 14 | return ( 15 |
16 |

ProppyJS: compose

17 | 18 |

Foo: {foo}

19 | 20 |

Bar: {bar}

21 | 22 |

Counter: {counter}

23 | 24 | 25 |
26 | ); 27 | } 28 | 29 | export default attach(P)(MyComponent); 30 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayDidSubscribe.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withState, didSubscribe } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withState("stars", "setStars", "n/a"), 7 | withState("isLoading", "setLoading", false), 8 | didSubscribe((props, providers) => { 9 | props.setLoading(true); 10 | fetch("https://api.github.com/repos/fahad19/proppy") 11 | .then(res => res.json()) 12 | .then(data => data.stargazers_count) 13 | .then(stars => { 14 | props.setLoading(false); 15 | props.setStars(stars); 16 | }); 17 | }) 18 | ); 19 | 20 | function MyComponent({ stars, isLoading }) { 21 | return ( 22 |
23 |

ProppyJS: didSubscribe

24 | 25 |

26 | Stargazers count of{" "} 27 | fahad19/proppy: {stars} 28 |

29 | 30 |

Request in progress: {isLoading ? "Yes" : "No"}

31 |
32 | ); 33 | } 34 | 35 | export default attach(P)(MyComponent); 36 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayEmit.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, emit } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ interval: 0 }), 7 | emit((cb, props) => { 8 | let n = props.interval; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ interval: n }); 13 | }, 300); 14 | 15 | return () => clearInterval(i); 16 | }) 17 | ); 18 | 19 | function MyComponent({ interval }) { 20 | return ( 21 |
22 |

ProppyJS: emit

23 | 24 |

Interval: {interval}

25 |
26 | ); 27 | } 28 | 29 | export default attach(P)(MyComponent); 30 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayMap.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, emit, map } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ number: 0 }), 7 | emit((cb, props) => { 8 | let n = props.number; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ number: n }); 13 | }, 200); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | map(props => ({ 18 | ...props, 19 | number: props.number * 100 20 | })) 21 | ); 22 | 23 | function MyComponent({ number }) { 24 | return ( 25 |
26 |

ProppyJS: map

27 | 28 |

Number: {number}

29 |
30 | ); 31 | } 32 | 33 | export default attach(P)(MyComponent); 34 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayOnChange.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, emit, onChange } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ interval: 0, type: "even" }), 7 | emit((cb, props) => { 8 | let n = props.interval; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ interval: n }); 13 | }, 1000); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | onChange("interval", props => ({ 18 | type: props.interval % 2 === 0 ? "even" : "odd" 19 | })) 20 | ); 21 | 22 | function MyComponent({ interval, type }) { 23 | return ( 24 |
25 |

ProppyJS: onChange

26 | 27 |

Interval: {interval}

28 | 29 |

Number is: {type}

30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayShouldUpdate.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, emit, shouldUpdate } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ number: 0 }), 7 | emit((cb, props) => { 8 | let n = props.number; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ number: n }); 13 | }, 1000); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | shouldUpdate((prevProps, nextProps) => { 18 | return nextProps.number % 2 === 0; 19 | }) 20 | ); 21 | 22 | function MyComponent({ number }) { 23 | return ( 24 |
25 |

ProppyJS: shouldUpdate

26 | 27 |

Number: {number}

28 | 29 |

(Re-renders when number is even)

30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithHandlers.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withState, withHandlers } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withState("counter", "setCounter", 0), 7 | withHandlers({ 8 | incrementBy: (props, providers) => n => { 9 | props.setCounter(props.counter + n); 10 | } 11 | }) 12 | ); 13 | 14 | function MyComponent({ counter, incrementBy }) { 15 | return ( 16 |
17 |

ProppyJS: withHandlers

18 | 19 |

Counter: {counter}

20 | 21 | 22 |
23 | ); 24 | } 25 | 26 | export default attach(P)(MyComponent); 27 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithObservable.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withObservable } from "proppy"; 3 | import { interval } from "rxjs"; 4 | import { map } from "rxjs/operators"; 5 | import { attach } from "proppy-preact"; 6 | 7 | const P = withObservable(() => { 8 | return interval(300).pipe( 9 | map(n => ({ 10 | interval: n 11 | })) 12 | ); 13 | }); 14 | 15 | function MyComponent({ interval }) { 16 | return ( 17 |
18 |

ProppyJS: withObservable

19 | 20 |

Interval: {interval}

21 |
22 | ); 23 | } 24 | 25 | export default attach(P)(MyComponent); 26 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithProps.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withProps } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = withProps({ 6 | foo: "foo value" 7 | }); 8 | 9 | function MyComponent({ foo }) { 10 | return ( 11 |
12 |

ProppyJS: withProps

13 | 14 |

Foo: {foo}

15 |
16 | ); 17 | } 18 | 19 | export default attach(P)(MyComponent); 20 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithReducer.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withReducer } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | function reducer(state, action) { 6 | switch (action.type) { 7 | case "INCREMENT": 8 | return { value: state.value + 1 }; 9 | case "DECREMENT": 10 | return { value: state.value - 1 }; 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | const P = withReducer("counter", "dispatch", reducer, { value: 0 }); 17 | 18 | function MyComponent({ counter, dispatch }) { 19 | return ( 20 |
21 |

ProppyJS: withReducer

22 | 23 |

Counter: {counter.value}

24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | 31 | export default attach(P)(MyComponent); 32 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithState.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withState } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = withState("counter", "setCounter", 0); 6 | 7 | function MyComponent({ counter, setCounter }) { 8 | return ( 9 |
10 |

ProppyJS: withState

11 | 12 |

Counter: {counter}

13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default attach(P)(MyComponent); 20 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithStateHandlers.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { withStateHandlers } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = withStateHandlers( 6 | // initial state 7 | { 8 | counter: 0 9 | }, 10 | 11 | // handlers 12 | { 13 | incrementBy: (props, providers) => n => { 14 | // return new state 15 | return { 16 | counter: props.counter + n 17 | }; 18 | } 19 | } 20 | ); 21 | 22 | function MyComponent({ counter, incrementBy }) { 23 | return ( 24 |
25 |

ProppyJS: withStateHandlers

26 | 27 |

Counter: {counter}

28 | 29 | 30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/preact-playground/src/proppy/PlayWithTimer.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { compose, withProps, withTimer } from "proppy"; 3 | import { attach } from "proppy-preact"; 4 | 5 | const P = compose( 6 | withProps({ hasElapsed: false }), 7 | withTimer(2000, { hasElapsed: true }) 8 | ); 9 | 10 | function MyComponent({ hasElapsed }) { 11 | return ( 12 |
13 |

ProppyJS: withElapsed

14 | 15 |

Elapsed two seconds: {hasElapsed ? "Yes!" : "No"}

16 |
17 | ); 18 | } 19 | 20 | export default attach(P)(MyComponent); 21 | -------------------------------------------------------------------------------- /examples/preact-playground/src/style/index.scss: -------------------------------------------------------------------------------- 1 | #root { 2 | font-family: sans-serif; 3 | text-align: center; 4 | color: #000; 5 | } 6 | -------------------------------------------------------------------------------- /examples/react-playground/README.md: -------------------------------------------------------------------------------- 1 | # ReactJS Playground 2 | 3 | Examples of ProppyJS using React. 4 | 5 | ## Demo 6 | 7 | You can access this example on [CodeSandbox](https://codesandbox.io/s/github/fahad19/proppy/tree/master/examples/react-playground). 8 | 9 | ## Local development 10 | 11 | ``` 12 | $ npm install 13 | $ npm start 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/react-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppyjs-react-playground", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "history": "^4.7.0", 9 | "proppy": "^1.2.1", 10 | "proppy-react": "^1.0.0", 11 | "proppy-redux": "^1.0.0", 12 | "proppy-rx": "^1.0.0", 13 | "react": "16.3.2", 14 | "react-dom": "16.3.2", 15 | "react-router": "^4.0.0", 16 | "react-router-dom": "^4.0.0", 17 | "react-scripts": "1.1.4", 18 | "redux": "^4.0.0", 19 | "rxjs": "^6.0.0" 20 | }, 21 | "devDependencies": {}, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test --env=jsdom", 26 | "eject": "react-scripts eject" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/react-playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/react-playground/src/config.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // proppy 4 | import PlayWithState from "./proppy/PlayWithState"; 5 | import PlayWithProps from "./proppy/PlayWithProps"; 6 | import PlayCompose from "./proppy/PlayCompose"; 7 | import PlayWithReducer from "./proppy/PlayWithReducer"; 8 | import PlayWithHandlers from "./proppy/PlayWithHandlers"; 9 | import PlayWithStateHandlers from "./proppy/PlayWithStateHandlers"; 10 | import PlayWithObservable from "./proppy/PlayWithObservable"; 11 | import PlayWithTimer from "./proppy/PlayWithTimer"; 12 | import PlayEmit from "./proppy/PlayEmit"; 13 | import PlayOnChange from "./proppy/PlayOnChange"; 14 | import PlayMap from "./proppy/PlayMap"; 15 | import PlayShouldUpdate from "./proppy/PlayShouldUpdate"; 16 | import PlayDidSubscribe from "./proppy/PlayDidSubscribe"; 17 | 18 | // proppy-redux 19 | import PlayWithStore from "./proppy-redux/components/PlayWithStore"; 20 | 21 | // proppy-rx 22 | import PlayWithStream from "./proppy-rx/PlayWithStream"; 23 | 24 | export const routesConfig = [ 25 | { 26 | name: "proppy", 27 | routes: [ 28 | { path: "/withProps", render: () => }, 29 | { path: "/withState", render: () => }, 30 | { path: "/compose", render: () => }, 31 | { path: "/withReducer", render: () => }, 32 | { path: "/withHandlers", render: () => }, 33 | { path: "/withStateHandlers", render: () => }, 34 | { path: "/withObservable", render: () => }, 35 | { path: "/withTimer", render: () => }, 36 | { path: "/emit", render: () => }, 37 | { path: "/onChange", render: () => }, 38 | { path: "/map", render: () => }, 39 | { path: "/shouldUpdate", render: () => }, 40 | { path: "/didSubscribe", render: () => } 41 | ] 42 | }, 43 | { 44 | name: "proppy-redux", 45 | routes: [{ path: "/withStore", render: () => }] 46 | }, 47 | { 48 | name: "proppy-rx", 49 | routes: [ 50 | { path: "/withStream", render: props => } 51 | ] 52 | } 53 | ]; 54 | -------------------------------------------------------------------------------- /examples/react-playground/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "react-dom"; 3 | import createHistory from "history/createBrowserHistory"; 4 | import { Router, Route, Switch, Link } from "react-router-dom"; 5 | import { ProppyProvider } from "proppy-react"; 6 | 7 | import { routesConfig } from "./config"; 8 | import store from "./proppy-redux/store"; 9 | 10 | const history = createHistory(); 11 | 12 | const styles = { 13 | fontFamily: "sans-serif", 14 | textAlign: "center" 15 | }; 16 | 17 | const providers = { 18 | store, 19 | history 20 | }; 21 | 22 | const App = () => ( 23 | 24 | 25 |
26 | 42 | 43 | 44 | {routesConfig.map(config => 45 | config.routes.map(route => ) 46 | )} 47 | 48 |
49 |

ProppyJS Playground with React

50 |
51 |
52 | 53 |
54 | 55 |

56 | Visit proppyjs.com for more info 57 |

58 |
59 |
60 |
61 | ); 62 | 63 | render(, document.getElementById("root")); 64 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from "../constants"; 2 | 3 | export function incrementCounter() { 4 | return { 5 | type: INCREMENT_COUNTER 6 | }; 7 | } 8 | 9 | export function decrementCounter() { 10 | return { 11 | type: DECREMENT_COUNTER 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/components/PlayWithStore.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStore } from "proppy-redux"; 3 | import { attach } from "proppy-react"; 4 | 5 | import { incrementCounter, decrementCounter } from "../actions/counter"; 6 | 7 | function mapState(state) { 8 | return { 9 | counter: state.counter.value 10 | }; 11 | } 12 | 13 | const mapDispatch = { 14 | increment: incrementCounter, 15 | decrement: decrementCounter 16 | }; 17 | 18 | const P = withStore(mapState, mapDispatch); 19 | 20 | function MyComponent({ counter, increment, decrement }) { 21 | return ( 22 |
23 |

ProppyJS: withStore

24 | 25 |

Counter: {counter}

26 | 27 | 28 | 29 |
30 | ); 31 | } 32 | 33 | export default attach(P)(MyComponent); 34 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/constants/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER = "INCREMENT_COUNTER"; 2 | export const DECREMENT_COUNTER = "DECREMENT_COUNTER"; 3 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from "../constants"; 2 | 3 | const INITIAL_STATE = { 4 | value: 0 5 | }; 6 | 7 | export default function counterReducer(state = INITIAL_STATE, action) { 8 | switch (action.type) { 9 | case INCREMENT_COUNTER: 10 | return Object.assign( 11 | {}, 12 | { 13 | value: state.value + 1 14 | } 15 | ); 16 | case DECREMENT_COUNTER: 17 | return Object.assign( 18 | {}, 19 | { 20 | value: state.value - 1 21 | } 22 | ); 23 | default: 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | 3 | import counter from "./counter"; 4 | 5 | export default combineReducers({ 6 | counter 7 | }); 8 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-redux/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | 3 | import rootReducer from "../reducers"; 4 | 5 | export default createStore(rootReducer); 6 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy-rx/PlayWithStream.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStream } from "proppy-rx"; 3 | import { map } from "rxjs/operators"; 4 | import { attach } from "proppy-react"; 5 | 6 | const P = withStream(props$ => 7 | props$.pipe( 8 | map(props => ({ 9 | url: props.match.url 10 | })) 11 | ) 12 | ); 13 | 14 | function MyComponent({ url }) { 15 | return ( 16 |
17 |

ProppyJS: withStream

18 | 19 |

URL: {url}

20 |
21 | ); 22 | } 23 | 24 | export default attach(P)(MyComponent); 25 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayCompose.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, withState } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ foo: "foo value" }), 7 | withProps((parentProps, providers) => ({ 8 | bar: `bar with ${parentProps.foo}` 9 | })), 10 | withState("counter", "setCounter", 0) 11 | ); 12 | 13 | function MyComponent({ foo, bar, counter, setCounter }) { 14 | return ( 15 |
16 |

ProppyJS: compose

17 | 18 |

Foo: {foo}

19 | 20 |

Bar: {bar}

21 | 22 |

Counter: {counter}

23 | 24 | 25 |
26 | ); 27 | } 28 | 29 | export default attach(P)(MyComponent); 30 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayDidSubscribe.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withState, didSubscribe } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withState("stars", "setStars", "n/a"), 7 | withState("isLoading", "setLoading", false), 8 | didSubscribe((props, providers) => { 9 | props.setLoading(true); 10 | fetch("https://api.github.com/repos/fahad19/proppy") 11 | .then(res => res.json()) 12 | .then(data => data.stargazers_count) 13 | .then(stars => { 14 | props.setLoading(false); 15 | props.setStars(stars); 16 | }); 17 | }) 18 | ); 19 | 20 | function MyComponent({ stars, isLoading }) { 21 | return ( 22 |
23 |

ProppyJS: didSubscribe

24 | 25 |

26 | Stargazers count of{" "} 27 | fahad19/proppy: {stars} 28 |

29 | 30 |

Request in progress: {isLoading ? "Yes" : "No"}

31 |
32 | ); 33 | } 34 | 35 | export default attach(P)(MyComponent); 36 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayEmit.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, emit } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ interval: 0 }), 7 | emit((cb, props) => { 8 | let n = props.interval; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ interval: n }); 13 | }, 300); 14 | 15 | return () => clearInterval(i); 16 | }) 17 | ); 18 | 19 | function MyComponent({ interval }) { 20 | return ( 21 |
22 |

ProppyJS: emit

23 | 24 |

Interval: {interval}

25 |
26 | ); 27 | } 28 | 29 | export default attach(P)(MyComponent); 30 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayMap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, emit, map } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ number: 0 }), 7 | emit((cb, props) => { 8 | let n = props.number; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ number: n }); 13 | }, 200); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | map(props => ({ 18 | ...props, 19 | number: props.number * 100 20 | })) 21 | ); 22 | 23 | function MyComponent({ number }) { 24 | return ( 25 |
26 |

ProppyJS: map

27 | 28 |

Number: {number}

29 |
30 | ); 31 | } 32 | 33 | export default attach(P)(MyComponent); 34 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayOnChange.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, emit, onChange } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ interval: 0, type: "even" }), 7 | emit((cb, props) => { 8 | let n = props.interval; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ interval: n }); 13 | }, 1000); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | onChange("interval", props => ({ 18 | type: props.interval % 2 === 0 ? "even" : "odd" 19 | })) 20 | ); 21 | 22 | function MyComponent({ interval, type }) { 23 | return ( 24 |
25 |

ProppyJS: onChange

26 | 27 |

Interval: {interval}

28 | 29 |

Number is: {type}

30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayShouldUpdate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, emit, shouldUpdate } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ number: 0 }), 7 | emit((cb, props) => { 8 | let n = props.number; 9 | 10 | const i = setInterval(() => { 11 | n++; 12 | cb({ number: n }); 13 | }, 1000); 14 | 15 | return () => clearInterval(i); 16 | }), 17 | shouldUpdate((prevProps, nextProps) => { 18 | return nextProps.number % 2 === 0; 19 | }) 20 | ); 21 | 22 | function MyComponent({ number }) { 23 | return ( 24 |
25 |

ProppyJS: shouldUpdate

26 | 27 |

Number: {number}

28 | 29 |

(Re-renders when number is even)

30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithHandlers.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withState, withHandlers } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withState("counter", "setCounter", 0), 7 | withHandlers({ 8 | incrementBy: (props, providers) => n => { 9 | props.setCounter(props.counter + n); 10 | } 11 | }) 12 | ); 13 | 14 | function MyComponent({ counter, incrementBy }) { 15 | return ( 16 |
17 |

ProppyJS: withHandlers

18 | 19 |

Counter: {counter}

20 | 21 | 22 |
23 | ); 24 | } 25 | 26 | export default attach(P)(MyComponent); 27 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithObservable.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withObservable } from "proppy"; 3 | import { interval } from "rxjs"; 4 | import { map } from "rxjs/operators"; 5 | import { attach } from "proppy-react"; 6 | 7 | const P = withObservable(() => { 8 | return interval(300).pipe( 9 | map(n => ({ 10 | interval: n 11 | })) 12 | ); 13 | }); 14 | 15 | function MyComponent({ interval }) { 16 | return ( 17 |
18 |

ProppyJS: withObservable

19 | 20 |

Interval: {interval}

21 |
22 | ); 23 | } 24 | 25 | export default attach(P)(MyComponent); 26 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithProps.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withProps } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = withProps({ 6 | foo: "foo value" 7 | }); 8 | 9 | function MyComponent({ foo }) { 10 | return ( 11 |
12 |

ProppyJS: withProps

13 | 14 |

Foo: {foo}

15 |
16 | ); 17 | } 18 | 19 | export default attach(P)(MyComponent); 20 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithReducer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withReducer } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | function reducer(state, action) { 6 | switch (action.type) { 7 | case "INCREMENT": 8 | return { value: state.value + 1 }; 9 | case "DECREMENT": 10 | return { value: state.value - 1 }; 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | const P = withReducer("counter", "dispatch", reducer, { value: 0 }); 17 | 18 | function MyComponent({ counter, dispatch }) { 19 | return ( 20 |
21 |

ProppyJS: withReducer

22 | 23 |

Counter: {counter.value}

24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | 31 | export default attach(P)(MyComponent); 32 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithState.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withState } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = withState("counter", "setCounter", 0); 6 | 7 | function MyComponent({ counter, setCounter }) { 8 | return ( 9 |
10 |

ProppyJS: withState

11 | 12 |

Counter: {counter}

13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default attach(P)(MyComponent); 20 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithStateHandlers.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStateHandlers } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = withStateHandlers( 6 | // initial state 7 | { 8 | counter: 0 9 | }, 10 | 11 | // handlers 12 | { 13 | incrementBy: (props, providers) => n => { 14 | // return new state 15 | return { 16 | counter: props.counter + n 17 | }; 18 | } 19 | } 20 | ); 21 | 22 | function MyComponent({ counter, incrementBy }) { 23 | return ( 24 |
25 |

ProppyJS: withStateHandlers

26 | 27 |

Counter: {counter}

28 | 29 | 30 |
31 | ); 32 | } 33 | 34 | export default attach(P)(MyComponent); 35 | -------------------------------------------------------------------------------- /examples/react-playground/src/proppy/PlayWithTimer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { compose, withProps, withTimer } from "proppy"; 3 | import { attach } from "proppy-react"; 4 | 5 | const P = compose( 6 | withProps({ hasElapsed: false }), 7 | withTimer(2000, { hasElapsed: true }) 8 | ); 9 | 10 | function MyComponent({ hasElapsed }) { 11 | return ( 12 |
13 |

ProppyJS: withElapsed

14 | 15 |

Elapsed two seconds: {hasElapsed ? "Yes!" : "No"}

16 |
17 | ); 18 | } 19 | 20 | export default attach(P)(MyComponent); 21 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.38", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "1.3.1", 7 | "changelog": { 8 | "repo": "fahad19/proppy", 9 | "labels": { 10 | "bug": "Bug fix", 11 | "PR": "Pull Requests" 12 | }, 13 | "cacheDir": ".changelog" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "bootstrap": "lerna bootstrap", 8 | "test": "lerna run test", 9 | "cover": "lerna run cover", 10 | "lint": "lerna run lint", 11 | "transpile": "lerna run transpile", 12 | "clean": "lerna run clean", 13 | "dist": "lerna run dist", 14 | "dist:lib": "lerna run dist:lib", 15 | "dist:min": "lerna run dist:lib", 16 | "build": "lerna run build" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/fahad19/proppy.git" 21 | }, 22 | "author": { 23 | "name": "Fahad Ibnay Heylaal", 24 | "url": "http://fahad19.com" 25 | }, 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/fahad19/proppy/issues" 29 | }, 30 | "homepage": "https://github.com/fahad19/proppy#readme", 31 | "dependencies": {}, 32 | "devDependencies": { 33 | "@types/jest": "^22.2.3", 34 | "bulma": "^0.7.1", 35 | "compression-webpack-plugin": "^1.1.11", 36 | "jest": "^22.4.3", 37 | "js-yaml": "^3.11.0", 38 | "jsdom": "11.11.0", 39 | "lerna": "^2.11.0", 40 | "lerna-changelog": "^0.7.0", 41 | "live-server": "^1.2.0", 42 | "lodash": "^4.17.10", 43 | "marked": "^0.3.19", 44 | "metalsmith": "^2.3.0", 45 | "node-sass": "^4.9.0", 46 | "rimraf": "^2.6.2", 47 | "ts-jest": "^22.4.4", 48 | "ts-loader": "^4.2.0", 49 | "tslint": "^5.10.0", 50 | "typescript": "^3.1.2", 51 | "webpack": "^4.20.2", 52 | "webpack-cli": "^3.1.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/README.md: -------------------------------------------------------------------------------- 1 | # proppy-frint-react 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-frint-react.svg)](https://www.npmjs.com/package/proppy-frint-react) 4 | 5 | > FrintJS + React integration for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [API](#api) 13 | - [ProppyProvider](#proppyprovider) 14 | 15 | 16 | 17 | --- 18 | 19 | # Guide 20 | 21 | ## Installation 22 | 23 | With [npm](https://www.npmjs.com/): 24 | 25 | ``` 26 | $ npm install --save proppy react proppy-react frint-react proppy-frint-react 27 | ``` 28 | 29 | Via [unpkg](https://unpkg.com) CDN: 30 | 31 | ```html 32 | 33 | 34 | 35 | 38 | ``` 39 | 40 | ## Usage 41 | 42 | In your Root component, wrap it with `ProppyProvider` component, and it will take care of getting all the providers from your FrintJS app automatically: 43 | 44 | ```js 45 | // components/Root.js 46 | import React from 'react'; 47 | import { ProppyProvider } from 'proppy-frint-react'; 48 | 49 | import MyComponent from './MyComponent'; 50 | 51 | export default function Root() { 52 | return ( 53 | 54 | 55 | 56 | ); 57 | } 58 | ``` 59 | 60 | Now anywhere from your components tree, you can use the `attach` higher-order component: 61 | 62 | ```js 63 | // components/MyComponent.js 64 | import React from 'react'; 65 | import { compose, withProps } from 'proppy'; 66 | import { attach } from 'proppy-react'; 67 | 68 | const P = compose( 69 | withProps((props, providers) => { 70 | const { app, foo } = providers; 71 | 72 | return { foo: foo }; 73 | }), 74 | withProps({ bar: 'bar value' }), 75 | ); 76 | 77 | function MyComponent(props) { 78 | const { foo, bar } = props; 79 | 80 | return

; 81 | } 82 | 83 | export default attach(P)(MyComponent); 84 | ``` 85 | 86 | # API 87 | 88 | ## ProppyProvider 89 | 90 | For setting providers at React's context-level. 91 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.(ts|tsx)$': 'ts-jest', 5 | }, 6 | testRegex: '\\.spec\\.(ts|tsx)', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'node'], 8 | collectCoverage: false, 9 | bail: true, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-frint-react", 3 | "version": "1.3.1", 4 | "description": "FrintJS + React integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-frint-react", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "@types/react": "^16.4.6", 32 | "frint": "^5.6.1", 33 | "frint-config": "^5.4.3", 34 | "frint-react": "^5.4.3", 35 | "proppy": "^1.3.1", 36 | "proppy-react": "^1.3.1", 37 | "react": "^15.3.0" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/fahad19/proppy/issues" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/src/ProppyProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { observe, streamProps, ProviderProps } from 'frint-react'; 3 | import { ProppyProvider as ProppyReactProvider } from 'proppy-react'; 4 | 5 | export class Base extends React.Component { 6 | public render() { 7 | const { providers, children } = this.props; 8 | return {children}; 9 | } 10 | } 11 | 12 | export const ProppyProvider = observe((app: any) => { 13 | return streamProps({ providers: app.container.registry }) 14 | .get$(); 15 | })(Base); 16 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import * as lib from './index'; 3 | 4 | describe('proppy-frint-preact :: index', () => { 5 | test('is an object', () => { 6 | expect(typeof lib).toEqual('object'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ProppyProvider } from './ProppyProvider'; 2 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy-frint-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | const externals = require('frint-config').externals; 6 | 7 | const NODE_ENV = process.env.NODE_ENV === 'production' ? 'production' : 'development'; 8 | 9 | const filename = NODE_ENV === 'production' ? '[name].min.js' : '[name].js'; 10 | 11 | const plugins = [ 12 | new webpack.DefinePlugin({ 13 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 14 | }), 15 | ]; 16 | 17 | if (NODE_ENV === 'production') { 18 | plugins.push( 19 | new CompressionPlugin({ 20 | asset: '[path].gz[query]', 21 | algorithm: 'gzip', 22 | test: /\.js/, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | entry: { 29 | 'proppy-frint-react': path.join(__dirname, '/src/index.ts'), 30 | }, 31 | output: { 32 | path: path.join(__dirname, 'dist'), 33 | filename: filename, 34 | libraryTarget: 'umd', 35 | library: 'ProppyFrintReact', 36 | }, 37 | externals: externals.concat([ 38 | { 39 | frint: 'Frint', 40 | lodash: '_', 41 | proppy: 'Proppy', 42 | react: 'React', 43 | rxjs: 'Rx', 44 | }, 45 | ]), 46 | mode: process.env.NODE_ENV || 'development', 47 | devtool: 'source-map', 48 | plugins: plugins, 49 | performance: { 50 | hints: false, 51 | }, 52 | resolve: { 53 | extensions: ['.ts', '.tsx', '.js'], 54 | }, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.(ts|tsx)$/, 59 | exclude: /(node_modules)/, 60 | loader: 'ts-loader', 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/proppy-preact/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-preact/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-preact/ProppyProvider.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/ProppyProvider'); 2 | -------------------------------------------------------------------------------- /packages/proppy-preact/README.md: -------------------------------------------------------------------------------- 1 | # proppy-preact 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-preact.svg)](https://www.npmjs.com/package/proppy-preact) 4 | 5 | > Preact integration package for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [API](#api) 13 | - [ProppyProvider](#proppyprovider) 14 | - [attach](#attach) 15 | 16 | 17 | 18 | --- 19 | 20 | # Guide 21 | 22 | ## Installation 23 | 24 | With [npm](https://www.npmjs.com/): 25 | 26 | ``` 27 | $ npm install --save proppy preact proppy-preact 28 | ``` 29 | 30 | Via [unpkg](https://unpkg.com) CDN: 31 | 32 | ```html 33 | 34 | 35 | 36 | 39 | ``` 40 | 41 | ## Usage 42 | 43 | First, setup your root component with providers data: 44 | 45 | ```js 46 | // components/Root.js 47 | import { h } from 'preact'; 48 | import { ProppyProvider } from 'proppy-preact'; 49 | 50 | import MyComponent from './MyComponent'; 51 | 52 | const providers = { 53 | foo: 'foo value', 54 | }; 55 | 56 | export default function Root() { 57 | return ( 58 | 59 | 60 | 61 | ); 62 | } 63 | ``` 64 | 65 | Now anywhere from your components tree, you can use the `attach` higher-order component: 66 | 67 | ```js 68 | // components/MyComponent.js 69 | import { h } from 'preact'; 70 | import { compose, withProps } from 'proppy'; 71 | import { attach } from 'proppy-preact'; 72 | 73 | const P = compose( 74 | withProps((props, providers) => ({ 75 | foo: providers.foo, 76 | })), 77 | withProps({ bar: 'bar value' }), 78 | ); 79 | 80 | function MyComponent(props) { 81 | const { foo, bar } = props; 82 | 83 | return

; 84 | } 85 | 86 | export default attach(P)(MyComponent); 87 | ``` 88 | 89 | # API 90 | 91 | ## ProppyProvider 92 | 93 | For setting providers at Preact's context-level. 94 | 95 | **Example:** 96 | 97 | ```js 98 | import { h } from 'preact'; 99 | import { ProppyProvider } from 'proppy-preact'; 100 | 101 | export default function Root() { 102 | return ( 103 | 104 | 105 | 106 | ); 107 | } 108 | ``` 109 | 110 | ## attach 111 | 112 | > attach(P)(Component) 113 | 114 | Higher-order component for attaching your Proppy factory to your Component. 115 | -------------------------------------------------------------------------------- /packages/proppy-preact/attach.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/attach'); 2 | -------------------------------------------------------------------------------- /packages/proppy-preact/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.(ts|tsx)$': 'ts-jest', 5 | }, 6 | testRegex: '\\.spec\\.(ts|tsx)', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'node'], 8 | collectCoverage: false, 9 | bail: true, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/proppy-preact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-preact", 3 | "version": "1.3.1", 4 | "description": "Preact integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-preact", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "frint-test-utils": "^5.7.2", 32 | "preact": "^8.2.9", 33 | "preact-render-spy": "^1.3.0", 34 | "proppy": "^1.3.1" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/fahad19/proppy/issues" 38 | }, 39 | "license": "MIT", 40 | "dependencies": { 41 | "preact-context": "^1.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/ProppyContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'preact-context'; 2 | 3 | export const ProppyContext = createContext({ 4 | providers: {}, 5 | }); 6 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/ProppyProvider.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | 3 | /** 4 | * Source: https://gist.github.com/developit/5d879edb820228224dc9 5 | */ 6 | export class ProppyProvider extends Component { 7 | public getChildContext() { 8 | const { children, ...context } = this.props; 9 | 10 | return context; 11 | } 12 | 13 | public render({ children }) { 14 | return (children && children[0]) || null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/ProppySubscription.tsx: -------------------------------------------------------------------------------- 1 | import { Proppy, ProppyFactory, Providers, create } from 'proppy'; 2 | import { Component, RenderableProps } from 'preact'; 3 | 4 | export interface ComponentProps { 5 | parentProps: any; 6 | providers: Providers; 7 | proppyFactory: ProppyFactory; 8 | } 9 | 10 | export interface ComponentState { 11 | proppyProps: any; 12 | } 13 | 14 | export class ProppySubscription extends Component, ComponentState> { 15 | private _p: Proppy; 16 | private _parent: Proppy; 17 | 18 | constructor(props, context) { 19 | super(props, context); 20 | 21 | const { providers } = context; 22 | 23 | this._parent = create({ 24 | initialize() { 25 | this.set(props); 26 | }, 27 | })(providers); 28 | this._p = props.proppyFactory(providers, this._parent); 29 | 30 | this.state = { 31 | proppyProps: this._p.props, 32 | }; 33 | } 34 | 35 | // @TODO: this needs attention 36 | public componentWillReceiveProps(nextProps) { 37 | this._parent.set(nextProps.parentProps); 38 | } 39 | 40 | public componentDidMount() { 41 | this._p.subscribe(proppyProps => 42 | this.setState({ 43 | proppyProps, 44 | }), 45 | ); 46 | } 47 | 48 | public componentWillUnmount() { 49 | this._p.destroy(); 50 | } 51 | 52 | public render(props) { 53 | return props.children[0](Object.assign({}, this.state.proppyProps, props.parentProps)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/attach.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { ProppyFactory } from 'proppy'; 3 | 4 | import { ProppyContext } from './ProppyContext'; 5 | import { ProppySubscription } from './ProppySubscription'; 6 | 7 | export function attach(P: ProppyFactory) { 8 | return function(Component) { 9 | return function(props) { 10 | return ( 11 | 12 | {providers => ( 13 | 14 | {proppyProps => } 15 | 16 | )} 17 | 18 | ); 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import * as lib from './index'; 3 | 4 | describe('proppy-preact :: index', () => { 5 | test('is an object', () => { 6 | expect(typeof lib).toEqual('object'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/proppy-preact/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ProppyProvider } from './ProppyProvider'; 2 | export { attach } from './attach'; 3 | -------------------------------------------------------------------------------- /packages/proppy-preact/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": ["es2015", "dom"], 6 | "module": "commonjs", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "jsxFactory": "h", 11 | "skipLibCheck": true 12 | }, 13 | "include": ["./src/**/*.ts"], 14 | "exclude": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/proppy-preact/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' ? 'production' : 'development'; 7 | 8 | const filename = NODE_ENV === 'production' ? '[name].min.js' : '[name].js'; 9 | 10 | const plugins = [ 11 | new webpack.DefinePlugin({ 12 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 13 | }), 14 | ]; 15 | 16 | if (NODE_ENV === 'production') { 17 | plugins.push( 18 | new CompressionPlugin({ 19 | asset: '[path].gz[query]', 20 | algorithm: 'gzip', 21 | test: /\.js/, 22 | }), 23 | ); 24 | } 25 | 26 | module.exports = { 27 | entry: { 28 | 'proppy-preact': path.join(__dirname, '/src/index.ts'), 29 | }, 30 | output: { 31 | path: path.join(__dirname, 'dist'), 32 | filename: filename, 33 | libraryTarget: 'umd', 34 | library: 'ProppyPreact', 35 | }, 36 | externals: { 37 | preact: 'Preact', 38 | proppy: 'Proppy', 39 | }, 40 | mode: process.env.NODE_ENV || 'development', 41 | devtool: 'source-map', 42 | plugins: plugins, 43 | performance: { 44 | hints: false, 45 | }, 46 | resolve: { 47 | extensions: ['.ts', '.tsx', '.js'], 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.(ts|tsx)$/, 53 | exclude: /(node_modules)/, 54 | loader: 'ts-loader', 55 | }, 56 | ], 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /packages/proppy-react/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-react/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-react/ProppyProvider.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/ProppyProvider'); 2 | -------------------------------------------------------------------------------- /packages/proppy-react/README.md: -------------------------------------------------------------------------------- 1 | # proppy-react 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-react.svg)](https://www.npmjs.com/package/proppy-react) 4 | 5 | > React integration package for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [API](#api) 13 | - [ProppyProvider](#proppyprovider) 14 | - [attach](#attach) 15 | 16 | 17 | 18 | --- 19 | 20 | # Guide 21 | 22 | ## Installation 23 | 24 | With [npm](https://www.npmjs.com/): 25 | 26 | ``` 27 | $ npm install --save proppy react proppy-react 28 | ``` 29 | 30 | Via [unpkg](https://unpkg.com) CDN: 31 | 32 | ```html 33 | 34 | 35 | 36 | 39 | ``` 40 | 41 | ## Usage 42 | 43 | First, setup your root component with providers data: 44 | 45 | ```js 46 | // components/Root.js 47 | import React from 'react'; 48 | import { ProppyProvider } from 'proppy-react'; 49 | 50 | import MyComponent from './MyComponent'; 51 | 52 | const providers = { 53 | foo: 'foo value', 54 | }; 55 | 56 | export default function Root() { 57 | return ( 58 | 59 | 60 | 61 | ); 62 | } 63 | ``` 64 | 65 | Now anywhere from your components tree, you can use the `attach` higher-order component: 66 | 67 | ```js 68 | // components/MyComponent.js 69 | import React from 'react'; 70 | import { compose, withProps } from 'proppy'; 71 | import { attach } from 'proppy-react'; 72 | 73 | const P = compose( 74 | withProps((props, providers) => ({ 75 | foo: providers.foo, 76 | })), 77 | withProps({ bar: 'bar value' }), 78 | ); 79 | 80 | function MyComponent(props) { 81 | const { foo, bar } = props; 82 | 83 | return

; 84 | } 85 | 86 | export default attach(P)(MyComponent); 87 | ``` 88 | 89 | # API 90 | 91 | ## ProppyProvider 92 | 93 | For setting providers at React's context-level. 94 | 95 | **Example:** 96 | 97 | ```js 98 | import React from 'react'; 99 | import { ProppyProvider } from 'proppy-react'; 100 | 101 | export default function Root() { 102 | return ( 103 | 104 | 105 | 106 | ); 107 | } 108 | ``` 109 | 110 | ## attach 111 | 112 | > attach(P)(Component) 113 | 114 | Higher-order component for attaching your Proppy factory to your Component. 115 | -------------------------------------------------------------------------------- /packages/proppy-react/attach.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/attach'); 2 | -------------------------------------------------------------------------------- /packages/proppy-react/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/src', 4 | ], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testRegex: '\\.spec\\.(ts|tsx)', 9 | moduleFileExtensions: [ 10 | 'ts', 11 | 'tsx', 12 | 'js', 13 | 'json', 14 | 'node', 15 | ], 16 | collectCoverage: false, 17 | bail: true, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/proppy-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-react", 3 | "version": "1.3.1", 4 | "description": "React integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-react", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "@types/react": "^16.3.13", 32 | "enzyme": "^3.3.0", 33 | "enzyme-adapter-react-16": "^1.1.1", 34 | "frint-test-utils": "^5.4.3", 35 | "jsdom": "^11.10.0", 36 | "proppy": "^1.3.1", 37 | "react": "^16.3.2", 38 | "react-dom": "^16.3.2", 39 | "react-test-renderer": "^16.3.2" 40 | }, 41 | "bugs": { 42 | "url": "https://github.com/fahad19/proppy/issues" 43 | }, 44 | "license": "MIT", 45 | "types": "./lib/index.d.ts" 46 | } 47 | -------------------------------------------------------------------------------- /packages/proppy-react/src/ProppyContext.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const ProppyContext = React.createContext('proppy'); 4 | -------------------------------------------------------------------------------- /packages/proppy-react/src/ProppyProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Providers } from 'proppy'; 3 | 4 | import { ProppyContext } from './ProppyContext'; 5 | 6 | export class ProppyProvider extends React.Component { 7 | public props: { 8 | providers: Providers; 9 | children: any; 10 | }; 11 | 12 | public render() { 13 | return ( 14 | 15 | {this.props.children} 16 | 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/proppy-react/src/ProppySubscription.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Proppy, ProppyFactory, Providers, create } from 'proppy'; 3 | 4 | export interface ComponentProps { 5 | children: any; 6 | parentProps: any; 7 | providers: Providers; 8 | proppyFactory: ProppyFactory; 9 | } 10 | 11 | export interface ComponentState { 12 | proppyProps: any; 13 | } 14 | 15 | export class ProppySubscription extends React.Component { 16 | private _p: Proppy; 17 | private _parent: Proppy; 18 | 19 | constructor(props, ...args) { 20 | super(props, ...args); 21 | 22 | const { 23 | providers, 24 | proppyFactory, 25 | parentProps, 26 | } = props; 27 | 28 | this._parent = create({ 29 | initialize() { 30 | this.set(parentProps); 31 | }, 32 | })(providers); 33 | this._p = props.proppyFactory(providers, this._parent); 34 | 35 | this.state = { 36 | proppyProps: this._p.props, 37 | }; 38 | } 39 | 40 | // @TODO: this needs attention 41 | public UNSAFE_componentWillReceiveProps(nextProps) { 42 | this._parent.set(nextProps.parentProps); 43 | } 44 | 45 | public componentDidMount() { 46 | this._p.subscribe( 47 | proppyProps => this.setState({ 48 | proppyProps, 49 | }) 50 | ); 51 | } 52 | 53 | public componentWillUnmount() { 54 | this._p.destroy(); 55 | } 56 | 57 | public render() { 58 | return this.props.children(this.state.proppyProps); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/proppy-react/src/attach.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ProppyFactory } from 'proppy'; 3 | 4 | import { ProppyContext } from './ProppyContext'; 5 | import { ProppySubscription } from './ProppySubscription'; 6 | 7 | export function attach(P: ProppyFactory) { 8 | return function (Component) { 9 | return function (props) { 10 | return ( 11 | 12 | {providers => ( 13 | 18 | {proppyProps => } 19 | 20 | )} 21 | 22 | ); 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/proppy-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ProppyContext } from './ProppyContext'; 2 | export { ProppyProvider } from './ProppyProvider'; 3 | export { ProppySubscription } from './ProppySubscription'; 4 | export { attach } from './attach'; 5 | -------------------------------------------------------------------------------- /packages/proppy-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' 7 | ? 'production' 8 | : 'development'; 9 | 10 | const filename = NODE_ENV === 'production' 11 | ? '[name].min.js' 12 | : '[name].js'; 13 | 14 | const plugins = [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 17 | }), 18 | ]; 19 | 20 | if (NODE_ENV === 'production') { 21 | plugins.push(new CompressionPlugin({ 22 | asset: '[path].gz[query]', 23 | algorithm: 'gzip', 24 | test: /\.js/, 25 | })); 26 | } 27 | 28 | module.exports = { 29 | entry: { 30 | 'proppy-react': path.join(__dirname, '/src/index.ts'), 31 | }, 32 | output: { 33 | path: path.join(__dirname, 'dist'), 34 | filename: filename, 35 | libraryTarget: 'umd', 36 | library: 'ProppyReact', 37 | }, 38 | externals: { 39 | proppy: 'Proppy', 40 | react: 'React', 41 | }, 42 | mode: process.env.NODE_ENV || 'development', 43 | devtool: 'source-map', 44 | plugins: plugins, 45 | performance: { 46 | hints: false, 47 | }, 48 | resolve: { 49 | extensions: [ 50 | '.ts', 51 | '.tsx', 52 | '.js', 53 | ], 54 | }, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.(ts|tsx)$/, 59 | exclude: /(node_modules)/, 60 | loader: 'ts-loader', 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/proppy-redux/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-redux/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-redux/README.md: -------------------------------------------------------------------------------- 1 | # proppy-redux 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-redux.svg)](https://www.npmjs.com/package/proppy-redux) 4 | 5 | > Redux integration package for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [API](#api) 13 | - [withStore](#withstore) 14 | 15 | 16 | 17 | --- 18 | 19 | # Guide 20 | 21 | ## Installation 22 | 23 | With [npm](https://www.npmjs.com/): 24 | 25 | ``` 26 | $ npm install --save proppy redux proppy-redux 27 | ``` 28 | 29 | Via [unpkg](https://unpkg.com) CDN: 30 | 31 | ```html 32 | 33 | 34 | 35 | 38 | ``` 39 | 40 | ## Usage 41 | 42 | With the `withStore` function, you can map state and dispatchable actions from Redux: 43 | 44 | ```js 45 | import { compose } from 'proppy'; 46 | import { withStore } from 'proppy-redux'; 47 | import { createStore }; 48 | 49 | // Redux reducer 50 | import reducer from '../reducers'; 51 | 52 | // Redux action creators 53 | import { incrementCounter } from '../actions/counter'; 54 | 55 | function mapState(state) { 56 | return { 57 | counter: state.counter.value, 58 | }; 59 | } 60 | 61 | const P = compose( 62 | withStore( 63 | mapState, 64 | { 65 | increment: incrementCounter, 66 | } 67 | ) 68 | ); 69 | 70 | // we should have the Redux store set in our providers 71 | const providers = { 72 | store: createStore(myReducer), 73 | }; 74 | 75 | // now we can create our instance 76 | const p = P(providers); 77 | 78 | console.log(p.props); 79 | // { 80 | // counter: 0, 81 | // increment: Function 82 | // } 83 | ``` 84 | 85 | # API 86 | 87 | ## withStore 88 | 89 | > withStore(mapState, mapDispatch, options) 90 | 91 | **Arguments:** 92 | 93 | * `mapState` (`Function`): Accepting Store's state, and returning mapped state 94 | * `mapDispatch` (`Object`): Action creators keyed by their names 95 | * `options` (`Object` [optional]) 96 | * `options.providerName` (`String`): The provider name, defaults to `store` 97 | -------------------------------------------------------------------------------- /packages/proppy-redux/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/src', 4 | ], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testRegex: '\\.spec\\.(ts|tsx)', 9 | moduleFileExtensions: [ 10 | 'ts', 11 | 'tsx', 12 | 'js', 13 | 'json', 14 | 'node', 15 | ], 16 | collectCoverage: false, 17 | bail: true, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/proppy-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-redux", 3 | "version": "1.3.1", 4 | "description": "Redux integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-redux", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "proppy": "^1.3.1", 32 | "redux": "^4.0.0" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/fahad19/proppy/issues" 36 | }, 37 | "license": "MIT", 38 | "types": "./lib/index.d.ts" 39 | } 40 | -------------------------------------------------------------------------------- /packages/proppy-redux/src/index.ts: -------------------------------------------------------------------------------- 1 | export { withStore } from './withStore'; 2 | -------------------------------------------------------------------------------- /packages/proppy-redux/src/withStore.ts: -------------------------------------------------------------------------------- 1 | import { create, ProppyFactory } from 'proppy'; 2 | 3 | const defaultOptions = { 4 | providerName: 'store', 5 | }; 6 | 7 | export function withStore( 8 | mapState, 9 | mapDispatch, 10 | options = defaultOptions 11 | ): ProppyFactory { 12 | return create({ 13 | initialize() { 14 | this._store = this.providers[options.providerName]; 15 | 16 | if (!this._store) { 17 | throw new Error('Store not found'); 18 | } 19 | 20 | if (mapState) { 21 | this.set(mapState.apply(this, [ 22 | this._store.getState(), 23 | this.providers, 24 | this.props, 25 | ])); 26 | } 27 | 28 | if (mapDispatch) { 29 | Object.keys(mapDispatch).forEach(actionName => { 30 | const actionCreator = mapDispatch[actionName]; 31 | 32 | this.set({ 33 | [actionName]: (...args) => this._store.dispatch( 34 | actionCreator(...args), 35 | ), 36 | }); 37 | }); 38 | } 39 | }, 40 | 41 | didSubscribe() { 42 | if (mapState) { 43 | this._storeSubscription = this._store.subscribe(() => { 44 | this.set(mapState.apply(this, [ 45 | this._store.getState(), 46 | this.providers, 47 | this.props, 48 | ])); 49 | }); 50 | } 51 | }, 52 | 53 | handleReceivedProps(parentProps) { 54 | if (mapState) { 55 | this.set(parentProps); 56 | this.set(mapState.apply(this, [ 57 | this._store.getState(), 58 | this.providers, 59 | this.props, 60 | ])); 61 | } 62 | }, 63 | 64 | willDestroy() { 65 | if (this._storeSubscription) { 66 | this._storeSubscription(); 67 | } 68 | }, 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /packages/proppy-redux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy-redux/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' 7 | ? 'production' 8 | : 'development'; 9 | 10 | const filename = NODE_ENV === 'production' 11 | ? '[name].min.js' 12 | : '[name].js'; 13 | 14 | const plugins = [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 17 | }), 18 | ]; 19 | 20 | if (NODE_ENV === 'production') { 21 | plugins.push(new CompressionPlugin({ 22 | asset: '[path].gz[query]', 23 | algorithm: 'gzip', 24 | test: /\.js/, 25 | })); 26 | } 27 | 28 | module.exports = { 29 | entry: { 30 | 'proppy-redux': path.join(__dirname, '/src/index.ts'), 31 | }, 32 | output: { 33 | path: path.join(__dirname, 'dist'), 34 | filename: filename, 35 | libraryTarget: 'umd', 36 | library: 'ProppyRedux', 37 | }, 38 | externals: { 39 | proppy: 'Proppy', 40 | redux: 'Redux', 41 | }, 42 | mode: process.env.NODE_ENV || 'development', 43 | devtool: 'source-map', 44 | plugins: plugins, 45 | performance: { 46 | hints: false, 47 | }, 48 | resolve: { 49 | extensions: [ 50 | '.ts', 51 | '.tsx', 52 | '.js', 53 | ], 54 | }, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.(ts|tsx)$/, 59 | exclude: /(node_modules)/, 60 | loader: 'ts-loader', 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/proppy-redux/withStore.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withStore'); 2 | -------------------------------------------------------------------------------- /packages/proppy-rx/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-rx/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-rx/README.md: -------------------------------------------------------------------------------- 1 | # proppy-rx 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-rx.svg)](https://www.npmjs.com/package/proppy-rx) 4 | 5 | > RxJS integration package for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Interop with RxJS](#interop-with-rxjs) 13 | - [API](#api) 14 | - [withStream](#withstream) 15 | - [from](#from) 16 | 17 | 18 | 19 | --- 20 | 21 | # Guide 22 | 23 | ## Installation 24 | 25 | With [npm](https://www.npmjs.com/): 26 | 27 | ``` 28 | $ npm install --save proppy rxjs proppy-rx 29 | ``` 30 | 31 | Via [unpkg](https://unpkg.com) CDN: 32 | 33 | ```html 34 | 35 | 36 | 37 | 40 | ``` 41 | 42 | ## Usage 43 | 44 | With the `withStream` function, you can map an incoming stream of props and return a new stream: 45 | 46 | ```js 47 | import { compose, withProps } from 'proppy'; 48 | import { withStream } from 'proppy-rx'; 49 | import { map } from 'rxjs/operators'; 50 | 51 | const P = compose( 52 | withProps({ foo: 'foo value' }), 53 | withStream((props$, providers) => { 54 | // `props$` is an Observable here 55 | 56 | // let's now return a stream of props here 57 | return props$.pipe( 58 | map(props => props), 59 | ); 60 | }), 61 | ); 62 | ``` 63 | 64 | ## Interop with RxJS 65 | 66 | You can also convert your instance to a props stream using RxJS: 67 | 68 | ```js 69 | import { from } from 'proppy-rx'; 70 | 71 | const props$ = from(p); 72 | ``` 73 | 74 | 75 | # API 76 | 77 | ## withStream 78 | 79 | > withStream((props$, providers) => props$) 80 | 81 | Used for taking an incoming stream of props, and returning a new stream. 82 | 83 | ## from 84 | 85 | > from(p) 86 | 87 | Returns an Observable of props. 88 | -------------------------------------------------------------------------------- /packages/proppy-rx/from.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/from'); 2 | -------------------------------------------------------------------------------- /packages/proppy-rx/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/src', 4 | ], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testRegex: '\\.spec\\.(ts|tsx)', 9 | moduleFileExtensions: [ 10 | 'ts', 11 | 'tsx', 12 | 'js', 13 | 'json', 14 | 'node', 15 | ], 16 | collectCoverage: false, 17 | bail: true, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/proppy-rx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-rx", 3 | "version": "1.3.1", 4 | "description": "RxJS integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-rx", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "proppy": "^1.3.1", 32 | "rxjs": "^6.0.0" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/fahad19/proppy/issues" 36 | }, 37 | "license": "MIT", 38 | "types": "./lib/index.d.ts" 39 | } 40 | -------------------------------------------------------------------------------- /packages/proppy-rx/src/from.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { map } from 'rxjs/operators'; 3 | import { withState } from 'proppy'; 4 | 5 | import { from } from './from'; 6 | 7 | describe('proppy-rx :: from', () => { 8 | test('is a function', () => { 9 | expect(typeof from).toEqual('function'); 10 | }); 11 | 12 | test('subscription triggers with Observable', () => { 13 | const P = withState('counter', 'setCounter', 0); 14 | const p = P(); 15 | const props$ = from(p); 16 | 17 | let triggerCount = 0; 18 | 19 | props$.subscribe(() => { 20 | triggerCount++; 21 | }); 22 | 23 | p.props.setCounter(1); 24 | p.props.setCounter(2); 25 | p.props.setCounter(3); 26 | 27 | expect(triggerCount).toEqual(4); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/proppy-rx/src/from.ts: -------------------------------------------------------------------------------- 1 | import { Proppy } from 'proppy'; 2 | import { Observable } from 'rxjs'; 3 | 4 | export function from(p: Proppy) { 5 | return new Observable((observer) => { 6 | return p.subscribe(props => observer.next(props)); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/proppy-rx/src/index.ts: -------------------------------------------------------------------------------- 1 | export { from } from './from'; 2 | export { withStream } from './withStream'; 3 | -------------------------------------------------------------------------------- /packages/proppy-rx/src/withStream.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { map } from 'rxjs/operators'; 3 | import { compose, withState } from 'proppy'; 4 | 5 | import { withStream } from './withStream'; 6 | 7 | describe('proppy-rx :: withStream', () => { 8 | test('is a function', () => { 9 | expect(typeof withStream).toEqual('function'); 10 | }); 11 | 12 | test('updates', () => { 13 | const P = compose( 14 | withState('counter', 'setCounter', 0), 15 | withStream(props$ => props$.pipe( 16 | map((props: any) => ({ 17 | ...props, 18 | counter: props.counter * 10, 19 | })), 20 | )), 21 | ); 22 | 23 | const p = P(); 24 | 25 | p.subscribe(() => {}); 26 | 27 | expect(p.props.counter).toEqual(0); 28 | 29 | p.props.setCounter(5); 30 | 31 | expect(p.props.counter).toEqual(50); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/proppy-rx/src/withStream.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory, create } from 'proppy'; 2 | import { of } from 'rxjs'; 3 | 4 | import { from } from './from'; 5 | 6 | const defaultOptions = { 7 | replace: false, 8 | }; 9 | 10 | export function withStream(fn, givenOptions = {}): ProppyFactory { 11 | const options = { 12 | ...defaultOptions, 13 | ...givenOptions, 14 | }; 15 | 16 | return create({ 17 | initialize() { 18 | const parent$ = this.parent 19 | ? from(this.parent) 20 | : of({}); 21 | 22 | this._$ = fn(parent$); 23 | }, 24 | 25 | didSubscribe() { 26 | this._s = this._$.subscribe(props => { 27 | this.set(props, options.replace); 28 | }); 29 | }, 30 | 31 | willDestroy() { 32 | this._s.unsubscribe(); 33 | }, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /packages/proppy-rx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy-rx/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' 7 | ? 'production' 8 | : 'development'; 9 | 10 | const filename = NODE_ENV === 'production' 11 | ? '[name].min.js' 12 | : '[name].js'; 13 | 14 | const plugins = [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 17 | }), 18 | ]; 19 | 20 | if (NODE_ENV === 'production') { 21 | plugins.push(new CompressionPlugin({ 22 | asset: '[path].gz[query]', 23 | algorithm: 'gzip', 24 | test: /\.js/, 25 | })); 26 | } 27 | 28 | module.exports = { 29 | entry: { 30 | 'proppy-rx': path.join(__dirname, '/src/index.ts'), 31 | }, 32 | output: { 33 | path: path.join(__dirname, 'dist'), 34 | filename: filename, 35 | libraryTarget: 'umd', 36 | library: 'ProppyRx', 37 | }, 38 | externals: { 39 | proppy: 'Proppy', 40 | rxjs: 'Rx', 41 | }, 42 | mode: process.env.NODE_ENV || 'development', 43 | devtool: 'source-map', 44 | plugins: plugins, 45 | performance: { 46 | hints: false, 47 | }, 48 | resolve: { 49 | extensions: [ 50 | '.ts', 51 | '.tsx', 52 | '.js', 53 | ], 54 | }, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.(ts|tsx)$/, 59 | exclude: /(node_modules)/, 60 | loader: 'ts-loader', 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/proppy-rx/withStream.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withStream'); 2 | -------------------------------------------------------------------------------- /packages/proppy-vue/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy-vue/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy-vue/README.md: -------------------------------------------------------------------------------- 1 | # proppy-vue 2 | 3 | [![npm](https://img.shields.io/npm/v/proppy-vue.svg)](https://www.npmjs.com/package/proppy-vue) 4 | 5 | > React integration package for ProppyJS 6 | 7 | 8 | 9 | - [Guide](#guide) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [API](#api) 13 | - [attach](#attach) 14 | 15 | 16 | 17 | --- 18 | 19 | # Guide 20 | 21 | ## Installation 22 | 23 | With [npm](https://www.npmjs.com/): 24 | 25 | ``` 26 | $ npm install --save proppy vue proppy-vue 27 | ``` 28 | 29 | Via [unpkg](https://unpkg.com) CDN: 30 | 31 | ```html 32 | 33 | 34 | 35 | 38 | ``` 39 | 40 | ## Usage 41 | 42 | Render your Vue.js app with providers data: 43 | 44 | ```js 45 | // index.js 46 | import Vue from 'vue'; 47 | 48 | import MyComponent from './components/MyComponent'; 49 | 50 | const providers = { 51 | foo: 'foo value', 52 | }; 53 | 54 | const app = new Vue({ 55 | provide: { 56 | proppy: providers, 57 | }, 58 | 59 | render(h) { 60 | return ; 61 | }, 62 | }); 63 | ``` 64 | 65 | Now anywhere from your components tree, you can use the `attach` higher-order component: 66 | 67 | ```js 68 | // components/MyComponent.js 69 | import { compose, withProps } from 'proppy'; 70 | import { attach } from 'proppy-vue'; 71 | 72 | const P = compose( 73 | withProps((props, providers) => ({ 74 | foo: providers.foo, 75 | })), 76 | withProps({ bar: 'bar value' }), 77 | ); 78 | 79 | const MyComponent = { 80 | name: 'MyComponent', 81 | 82 | props: ['foo', 'bar'], 83 | 84 | render(h) { 85 | const { foo, bar } = this; 86 | 87 | return

; 88 | }, 89 | }; 90 | 91 | export default attach(P)(MyComponent); 92 | ``` 93 | 94 | # API 95 | 96 | ## attach 97 | 98 | > attach(P)(Component) 99 | 100 | Higher-order component for attaching your Proppy factory to your Component. 101 | -------------------------------------------------------------------------------- /packages/proppy-vue/attach.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/attach'); 2 | -------------------------------------------------------------------------------- /packages/proppy-vue/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/src', 4 | ], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testRegex: '\\.spec\\.(ts|tsx)', 9 | moduleFileExtensions: [ 10 | 'ts', 11 | 'tsx', 12 | 'js', 13 | 'json', 14 | 'node', 15 | ], 16 | collectCoverage: false, 17 | bail: true, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/proppy-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy-vue", 3 | "version": "1.3.1", 4 | "description": "Vue.js integration with ProppyJS", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy-vue", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "@vue/test-utils": "^1.0.0-beta.15", 32 | "ajv": "^6.4.0", 33 | "proppy": "^1.3.1", 34 | "vue": "^2.5.16", 35 | "vue-template-compiler": "^2.5.16" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/fahad19/proppy/issues" 39 | }, 40 | "license": "MIT", 41 | "types": "./lib/index.d.ts" 42 | } 43 | -------------------------------------------------------------------------------- /packages/proppy-vue/src/attach.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'proppy'; 2 | 3 | export function attach(ProppyFactory) { 4 | return function(Component) { 5 | const componentPropNames = Component.props || []; 6 | 7 | const watch = componentPropNames.reduce((acc, propName) => { 8 | acc[propName] = function(newVal, oldVal) { 9 | if (typeof newVal !== 'undefined') { 10 | this._parent.set({ 11 | [propName]: newVal 12 | }); 13 | } 14 | }; 15 | 16 | return acc; 17 | }, {}); 18 | 19 | return { 20 | name: `Attached${Component.name}`, 21 | 22 | inject: ['proppy'], 23 | 24 | props: componentPropNames, 25 | 26 | watch, 27 | 28 | data() { 29 | return { 30 | proppyProps: {} 31 | }; 32 | }, 33 | 34 | methods: { 35 | setProppyProps(props) { 36 | this.proppyProps = Object.assign({}, props); 37 | } 38 | }, 39 | 40 | beforeMount() { 41 | const parentProps = componentPropNames.reduce( 42 | (acc, componentPropName) => { 43 | if (typeof this[componentPropName] === 'undefined') { 44 | return acc; 45 | } 46 | 47 | return { 48 | ...acc, 49 | [componentPropName]: this[componentPropName] 50 | }; 51 | }, 52 | {} 53 | ); 54 | 55 | this._parent = create({ 56 | initialize() { 57 | this.set(parentProps); 58 | } 59 | })(this.proppy || {}); 60 | 61 | this._p = ProppyFactory(this.proppy, this._parent); 62 | 63 | this.setProppyProps(this._p.props); 64 | 65 | this._p.subscribe(props => { 66 | this.setProppyProps(props); 67 | }); 68 | }, 69 | 70 | beforeDestroy() { 71 | if (this._p) { 72 | this._p.destroy(); 73 | } 74 | }, 75 | 76 | render(h) { 77 | const data = { 78 | props: this.proppyProps 79 | }; 80 | 81 | return h(Component, data); 82 | }, 83 | }; 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /packages/proppy-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | export { attach } from './attach'; 2 | -------------------------------------------------------------------------------- /packages/proppy-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy-vue/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' 7 | ? 'production' 8 | : 'development'; 9 | 10 | const filename = NODE_ENV === 'production' 11 | ? '[name].min.js' 12 | : '[name].js'; 13 | 14 | const plugins = [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 17 | }), 18 | ]; 19 | 20 | if (NODE_ENV === 'production') { 21 | plugins.push(new CompressionPlugin({ 22 | asset: '[path].gz[query]', 23 | algorithm: 'gzip', 24 | test: /\.js/, 25 | })); 26 | } 27 | 28 | module.exports = { 29 | entry: { 30 | 'proppy-vue': path.join(__dirname, '/src/index.ts'), 31 | }, 32 | output: { 33 | path: path.join(__dirname, 'dist'), 34 | filename: filename, 35 | libraryTarget: 'umd', 36 | library: 'ProppyVue', 37 | }, 38 | externals: { 39 | proppy: 'Proppy', 40 | vue: 'Vue', 41 | }, 42 | mode: process.env.NODE_ENV || 'development', 43 | devtool: 'source-map', 44 | plugins: plugins, 45 | performance: { 46 | hints: false, 47 | }, 48 | resolve: { 49 | extensions: [ 50 | '.ts', 51 | '.tsx', 52 | '.js', 53 | ], 54 | }, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.(ts|tsx)$/, 59 | exclude: /(node_modules)/, 60 | loader: 'ts-loader', 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/proppy/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | lib 4 | dist 5 | -------------------------------------------------------------------------------- /packages/proppy/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | coverage 3 | dist 4 | !lib 5 | -------------------------------------------------------------------------------- /packages/proppy/compose.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/compose'); 2 | -------------------------------------------------------------------------------- /packages/proppy/create.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/create'); 2 | -------------------------------------------------------------------------------- /packages/proppy/didSubscribe.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/didSubscribe'); 2 | -------------------------------------------------------------------------------- /packages/proppy/emit.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/emit'); 2 | -------------------------------------------------------------------------------- /packages/proppy/handleReceivedProps.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/handleReceivedProps'); 2 | -------------------------------------------------------------------------------- /packages/proppy/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/src', 4 | ], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testRegex: '\\.spec\\.(ts|tsx)', 9 | moduleFileExtensions: [ 10 | 'ts', 11 | 'tsx', 12 | 'js', 13 | 'json', 14 | 'node', 15 | ], 16 | collectCoverage: false, 17 | bail: true, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/proppy/map.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/map'); 2 | -------------------------------------------------------------------------------- /packages/proppy/onChange.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/onChange'); 2 | -------------------------------------------------------------------------------- /packages/proppy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proppy", 3 | "version": "1.3.1", 4 | "description": "ProppyJS - Functional composition of props for components", 5 | "main": "lib/index.js", 6 | "homepage": "https://github.com/fahad19/proppy/tree/master/packages/proppy", 7 | "scripts": { 8 | "clean": "../../node_modules/.bin/rimraf dist lib", 9 | "lint": "../../node_modules/.bin/tslint -c ../../tslint.json -p tsconfig.json -t stylish", 10 | "transpile": "../../node_modules/.bin/tsc", 11 | "test": "../../node_modules/.bin/jest --", 12 | "cover": "../../node_modules/.bin/jest --coverage", 13 | "dist:lib": "../../node_modules/.bin/webpack --config ./webpack.config.js", 14 | "dist:min": "NODE_ENV=production ../../node_modules/.bin/webpack --config ./webpack.config.js", 15 | "dist": "npm run dist:lib && npm run dist:min", 16 | "prepublish": "npm run transpile", 17 | "build": "npm run transpile && npm run dist" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/fahad19/proppy.git" 22 | }, 23 | "author": { 24 | "name": "Fahad Ibnay Heylaal", 25 | "url": "http://fahad19.com" 26 | }, 27 | "keywords": [ 28 | "proppy" 29 | ], 30 | "devDependencies": { 31 | "rxjs": "^6.1.0" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/fahad19/proppy/issues" 35 | }, 36 | "license": "MIT", 37 | "types": "./lib/index.d.ts" 38 | } 39 | -------------------------------------------------------------------------------- /packages/proppy/shouldUpdate.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/shouldUpdate'); 2 | -------------------------------------------------------------------------------- /packages/proppy/src/compose.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withProps } from './withProps'; 4 | import { withState } from './withState'; 5 | import { map } from './map'; 6 | import { shouldUpdate } from './shouldUpdate'; 7 | 8 | describe('proppy :: compose', () => { 9 | test('is a function', () => { 10 | expect(typeof compose).toEqual('function'); 11 | }); 12 | 13 | test('with multiple defaults', () => { 14 | const P = compose( 15 | withProps({ foo: 'foo value' }), 16 | withProps({ bar: 'bar value' }), 17 | withProps((props, { baz }) => ({ baz })), 18 | ); 19 | 20 | const p = P({ 21 | baz: 'baz value', 22 | }); 23 | 24 | expect(p.props).toEqual({ 25 | foo: 'foo value', 26 | bar: 'bar value', 27 | baz: 'baz value', 28 | }); 29 | }); 30 | 31 | test('with multiple states', () => { 32 | const P = compose( 33 | withState('counter', 'setCounter', 0), 34 | withState('age', 'setAge', 50), 35 | compose( 36 | withState('score', 'setScore', 0), 37 | shouldUpdate( 38 | (prevProps, nextProps) => nextProps.score % 2 === 0, 39 | ), 40 | ), 41 | map(props => ({ 42 | ...props, 43 | score: props.score * 10, 44 | })), 45 | ); 46 | 47 | const p = P(); 48 | 49 | expect(p.props.counter).toEqual(0); 50 | expect(typeof p.props.setCounter).toEqual('function'); 51 | 52 | expect(p.props.age).toEqual(50); 53 | expect(typeof p.props.setAge).toEqual('function'); 54 | 55 | expect(p.props.score).toEqual(0); 56 | expect(typeof p.props.setScore).toEqual('function'); 57 | 58 | let triggerCount = 0; 59 | p.subscribe(() => { 60 | triggerCount++; 61 | }); 62 | 63 | p.props.setCounter(1); 64 | expect(p.props.counter).toEqual(1); 65 | p.props.setCounter(2); 66 | expect(p.props.counter).toEqual(2); 67 | 68 | p.props.setAge(55); 69 | expect(p.props.age).toEqual(55); 70 | 71 | expect(triggerCount).toEqual(4); 72 | 73 | expect(p.props.counter).toEqual(2); 74 | expect(typeof p.props.setCounter).toEqual('function'); 75 | expect(p.props.age).toEqual(55); 76 | expect(typeof p.props.setAge).toEqual('function'); 77 | 78 | p.props.setScore(1); 79 | expect(p.props.score).toEqual(0); 80 | 81 | p.props.setScore(2); 82 | expect(p.props.score).toEqual(20); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /packages/proppy/src/compose.ts: -------------------------------------------------------------------------------- 1 | import { Proppy, ProppyFactory, Providers } from './types'; 2 | 3 | export function compose(...funcs): ProppyFactory { 4 | if (funcs.length === 0) { 5 | throw new Error('Cannot compose without any ProppyJS factories'); 6 | } 7 | 8 | if (funcs.length === 1) { 9 | return funcs[0]; 10 | } 11 | 12 | return function (providers: Providers = {}, parent: Proppy|null = null) { 13 | let f = funcs[0](providers, parent); 14 | 15 | for (let i = 1; i < funcs.length; i++) { 16 | f = funcs[i](providers, f); 17 | } 18 | 19 | return f; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/proppy/src/create.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Proppy, 3 | ProppyFactory, 4 | ProppyFactoryOptions, 5 | } from './types'; 6 | 7 | function notify(callbacks, props) { 8 | callbacks.forEach(cb => cb(props)); 9 | } 10 | 11 | function defaultParentHandler() { 12 | this.set.apply(this, arguments); 13 | } 14 | 15 | function getParentHandler(parent: null|Proppy, options: ProppyFactoryOptions) { 16 | if ( 17 | !parent || 18 | options.handleReceivedProps === false 19 | ) { 20 | return; 21 | } 22 | 23 | if ( 24 | options.handleReceivedProps === true || 25 | options.handleReceivedProps === undefined 26 | ) { 27 | return defaultParentHandler; 28 | } 29 | 30 | return options.handleReceivedProps; 31 | } 32 | 33 | export function create(options: ProppyFactoryOptions): ProppyFactory { 34 | return function (providers = {}, parent): Proppy { 35 | let callbacks = []; 36 | let hasSubscribed = false; 37 | const parentHandler = getParentHandler(parent, options); 38 | let parentSubscription; 39 | 40 | const p: Proppy = { 41 | props: parent 42 | ? Object.assign({}, parent.props) 43 | : {}, 44 | parent, 45 | providers, 46 | }; 47 | 48 | p.set = function (props, replace = false) { 49 | p.props = replace 50 | ? Object.assign({}, props) 51 | : Object.assign({}, p.props, props); 52 | 53 | return notify(callbacks, p.props); 54 | }; 55 | 56 | p.subscribe = function (cb) { 57 | if (!hasSubscribed) { 58 | hasSubscribed = true; 59 | 60 | if (options.didSubscribe) { 61 | options.didSubscribe.apply(p); 62 | } 63 | } 64 | 65 | callbacks.push(cb); 66 | cb(p.props); 67 | 68 | return function () { 69 | callbacks = callbacks.filter(callback => cb !== callback); 70 | }; 71 | }; 72 | 73 | p.destroy = function () { 74 | if (options.willDestroy) { 75 | options.willDestroy.apply(p); 76 | } 77 | 78 | if (parentSubscription) { 79 | parentSubscription(); 80 | parent.destroy(); 81 | } 82 | 83 | callbacks = []; 84 | }; 85 | 86 | if (options.initialize) { 87 | options.initialize.apply(p); 88 | } 89 | 90 | if (parentHandler) { 91 | parentSubscription = parent.subscribe( 92 | props => parentHandler.apply(p, [props]), 93 | ); 94 | } 95 | 96 | return p; 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /packages/proppy/src/didSubscribe.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { compose } from './compose'; 5 | import { withState } from './withState'; 6 | import { didSubscribe } from './didSubscribe'; 7 | 8 | describe('proppy :: didSubscribe', () => { 9 | test('is a function', () => { 10 | expect(typeof didSubscribe).toEqual('function'); 11 | }); 12 | 13 | test('updates', () => { 14 | let called = false; 15 | 16 | const P = compose( 17 | withState('counter', 'setCounter', 0), 18 | didSubscribe((props) => { 19 | props.setCounter(1); 20 | 21 | return () => { 22 | called = true; 23 | }; 24 | }), 25 | ); 26 | const p = P(); 27 | 28 | p.subscribe(() => {}); 29 | 30 | expect(p.props.counter).toEqual(1); 31 | expect(typeof p.props.setCounter).toEqual('function'); 32 | expect(called).toEqual(false); 33 | 34 | p.destroy(); 35 | expect(called).toEqual(true); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/proppy/src/didSubscribe.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function didSubscribe(fn): ProppyFactory { 5 | return create({ 6 | didSubscribe: function () { 7 | this._u = fn(this.props, this.providers); 8 | }, 9 | 10 | willDestroy() { 11 | if (typeof this._u === 'function') { 12 | this._u(this.props, this.providers); 13 | } 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/proppy/src/emit.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { emit } from './emit'; 5 | 6 | describe('proppy :: emit', () => { 7 | test('is a function', () => { 8 | expect(typeof emit).toEqual('function'); 9 | }); 10 | 11 | test('initial state', () => { 12 | let cancelled = false; 13 | 14 | const P = emit(function (cb) { 15 | cb({ counter: 1 }); 16 | cb({ counter: 2 }); 17 | 18 | return (props) => { 19 | cancelled = props; 20 | }; 21 | }); 22 | const p = P(); 23 | 24 | expect(p.props).toEqual({}); 25 | 26 | p.subscribe(() => {}); 27 | 28 | expect(p.props).toEqual({ counter: 2 }); 29 | expect(cancelled).toEqual(false); 30 | 31 | p.destroy(); 32 | expect(cancelled).toEqual({ counter: 2 }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/proppy/src/emit.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function emit(fn): ProppyFactory { 5 | return create({ 6 | didSubscribe() { 7 | const cb = (...args) => this.set(...args); 8 | 9 | this._cancel = fn(cb, this.props, this.providers); 10 | }, 11 | 12 | willDestroy() { 13 | if (typeof this._cancel === 'function') { 14 | this._cancel(this.props); 15 | } 16 | } 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy/src/handleReceivedProps.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { handleReceivedProps } from './handleReceivedProps'; 5 | import { compose } from './compose'; 6 | import { emit } from './emit'; 7 | 8 | describe('proppy :: handleReceivedProps', () => { 9 | test('is a function', () => { 10 | expect(typeof handleReceivedProps).toEqual('function'); 11 | }); 12 | 13 | test('updates', () => { 14 | let cancelled = false; 15 | 16 | const P = compose( 17 | emit(function (cb) { 18 | cb({ counter: 1 }); 19 | cb({ counter: 2 }); 20 | 21 | return (props) => { 22 | cancelled = props; 23 | }; 24 | }), 25 | handleReceivedProps(false), 26 | ); 27 | const p = P(); 28 | 29 | expect(p.props).toEqual({}); 30 | 31 | p.subscribe(() => {}); 32 | 33 | expect(p.props).toEqual({}); 34 | 35 | p.destroy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/proppy/src/handleReceivedProps.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function handleReceivedProps(fn): ProppyFactory { 5 | return create({ 6 | handleReceivedProps: fn, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/proppy/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Proppy, 3 | ProppyFactory, 4 | ProppyFactoryOptions, 5 | Props, 6 | Providers, 7 | } from './types'; 8 | 9 | export { compose } from './compose'; 10 | 11 | export { create } from './create'; 12 | 13 | export { map } from './map'; 14 | export { shouldUpdate } from './shouldUpdate'; 15 | export { withHandlers } from './withHandlers'; 16 | export { withObservable } from './withObservable'; 17 | export { withProps } from './withProps'; 18 | export { withReducer } from './withReducer'; 19 | export { withState } from './withState'; 20 | export { withStateHandlers } from './withStateHandlers'; 21 | export { withTimer } from './withTimer'; 22 | export { onChange } from './onChange'; 23 | export { didSubscribe } from './didSubscribe'; 24 | export { willDestroy } from './willDestroy'; 25 | export { emit } from './emit'; 26 | export { handleReceivedProps } from './handleReceivedProps'; 27 | -------------------------------------------------------------------------------- /packages/proppy/src/map.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withProps } from './withProps'; 4 | import { withState } from './withState'; 5 | import { map } from './map'; 6 | 7 | describe('proppy :: map', () => { 8 | test('is a function', () => { 9 | expect(typeof map).toEqual('function'); 10 | }); 11 | 12 | test('with plain object', () => { 13 | const P = compose( 14 | withProps({ counter: 10 }), 15 | map(props => ({ 16 | counter: props.counter * 10, 17 | })), 18 | ); 19 | const p = P(); 20 | 21 | expect(p.props).toEqual({ 22 | counter: 100, 23 | }); 24 | }); 25 | 26 | test('with state', () => { 27 | const P = compose( 28 | withProps({ staticCounter: 10 }), 29 | withState('counter', 'setCounter', 0), 30 | map(props => ({ 31 | ...props, 32 | staticCounter: props.staticCounter * 10, 33 | counter: props.counter * 10, 34 | })), 35 | ); 36 | const p = P(); 37 | 38 | expect(p.props.counter).toEqual(0); 39 | expect(p.props.staticCounter).toEqual(100); 40 | 41 | p.props.setCounter(5); 42 | 43 | expect(p.props.counter).toEqual(50); 44 | expect(p.props.staticCounter).toEqual(100); 45 | }); 46 | 47 | test('subscription', () => { 48 | expect.assertions(2); 49 | 50 | const P = compose( 51 | withProps({ counter: 10 }), 52 | map(props => ({ 53 | counter: props.counter * 10, 54 | })), 55 | ); 56 | const p = P(); 57 | 58 | expect(p.props).toEqual({ 59 | counter: 100, 60 | }); 61 | 62 | p.subscribe(props => { 63 | expect(props).toEqual({ 64 | counter: 100, 65 | }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/proppy/src/map.ts: -------------------------------------------------------------------------------- 1 | import { Proppy, ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function map(mapperFn): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | this.set(mapperFn(this.props, this.providers), true); 8 | }, 9 | 10 | handleReceivedProps(parentProps) { 11 | this.set(mapperFn(parentProps, this.providers), true); 12 | }, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/proppy/src/onChange.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withProps } from './withProps'; 4 | import { withState } from './withState'; 5 | import { onChange } from './onChange'; 6 | 7 | describe('proppy :: onChange', () => { 8 | test('is a function', () => { 9 | expect(typeof onChange).toEqual('function'); 10 | }); 11 | 12 | test('changes by string', () => { 13 | const P = compose( 14 | withProps({ foo: 'initial foo' }), 15 | withState('counter', 'setCounter', 0), 16 | onChange('counter', (props) => ({ 17 | foo: `changed foo with counter ${props.counter}` 18 | })), 19 | ); 20 | const p = P(); 21 | 22 | expect(p.props.foo).toEqual('initial foo'); 23 | expect(p.props.counter).toEqual(0); 24 | 25 | p.props.setCounter(5); 26 | expect(p.props.foo).toEqual('changed foo with counter 5'); 27 | }); 28 | 29 | test('changes by comparison', () => { 30 | const P = compose( 31 | withProps({ foo: 'initial foo' }), 32 | withState('counter', 'setCounter', 0), 33 | onChange( 34 | (prevProps, nextProps) => prevProps.counter !== nextProps.counter, 35 | (props) => ({ 36 | foo: `changed foo with counter ${props.counter}` 37 | }) 38 | ), 39 | ); 40 | const p = P(); 41 | 42 | expect(p.props.foo).toEqual('initial foo'); 43 | expect(p.props.counter).toEqual(0); 44 | 45 | p.props.setCounter(5); 46 | expect(p.props.counter).toEqual(5); 47 | expect(p.props.foo).toEqual('changed foo with counter 5'); 48 | 49 | // should still be same since no change 50 | p.props.setCounter(5); 51 | expect(p.props.counter).toEqual(5); 52 | expect(p.props.foo).toEqual('changed foo with counter 5'); 53 | 54 | p.props.setCounter(10); 55 | expect(p.props.counter).toEqual(10); 56 | expect(p.props.foo).toEqual('changed foo with counter 10'); 57 | }); 58 | 59 | test('changes by string, returning async props', () => { 60 | const P = compose( 61 | withState('foo', 'setFoo', 'initial foo'), 62 | withState('counter', 'setCounter', 0), 63 | onChange('counter', (props, providers, cb) => { 64 | cb({ 65 | foo: `changed foo with counter ${props.counter}` 66 | }); 67 | }), 68 | ); 69 | const p = P(); 70 | 71 | expect(p.props.foo).toEqual('initial foo'); 72 | expect(p.props.counter).toEqual(0); 73 | 74 | p.props.setCounter(5); 75 | expect(p.props.foo).toEqual('changed foo with counter 5'); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /packages/proppy/src/onChange.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function onChange(propName, fn): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | this._prevProps = this.props; 8 | this._own = {}; 9 | 10 | this._i = false; 11 | }, 12 | 13 | handleReceivedProps(parentProps) { 14 | if (!this._i) { 15 | this._i = true; 16 | 17 | return; 18 | } 19 | 20 | const detector = typeof propName === 'string' 21 | ? (p, n) => p[propName] !== n[propName] 22 | : propName; 23 | 24 | this.set(Object.assign({}, parentProps, this._own)); 25 | 26 | const cb = newProps => { 27 | this._own = newProps; 28 | this.set(newProps); 29 | }; 30 | 31 | if (detector(this._prevProps, parentProps)) { 32 | const result = fn(parentProps, this.providers, cb); 33 | 34 | if (result) { 35 | cb(result); 36 | } 37 | } 38 | 39 | this._prevProps = this.props; 40 | }, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/proppy/src/shouldUpdate.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withState } from './withState'; 4 | import { shouldUpdate } from './shouldUpdate'; 5 | 6 | describe('proppy :: shouldUpdate', () => { 7 | test('is a function', () => { 8 | expect(typeof shouldUpdate).toEqual('function'); 9 | }); 10 | 11 | test('with state', () => { 12 | const P = compose( 13 | withState('counter', 'setCounter', 0), 14 | shouldUpdate( 15 | (prevProps, nextProps) => nextProps.counter % 2 === 0, 16 | ), 17 | ); 18 | const p = P(); 19 | 20 | expect(p.props.counter).toEqual(0); 21 | 22 | // shouldn NOT update 23 | p.props.setCounter(1); 24 | expect(p.props.counter).toEqual(0); 25 | 26 | // should update 27 | p.props.setCounter(2); 28 | expect(p.props.counter).toEqual(2); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/proppy/src/shouldUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function shouldUpdate(fn): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | this._prevProps = this.props; 8 | }, 9 | 10 | handleReceivedProps(parentProps) { 11 | if (fn(this._prevProps, parentProps, this.providers)) { 12 | this.set(parentProps, true); 13 | } 14 | }, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/proppy/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Props = any; 2 | export type Providers = any; 3 | export type SetFunction = (props: Props, replace?: boolean) => any; 4 | export type UnsubscribeFunction = () => void; 5 | export type SubscribeFunction = (cb: any) => UnsubscribeFunction; 6 | export type DestroyFunction = () => any; 7 | 8 | export interface Proppy { 9 | parent?: Proppy|null; 10 | providers?: Providers; 11 | props?: Props; 12 | set?: SetFunction; 13 | subscribe?: SubscribeFunction; 14 | destroy?: DestroyFunction; 15 | } 16 | 17 | export type ProppyFactory = (providers?: Providers, parent?: Proppy|null) => Proppy; 18 | 19 | export type InitializeFunction = () => void; 20 | export type DidSubscribeFunction = () => void; 21 | export type WillDestroyFunction = () => void; 22 | export type HandleReceivedPropsFunction = (props) => void; 23 | 24 | export interface ProppyFactoryOptions { 25 | initialize?: InitializeFunction; 26 | didSubscribe?: DidSubscribeFunction; 27 | willDestroy?: WillDestroyFunction; 28 | handleReceivedProps?: boolean|HandleReceivedPropsFunction; 29 | } 30 | -------------------------------------------------------------------------------- /packages/proppy/src/willDestroy.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { compose } from './compose'; 5 | import { withState } from './withState'; 6 | import { willDestroy } from './willDestroy'; 7 | 8 | describe('proppy :: willDestroy', () => { 9 | test('is a function', () => { 10 | expect(typeof willDestroy).toEqual('function'); 11 | }); 12 | 13 | test('updates', () => { 14 | let called = false; 15 | 16 | const P = compose( 17 | withState('counter', 'setCounter', 0), 18 | willDestroy(() => { 19 | called = true; 20 | }), 21 | ); 22 | const p = P(); 23 | 24 | p.subscribe(() => {}); 25 | 26 | expect(p.props.counter).toEqual(0); 27 | expect(typeof p.props.setCounter).toEqual('function'); 28 | expect(called).toBe(false); 29 | 30 | p.destroy(); 31 | expect(called).toBe(true); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/proppy/src/willDestroy.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function willDestroy(fn): ProppyFactory { 5 | return create({ 6 | willDestroy: function () { 7 | fn(this.props, this.providers); 8 | }, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/proppy/src/withHandlers.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withState } from './withState'; 4 | import { withHandlers } from './withHandlers'; 5 | 6 | describe('proppy :: withHandlers', () => { 7 | test('is a function', () => { 8 | expect(typeof withHandlers).toEqual('function'); 9 | }); 10 | 11 | test('updates', () => { 12 | const P = compose( 13 | withState('counter', 'setCounter', 0), 14 | withHandlers({ 15 | increment: props => () => props.setCounter(props.counter + 1), 16 | decrement: props => () => props.setCounter(props.counter - 1), 17 | }), 18 | ); 19 | const p = P(); 20 | 21 | expect(p.props.counter).toEqual(0); 22 | expect(typeof p.props.increment).toEqual('function'); 23 | expect(typeof p.props.decrement).toEqual('function'); 24 | 25 | p.props.increment(); 26 | p.props.increment(); 27 | 28 | expect(p.props.counter).toEqual(2); 29 | 30 | p.props.decrement(); 31 | 32 | expect(p.props.counter).toEqual(1); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/proppy/src/withHandlers.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withHandlers(handlers): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | Object.keys(handlers).forEach((k) => { 8 | const v = handlers[k]; 9 | 10 | this.set({ 11 | [k]: (...args) => v( 12 | this.props, 13 | this.providers, 14 | )(...args), 15 | }); 16 | }); 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/proppy/src/withObservable.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { withObservable } from './withObservable'; 3 | import { Observable } from 'rxjs'; 4 | 5 | describe('proppy :: withObservable', () => { 6 | test('is a function', () => { 7 | expect(typeof withObservable).toEqual('function'); 8 | }); 9 | 10 | test('subscription', () => { 11 | expect.assertions(2); 12 | 13 | const P = withObservable(() => new Observable(observer => { 14 | observer.next({ 15 | foo: 'foo value', 16 | }); 17 | observer.complete(); 18 | })); 19 | const p = P(); 20 | 21 | expect(p.props).toEqual({}); 22 | 23 | p.subscribe(props => { 24 | expect(props).toEqual({ 25 | foo: 'foo value', 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/proppy/src/withObservable.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withObservable(stream$): ProppyFactory { 5 | return create({ 6 | didSubscribe() { 7 | const observable = typeof stream$ === 'function' 8 | ? stream$(this.props, this.providers) 9 | : stream$; 10 | 11 | this._observableSubscription = observable.subscribe( 12 | props => this.set(props), 13 | ); 14 | }, 15 | 16 | willDestroy() { 17 | this._observableSubscription.unsubscribe(); 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/proppy/src/withProps.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { withProps } from './withProps'; 3 | 4 | describe('proppy :: withProps', () => { 5 | test('is a function', () => { 6 | expect(typeof withProps).toEqual('function'); 7 | }); 8 | 9 | test('with plain object', () => { 10 | const P = withProps({ 11 | foo: 'foo value', 12 | }); 13 | const p = P(); 14 | 15 | expect(p.props).toEqual({ 16 | foo: 'foo value', 17 | }); 18 | }); 19 | 20 | test('with function returning plain object', () => { 21 | const P = withProps(() => ({ 22 | foo: 'foo value', 23 | })); 24 | const p = P(); 25 | 26 | expect(p.props).toEqual({ 27 | foo: 'foo value', 28 | }); 29 | }); 30 | 31 | test('with function accessing args returning plain object', () => { 32 | const P = withProps((props, { bar }) => ({ 33 | foo: 'foo value', 34 | bar, 35 | })); 36 | const p = P({ 37 | bar: 'bar value', 38 | }); 39 | 40 | expect(p.props).toEqual({ 41 | foo: 'foo value', 42 | bar: 'bar value', 43 | }); 44 | }); 45 | 46 | test('subscription', () => { 47 | expect.assertions(2); 48 | 49 | const P = withProps(() => ({ 50 | foo: 'foo value', 51 | })); 52 | const p = P(); 53 | 54 | expect(p.props).toEqual({ 55 | foo: 'foo value', 56 | }); 57 | 58 | p.subscribe(props => { 59 | expect(props).toEqual({ 60 | foo: 'foo value', 61 | }); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/proppy/src/withProps.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withProps(defaults: any = {}): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | const props = typeof defaults === 'function' 8 | ? defaults(this.props, this.providers) 9 | : defaults; 10 | 11 | this.set(props); 12 | }, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/proppy/src/withReducer.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { withReducer } from './withReducer'; 5 | 6 | describe('proppy :: withReducer', () => { 7 | test('is a function', () => { 8 | expect(typeof withReducer).toEqual('function'); 9 | }); 10 | 11 | function testReducer(state, action) { 12 | switch (action.type) { 13 | case 'INCREMENT': 14 | return { value: state.value + 1 }; 15 | case 'DECREMENT': 16 | return { value: state.value - 1 }; 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | test('initial state', () => { 23 | const P = withReducer('counter', 'dispatch', testReducer, { value: 0 }); 24 | const p = P(); 25 | 26 | expect(p.props.counter.value).toEqual(0); 27 | expect(typeof p.props.dispatch).toEqual('function'); 28 | }); 29 | 30 | test('initial subscription', () => { 31 | expect.assertions(3); 32 | 33 | const P = withReducer('counter', 'dispatch', testReducer, { value: 0 }); 34 | const p = P(); 35 | 36 | expect(p.props.counter.value).toEqual(0); 37 | expect(typeof p.props.dispatch).toEqual('function'); 38 | 39 | p.subscribe(props => { 40 | expect(props.counter.value).toEqual(0); 41 | }); 42 | }); 43 | 44 | test('updates', () => { 45 | const P = withReducer('counter', 'dispatch', testReducer, { value: 0 }); 46 | const p = P({ foo: 'foo value' }); 47 | 48 | expect(p.props.counter.value).toEqual(0); 49 | expect(typeof p.props.dispatch).toEqual('function'); 50 | 51 | p.props.dispatch({ type: 'INCREMENT' }); 52 | expect(p.props.counter.value).toEqual(1); 53 | 54 | p.props.dispatch({ type: 'INCREMENT' }); 55 | expect(p.props.counter.value).toEqual(2); 56 | 57 | p.props.dispatch({ type: 'DECREMENT' }); 58 | expect(p.props.counter.value).toEqual(1); 59 | 60 | p.props.dispatch({ type: 'I_DONT_EXIST' }); 61 | expect(p.props.counter.value).toEqual(1); 62 | 63 | p.props.dispatch((props, providers) => { 64 | expect(p.props.counter.value).toEqual(1); 65 | expect(providers).toEqual({ foo: 'foo value' }); 66 | 67 | return { type: 'INCREMENT' }; 68 | }); 69 | expect(p.props.counter.value).toEqual(2); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/proppy/src/withReducer.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withReducer(stateName, dispatcherName, reducer, initialState): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | this.props[stateName] = initialState; 8 | this.props[dispatcherName] = (action) => { 9 | const payload = typeof action === 'function' 10 | ? action(this.props, this.providers) 11 | : action; 12 | 13 | this.set({ 14 | [stateName]: reducer(this.props[stateName], payload), 15 | }); 16 | }; 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/proppy/src/withState.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { from } from 'rxjs'; 3 | 4 | import { withState } from './withState'; 5 | 6 | describe('proppy :: withState', () => { 7 | test('is a function', () => { 8 | expect(typeof withState).toEqual('function'); 9 | }); 10 | 11 | test('initial state', () => { 12 | const P = withState('counter', 'setCounter', 0); 13 | const p = P(); 14 | 15 | expect(p.props.counter).toEqual(0); 16 | expect(typeof p.props.setCounter).toEqual('function'); 17 | }); 18 | 19 | test('initial subscription', () => { 20 | expect.assertions(3); 21 | 22 | const P = withState('counter', 'setCounter', 0); 23 | const p = P(); 24 | 25 | expect(p.props.counter).toEqual(0); 26 | expect(typeof p.props.setCounter).toEqual('function'); 27 | 28 | p.subscribe(props => { 29 | expect(props.counter).toEqual(0); 30 | }); 31 | }); 32 | 33 | test('updates', () => { 34 | const P = withState('counter', 'setCounter', 0); 35 | const p = P(); 36 | 37 | expect(p.props.counter).toEqual(0); 38 | expect(typeof p.props.setCounter).toEqual('function'); 39 | 40 | p.props.setCounter(1); 41 | expect(p.props.counter).toEqual(1); 42 | 43 | p.props.setCounter(2); 44 | expect(p.props.counter).toEqual(2); 45 | 46 | p.props.setCounter(3); 47 | expect(p.props.counter).toEqual(3); 48 | }); 49 | 50 | test('subscription triggers', () => { 51 | const P = withState('counter', 'setCounter', 0); 52 | const p = P(); 53 | 54 | let triggerCount = 0; 55 | p.subscribe(() => { 56 | triggerCount++; 57 | }); 58 | 59 | p.props.setCounter(1); 60 | p.props.setCounter(2); 61 | p.props.setCounter(3); 62 | 63 | expect(triggerCount).toEqual(4); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/proppy/src/withState.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withState(stateName, setterName, initialState): ProppyFactory { 5 | return create({ 6 | initialize() { 7 | this.props[stateName] = initialState; 8 | this.props[setterName] = (value) => { 9 | this.set({ 10 | [stateName]: value, 11 | }); 12 | }; 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/proppy/src/withStateHandlers.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { withStateHandlers } from './withStateHandlers'; 3 | 4 | describe('proppy :: withStateHandlers', () => { 5 | test('is a function', () => { 6 | expect(typeof withStateHandlers).toEqual('function'); 7 | }); 8 | 9 | test('updates', () => { 10 | const P = withStateHandlers( 11 | { counter: 0 }, 12 | { 13 | increment: ({ counter }) => () => ({ counter: counter + 1 }), 14 | decrement: ({ counter }) => () => ({ counter: counter - 1 }), 15 | }, 16 | ); 17 | const p = P(); 18 | 19 | expect(p.props.counter).toEqual(0); 20 | expect(typeof p.props.increment).toEqual('function'); 21 | expect(typeof p.props.decrement).toEqual('function'); 22 | 23 | p.props.increment(); 24 | p.props.increment(); 25 | 26 | expect(p.props.counter).toEqual(2); 27 | 28 | p.props.decrement(); 29 | 30 | expect(p.props.counter).toEqual(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/proppy/src/withStateHandlers.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withStateHandlers( 5 | initialState, 6 | handlers, 7 | acceptReceivedProps = true 8 | ): ProppyFactory { 9 | return create({ 10 | initialize() { 11 | this.set(initialState, !acceptReceivedProps); 12 | 13 | Object.keys(handlers).forEach((k) => { 14 | const v = handlers[k]; 15 | 16 | this.set({ 17 | [k]: (...args) => this.set( 18 | v( 19 | this.props, 20 | this.providers, 21 | )(...args), 22 | ), 23 | }); 24 | }); 25 | }, 26 | 27 | handleReceivedProps: acceptReceivedProps, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /packages/proppy/src/withTimer.spec.ts: -------------------------------------------------------------------------------- 1 | /* global describe, test, expect */ 2 | import { compose } from './compose'; 3 | import { withTimer } from './withTimer'; 4 | 5 | describe('proppy :: withTimer', () => { 6 | test('is a function', () => { 7 | expect(typeof withTimer).toEqual('function'); 8 | }); 9 | 10 | test('updates', (done) => { 11 | const P = compose( 12 | withTimer(100, { foo: 'foo value' }), 13 | withTimer(100, (props, providers) => ({ 14 | foo: props.foo, 15 | bar: providers.bar, 16 | })), 17 | ); 18 | 19 | const p = P({ 20 | bar: 'bar value', 21 | }); 22 | 23 | p.subscribe(() => {}); 24 | 25 | expect(p.props).toEqual({}); 26 | 27 | setTimeout(() => { 28 | expect(p.props).toEqual({ 29 | foo: 'foo value', 30 | bar: 'bar value', 31 | }); 32 | 33 | p.destroy(); 34 | done(); 35 | }, 150); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/proppy/src/withTimer.ts: -------------------------------------------------------------------------------- 1 | import { ProppyFactory } from './types'; 2 | import { create } from './create'; 3 | 4 | export function withTimer(timer: number, defaults: any = {}): ProppyFactory { 5 | return create({ 6 | didSubscribe() { 7 | this._t = setTimeout(() => { 8 | const props = typeof defaults === 'function' 9 | ? defaults(this.props, this.providers) 10 | : defaults; 11 | 12 | this.set(props); 13 | }, timer); 14 | }, 15 | 16 | willDestroy() { 17 | clearTimeout(this._t); 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/proppy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es5", 5 | "lib": [ 6 | "es2015", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "jsx": "react" 13 | }, 14 | "include": [ 15 | "./src/**/*.ts" 16 | ], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/proppy/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const CompressionPlugin = require('compression-webpack-plugin'); 5 | 6 | const NODE_ENV = process.env.NODE_ENV === 'production' 7 | ? 'production' 8 | : 'development'; 9 | 10 | const filename = NODE_ENV === 'production' 11 | ? '[name].min.js' 12 | : '[name].js'; 13 | 14 | const plugins = [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 17 | }), 18 | ]; 19 | 20 | if (NODE_ENV === 'production') { 21 | plugins.push(new CompressionPlugin({ 22 | asset: '[path].gz[query]', 23 | algorithm: 'gzip', 24 | test: /\.js/, 25 | })); 26 | } 27 | 28 | module.exports = { 29 | entry: { 30 | 'proppy': path.join(__dirname, '/src/index.ts'), 31 | }, 32 | output: { 33 | path: path.join(__dirname, 'dist'), 34 | filename: filename, 35 | libraryTarget: 'window', 36 | library: 'Proppy', 37 | }, 38 | externals: {}, 39 | mode: process.env.NODE_ENV || 'development', 40 | devtool: 'source-map', 41 | plugins: plugins, 42 | performance: { 43 | hints: false, 44 | }, 45 | resolve: { 46 | extensions: [ 47 | '.ts', 48 | '.tsx', 49 | '.js', 50 | ], 51 | }, 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.(ts|tsx)$/, 56 | exclude: /(node_modules)/, 57 | loader: 'ts-loader', 58 | }, 59 | ], 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /packages/proppy/willDestroy.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/willDestroy'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withHandlers.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withHandlers'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withObservable.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withObservable'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withProps.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withProps'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withReducer.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withReducer'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withState.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withState'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withStateHandlers.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withStateHandlers'); 2 | -------------------------------------------------------------------------------- /packages/proppy/withTimer.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/withTimer'); 2 | -------------------------------------------------------------------------------- /site/assets/css/content.scss: -------------------------------------------------------------------------------- 1 | .content.main { 2 | font-size: 16px; 3 | 4 | h1, 5 | h2, 6 | h3, 7 | h4, 8 | h5, 9 | h6 { 10 | position: relative; 11 | } 12 | 13 | a.permalink { 14 | color: inherit; 15 | } 16 | 17 | a { 18 | border-bottom: 1px solid rgba(0,0,0,0.2); 19 | 20 | &, 21 | & code { 22 | background-color: rgba(187,239,253,0.3); 23 | color: #1a1a1a; 24 | } 25 | 26 | &:hover { 27 | background-color: #bbeffd; 28 | border-bottom-color: #1a1a1a; 29 | } 30 | 31 | &.has-image { 32 | background: none; 33 | color: transparent; 34 | border: none; 35 | } 36 | } 37 | 38 | &.column { 39 | p a, 40 | li a { 41 | // border-bottom: 1px solid #ccc; 42 | font-weight: 600; 43 | } 44 | } 45 | 46 | h1 { 47 | border-bottom: 1px solid #eee; 48 | padding-bottom: 18px; 49 | font-size: 36px !important; 50 | line-height: 50px !important; 51 | } 52 | 53 | h2 { 54 | font-size: 32px !important; 55 | border-bottom: 1px solid #eee; 56 | padding-bottom: 18px; 57 | } 58 | 59 | p > code { 60 | color: inherit; 61 | } 62 | } 63 | 64 | // resetting bulma for prism themes 65 | pre, 66 | pre[class*="language-"] { 67 | padding: 0; 68 | border: 1px solid #eee; 69 | border-radius: 2px; 70 | margin-bottom: 15px; 71 | font-size: 13px; 72 | line-height: 18px; 73 | 74 | .tag { 75 | align-items: center; 76 | background-color: transparent; 77 | border-radius: 0; 78 | color: auto; 79 | display: inline-block; 80 | font-size: 100%; 81 | height: auto; 82 | justify-content: center; 83 | line-height: 18px; 84 | padding-left: 0; 85 | padding-right: 0; 86 | vertical-align: top; 87 | white-space: nowrap; 88 | } 89 | 90 | .number { 91 | align-items: center; 92 | background-color: transparent; 93 | border-radius: 0; 94 | display: inline-block; 95 | font-size: 100%; 96 | height: auto; 97 | justify-content: center; 98 | margin-right: 0; 99 | min-width: 0; 100 | padding: 0; 101 | text-align: center; 102 | vertical-align: top; 103 | } 104 | } 105 | 106 | pre[class*="language-"] { 107 | border: 1px solid #f6e4cc; 108 | } 109 | 110 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 111 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 112 | background: #afd5fe; 113 | } 114 | 115 | .line-highlight { 116 | margin-top: 18px; 117 | min-height: 24px; 118 | 119 | &:before, 120 | &:after { 121 | display: none; 122 | } 123 | } 124 | 125 | pre[class*="language-"] { 126 | &, 127 | & code, 128 | .line-highlight { 129 | line-height: 18px; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /site/assets/css/flow.scss: -------------------------------------------------------------------------------- 1 | @keyframes bounceInDown { 2 | from, 3 | 60%, 4 | 75%, 5 | 90%, 6 | to { 7 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 8 | } 9 | 10 | 0% { 11 | opacity: 0; 12 | transform: translate3d(0, -280px, 0); 13 | } 14 | 15 | 60% { 16 | opacity: 1; 17 | transform: translate3d(0, 25px, 0); 18 | } 19 | 20 | 75% { 21 | transform: translate3d(0, -10px, 0); 22 | } 23 | 24 | 90% { 25 | transform: translate3d(0, 5px, 0); 26 | } 27 | 28 | to { 29 | transform: translate3d(0, 0, 0); 30 | } 31 | } 32 | 33 | .bounceInDown { 34 | animation-name: bounceInDown; 35 | } 36 | 37 | @keyframes bounceInUp { 38 | from, 39 | 60%, 40 | 75%, 41 | 90%, 42 | to { 43 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 44 | } 45 | 46 | from { 47 | opacity: 0; 48 | transform: translate3d(0, 280px, 0); 49 | } 50 | 51 | 60% { 52 | opacity: 1; 53 | transform: translate3d(0, -20px, 0); 54 | } 55 | 56 | 75% { 57 | transform: translate3d(0, 10px, 0); 58 | } 59 | 60 | 90% { 61 | transform: translate3d(0, -5px, 0); 62 | } 63 | 64 | to { 65 | transform: translate3d(0, 0, 0); 66 | } 67 | } 68 | 69 | .bounceInUp { 70 | animation-name: bounceInUp; 71 | } 72 | 73 | .animated { 74 | animation-duration: 0.3s; 75 | animation-fill-mode: both; 76 | } 77 | 78 | .flow, 79 | .examples, 80 | .benefits { 81 | margin-top: 40px; 82 | margin-bottom: 40px; 83 | 84 | h2 { 85 | text-align: center; 86 | font-weight: 800; 87 | font-size: 54px; 88 | border-bottom: 1px solid #eee; 89 | padding-bottom: 10px; 90 | margin-bottom: 30px; 91 | } 92 | 93 | pre { 94 | -webkit-overflow-scrolling: touch; 95 | overflow-x: auto; 96 | padding: 1.25em 1.5em; 97 | white-space: pre; 98 | word-wrap: normal; 99 | margin-top: 0; 100 | } 101 | 102 | .menu-list a { 103 | font-size: 24px; 104 | position: relative; 105 | 106 | img { 107 | width: 24px; 108 | position: relative; 109 | top: 3px; 110 | margin-right: 6px; 111 | } 112 | } 113 | 114 | .toggler { 115 | position: absolute; 116 | z-index: 5; 117 | right: 11px; 118 | /* border: 1px solid red; */ 119 | padding: 10px; 120 | border-bottom-left-radius: 4px; 121 | font-weight: 600; 122 | background: #f7e5cc; 123 | } 124 | } 125 | 126 | .flow { 127 | .box { 128 | position: relative; 129 | text-align: center; 130 | box-shadow: none; 131 | 132 | h3 { 133 | margin-bottom: 30px; 134 | font-weight: 600; 135 | text-transform: uppercase; 136 | } 137 | 138 | ul, 139 | li { 140 | list-style: none; 141 | padding: 0; 142 | margin: 0; 143 | } 144 | 145 | p { 146 | margin-top: 15px; 147 | text-transform: uppercase; 148 | } 149 | 150 | img { 151 | height: 140px; 152 | } 153 | 154 | i { 155 | position: absolute; 156 | font-size: 36px; 157 | top: 125px; 158 | } 159 | 160 | &.first { 161 | i { 162 | right: -24px; 163 | } 164 | } 165 | 166 | &.second { 167 | z-index: -1; 168 | } 169 | 170 | &.third { 171 | i { 172 | left: -24px; 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /site/assets/css/footer.scss: -------------------------------------------------------------------------------- 1 | footer.footer { 2 | border-top: 1px solid #eee; 3 | padding: 3rem 1.5rem 3rem; 4 | position: relative; 5 | 6 | .social-icons { 7 | font-size: 24px; 8 | 9 | .icon { 10 | padding: 10px 20px; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /site/assets/css/header.scss: -------------------------------------------------------------------------------- 1 | .hero { 2 | .subtitle { 3 | font-size: 24px; 4 | } 5 | 6 | .title { 7 | font-size: 36px; 8 | line-height: 52px; 9 | font-weight: 100; 10 | } 11 | 12 | .subtitle, 13 | .title { 14 | color: #777 !important; 15 | font-family: 'Open Sans', sans-serif; 16 | margin-bottom: 30px; 17 | 18 | span { 19 | color: #fff; 20 | } 21 | } 22 | 23 | .brand { 24 | 25 | } 26 | 27 | .hero-body { 28 | .button { 29 | border-radius: 50px; 30 | padding: 0px 24px; 31 | margin: 0 3px; 32 | text-transform: uppercase; 33 | font-weight: 600; 34 | font-size: 16px; 35 | letter-spacing: 0px; 36 | height: 48px; 37 | } 38 | 39 | .button.is-primary { 40 | background: #f6566c; 41 | box-shadow: 5px 5px 50px #e4498e; 42 | } 43 | 44 | .button.is-transparent { 45 | color: #fff; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /site/assets/css/main.scss: -------------------------------------------------------------------------------- 1 | @import "content"; 2 | @import "footer"; 3 | @import "global"; 4 | @import "header"; 5 | @import "flow"; 6 | -------------------------------------------------------------------------------- /site/assets/img/banner-trimmed-900.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/banner-trimmed-900.png -------------------------------------------------------------------------------- /site/assets/img/banner-trimmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/banner-trimmed.png -------------------------------------------------------------------------------- /site/assets/img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/banner.png -------------------------------------------------------------------------------- /site/assets/img/js-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/js-logo.png -------------------------------------------------------------------------------- /site/assets/img/logo-text-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/logo-text-80.png -------------------------------------------------------------------------------- /site/assets/img/logo-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/logo-text.png -------------------------------------------------------------------------------- /site/assets/img/logo-transparent-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/logo-transparent-128.png -------------------------------------------------------------------------------- /site/assets/img/logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/logo-transparent.png -------------------------------------------------------------------------------- /site/assets/img/preact-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/preact-logo.png -------------------------------------------------------------------------------- /site/assets/img/proppy-flow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/proppy-flow.gif -------------------------------------------------------------------------------- /site/assets/img/proppy-og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/proppy-og.png -------------------------------------------------------------------------------- /site/assets/img/proppy-square-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/proppy-square-128.png -------------------------------------------------------------------------------- /site/assets/img/proppy-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/proppy-square.png -------------------------------------------------------------------------------- /site/assets/img/react-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /site/assets/img/redux-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/redux-logo.png -------------------------------------------------------------------------------- /site/assets/img/rxjs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/rxjs-logo.png -------------------------------------------------------------------------------- /site/assets/img/vue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahad19/proppy/4623c751cce1a2bd926ba2b25a0b41c54797e403/site/assets/img/vue-logo.png -------------------------------------------------------------------------------- /site/content/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Documentation 3 | sidebarPartial: docsSidebar 4 | importContentFromRoot: README.md 5 | --- 6 | 7 | # Documentation 8 | 9 | -------------------------------------------------------------------------------- /site/content/docs/bundling.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bundling 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Bundling 7 | 8 | Even though most packages are pretty small, you can optimize your production bundles even further by only including the functions that your application uses. 9 | 10 | ## Tree shaking 11 | 12 | If you are bundling your application with Webpack, you can consider applying tree shaking: [https://webpack.js.org/guides/tree-shaking/](https://webpack.js.org/guides/tree-shaking/). 13 | 14 | ## Import individually 15 | 16 | If you are unable to benefit from tree shaking in your environment, you can also import the functions from Proppy packages individually: 17 | 18 | ```js 19 | import { compose } from 'proppy/compose'; 20 | import { withProps } from 'proppy/withProps'; 21 | 22 | import { attach } from 'proppy-react/attach'; 23 | ``` 24 | -------------------------------------------------------------------------------- /site/content/docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Change Log 3 | sidebarPartial: docsSidebar 4 | importContentFromRoot: CHANGELOG.md 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contribution Guide 3 | sidebarPartial: docsSidebar 4 | importContentFromRoot: CONTRIBUTING.md 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/examples/react-counter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Example: React Counter with and without Proppy" 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Example: React Counter with and without Proppy 7 | 8 | 9 | 10 | - [Without Proppy](#without-proppy) 11 | - [With Proppy](#with-proppy) 12 | 13 | 14 | 15 | A basic example of a React component: 16 | 17 | * Showing the `counter` value, and 18 | * An `Increment` button incrementing the counter's value by 1 19 | * Component re-renders only if `counter` is an even number 20 | 21 | ## Without Proppy 22 | 23 | ```js 24 | import React from 'react'; 25 | 26 | class MyComponent extends React.Component { 27 | constructor(props) { 28 | super(props); 29 | 30 | this.state = { 31 | counter: 0 32 | }; 33 | 34 | this.handleIncrement = this.handleIncrement.bind(this); 35 | } 36 | 37 | handleIncrement() { 38 | const { counter } = this.state; 39 | 40 | this.setState({ 41 | counter: counter + 1 42 | }); 43 | } 44 | 45 | // re-render only if `counter` is an even number 46 | shouldComponentUpdate(nextProps, nextState) { 47 | if (nextState.counter % 2 !== 0) { 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | render() { 55 | const { counter } = this.state; 56 | 57 | return ( 58 |
59 |

Counter: {counter}

60 | 61 | 64 |
65 | ); 66 | } 67 | } 68 | 69 | export default MyComponent; 70 | ``` 71 | 72 | ## With Proppy 73 | 74 | ```js 75 | import React from 'react'; 76 | import { compose, withStateHandlers, shouldUpdate } from 'proppy'; 77 | import { attach } from 'proppy-react'; 78 | 79 | const P = compose( 80 | withStateHandlers( 81 | { counter: 0 }, 82 | { handleIncrement: props => () => ({ counter: props.counter + 1 }) } 83 | ), 84 | shouldUpdate((prevProps, nextProps) => nextProps.counter % 2 === 0) 85 | ); 86 | 87 | function MyComponent({ counter, handleIncrement }) { 88 | return ( 89 |
90 |

Counter: {counter}

91 | 92 | 95 |
96 | ); 97 | } 98 | 99 | export default attach(P)(MyComponent); 100 | ``` 101 | -------------------------------------------------------------------------------- /site/content/docs/examples/react-fetch-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Example: React Fetch Request with and without Proppy" 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Example: React Fetch Request with and without Proppy 7 | 8 | 9 | 10 | - [Without Proppy](#without-proppy) 11 | - [With Proppy](#with-proppy) 12 | 13 | 14 | 15 | A basic example of a React component: 16 | 17 | * Receiving `productId` prop 18 | * Handler for fetching a Product by that ID from server 19 | * Fetch it when component is mounted 20 | * Render the Product's title 21 | * Refresh button to fetch the Product again 22 | * A loading indicator when request is in progress 23 | 24 | ## Without Proppy 25 | 26 | ```js 27 | import React from 'react'; 28 | 29 | class MyComponent extends React.Component { 30 | constructor(props) { 31 | super(props); 32 | 33 | this.state = { 34 | product: null, 35 | isLoading: false 36 | }; 37 | 38 | this.handleFetch = this.handleFetch.bind(this); 39 | } 40 | 41 | handleFetch(productId) { 42 | this.setState({ 43 | isLoading: true 44 | }); 45 | 46 | fetch(`/api/products/${productId}.json`) 47 | .then(res => res.json()) 48 | .then(res => this.setState({ 49 | product: res.data, 50 | isLoading: false 51 | })); 52 | } 53 | 54 | componentDidMount() { 55 | const { productId } = this.props; 56 | 57 | this.handleFetch(productId); 58 | } 59 | 60 | render() { 61 | const { productId } = this.props; 62 | const { product, isLoading } = this.state; 63 | 64 | return ( 65 |
66 | {loading &&

Loading...

} 67 | 68 |

Title: {product && product.title}

69 | 70 | 73 |
74 | ); 75 | } 76 | } 77 | 78 | export default MyComponent; 79 | ``` 80 | 81 | ## With Proppy 82 | 83 | ```js 84 | import React from 'react'; 85 | import { compose, withState, withHandlers, didSubscribe } from 'proppy'; 86 | import { attach } from 'proppy-react'; 87 | 88 | const P = compose( 89 | withState('product', 'setProduct', null), 90 | withState('isLoading', 'setLoading', false), 91 | withHandlers({ 92 | handleFetch: props => productId => { 93 | props.setLoading(true); 94 | 95 | return fetch(`/api/products/${productId}.json`) 96 | .then(res => { 97 | props.setProduct(res.data); 98 | props.setLoading(false); 99 | }); 100 | } 101 | }), 102 | didSubscribe(props => props.handleFetch(props.productId)) 103 | ); 104 | 105 | function MyComponent({ productId, product, isLoading, handleFetch }) { 106 | return ( 107 |
108 | {isLoading &&

Loading...

} 109 | 110 |

Title: {product && product.title}

111 | 112 | 115 |
116 | ); 117 | } 118 | 119 | export default attach(P)(MyComponent); 120 | ``` 121 | -------------------------------------------------------------------------------- /site/content/docs/examples/react-rxjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Example: React + RxJS with and without Proppy" 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Example: React + RxJS with and without Proppy 7 | 8 | 9 | 10 | - [Without Proppy](#without-proppy) 11 | - [With Proppy](#with-proppy) 12 | 13 | 14 | 15 | A basic example of a React component: 16 | 17 | * Showing `interval` coming from RxJS Observable 18 | * Interval starts as soon as component mounts 19 | * Component renders only when `interval` is an even number 20 | * Observable is unsubscribed when component is unmounted 21 | 22 | ## Without Proppy 23 | 24 | ```js 25 | import React from 'react'; 26 | import { interval } from 'rxjs'; 27 | import { map } from 'rxjs/operators'; 28 | 29 | class MyComponent extends React.Component { 30 | constructor(props) { 31 | super(props); 32 | 33 | this.state = { 34 | interval: 0 35 | }; 36 | } 37 | 38 | componentDidMount() { 39 | // emits an integer every 1 second 40 | const interval$ = interval(1000).pipe( 41 | map(n => ({ 42 | inverval: n 43 | })) 44 | ); 45 | 46 | this._subscription = interval$.subscribe( 47 | result => this.setState(result) 48 | ) 49 | } 50 | 51 | shouldComponentUpdate(nextProps, nextState) { 52 | if (nextState.interval % 2 !== 0) { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | componentWillUnmount() { 60 | this._subscription.unsubscribe(); 61 | } 62 | 63 | render() { 64 | const { interval } = this.state; 65 | 66 | return

Interval: {interval}

; 67 | } 68 | } 69 | 70 | export default MyComponent; 71 | ``` 72 | 73 | ## With Proppy 74 | 75 | ```js 76 | import React from 'react'; 77 | import { interval } from 'rxjs'; 78 | import { map } from 'rxjs/operators'; 79 | import { compose, withObservable, shouldUpdate } from 'proppy'; 80 | 81 | const P = compose( 82 | withObservable(props => interval(1000).pipe( 83 | map(n => ({ interval: n })) 84 | )), 85 | shouldUpdate((prevProps, nextProps) => nextProps.counter % 2 === 0) 86 | ); 87 | 88 | function MyComponent({ interval }) { 89 | return

Interval: {interval}

; 90 | } 91 | 92 | export default attach(P)(MyComponent); 93 | ``` 94 | 95 | For advanced usage, look into `proppy-rx` package, when you can also access incoming props as an Observable. 96 | -------------------------------------------------------------------------------- /site/content/docs/examples/vue-counter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Example: Vue.js Counter with Proppy" 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Comparison: Vue.js Counter with Proppy 7 | 8 | 9 | 10 | - [Without Proppy](#without-proppy) 11 | - [With Proppy](#with-proppy) 12 | 13 | 14 | 15 | A basic example of a Vue.js component: 16 | 17 | * Showing the `counter` value, and 18 | * An `Increment` button incrementing the counter's value by 1 19 | * Component re-renders upon changes 20 | 21 | ## Without Proppy 22 | 23 | ```js 24 | const MyComponent = { 25 | data: function () { 26 | return { 27 | counter: 0 28 | }; 29 | }, 30 | 31 | methods: { 32 | handleIncrement: function () { 33 | const currentCounter = this.counter; 34 | 35 | this.counter = currentCounter + 1; 36 | } 37 | }, 38 | 39 | render(h) { 40 | return ( 41 |
42 |

Counter: {this.counter}

43 | 44 | 47 |
48 | ); 49 | } 50 | }; 51 | 52 | export default MyComponent; 53 | ``` 54 | 55 | ## With Proppy 56 | 57 | ```js 58 | import { withStateHandlers } from 'proppy'; 59 | import { attach } from 'proppy-vue'; 60 | 61 | const P = withStateHandlers( 62 | { counter: 0 }, 63 | { handleIncrement: props => () => ({ counter: props.counter + 1 }) } 64 | ); 65 | 66 | const MyComponent = { 67 | props: ['counter', 'handleIncrement'], 68 | 69 | render(h) { 70 | return ( 71 |
72 |

Counter: {this.counter}

73 | 74 | 77 |
78 | ); 79 | } 80 | }; 81 | 82 | export default attach(P)(MyComponent); 83 | ``` 84 | -------------------------------------------------------------------------------- /site/content/docs/factory-instance.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Factory & Instance 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Factory & Instance 7 | 8 | 9 | 10 | - [Factory](#factory) 11 | - [Instance](#instance) 12 | 13 | 14 | 15 | ## Factory 16 | 17 | Proppy factories are functions that return you the Proppy instance. 18 | 19 | The factories optionally accept a common `providers` object which can be used by the instances as they see fit. 20 | 21 | In the example below, `P` is a factory: 22 | 23 | ```js 24 | const P = function (providers = {}) { 25 | return instance; 26 | } 27 | ``` 28 | 29 | We will learn about the API of the instance below: 30 | 31 | ## Instance 32 | 33 | Proppy instances allow you to access the props both synchronously, as well as asynchronously via subscription: 34 | 35 | ```js 36 | const p = P(); 37 | 38 | // current props (synchronous) 39 | console.log(p.props); 40 | 41 | // subscription (asynchronous) 42 | // will emit as many times the props get updated 43 | const unsubscribe = p.subscribe(props => console.log(props)); 44 | unsubscribe(); 45 | 46 | // destroy 47 | // all listeners are unsubscribed and internal state is cleared 48 | p.destroy(); 49 | ``` 50 | 51 | The library itself is unopinionated how you implement your factories and instances yourself. But it comes with a handy [`create`](../packages/proppy#create) function that helps you to create your own instances quickly. 52 | 53 | It's best if you read the [quick start](../quickstart) guide next. 54 | -------------------------------------------------------------------------------- /site/content/docs/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Frequently Asked Questions 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # FAQ 7 | 8 | 9 | 10 | - [What is the primary feature of Proppy?](#what-is-the-primary-feature-of-proppy) 11 | - [Can I use Proppy with React, Vue.js, and Preact?](#can-i-use-proppy-with-react-vuejs-and-preact) 12 | - [Is Proppy a Redux alternative?](#is-proppy-a-redux-alternative) 13 | - [How is it different than Redux?](#how-is-it-different-than-redux) 14 | - [What is the difference between Proppy and Recompose?](#what-is-the-difference-between-proppy-and-recompose) 15 | - [Can I use RxJS with Proppy?](#can-i-use-rxjs-with-proppy) 16 | - [Is there any cost in performance?](#is-there-any-cost-in-performance) 17 | 18 | 19 | 20 | ## What is the primary feature of Proppy? 21 | 22 | It just helps you compose your props, that you can use anywhere later. Nothing more. 23 | 24 | ## Can I use Proppy with React, Vue.js, and Preact? 25 | 26 | Yes! We have integrations for all of them: 27 | 28 | * [`proppy-react`](../packages/proppy-react) 29 | * [`proppy-vue`](../packages/proppy-vue) 30 | * [`proppy-preact`](../packages/proppy-preact) 31 | 32 | ## Is Proppy a Redux alternative? 33 | 34 | Not at all. We have an integration package for ease of Redux usage too: [`proppy-redux`](../packages/proppy-redux). 35 | 36 | ## How is it different than Redux? 37 | 38 | [Redux](https://redux.js.org/) is meant for state management for your whole application, based on actions and reducers. 39 | 40 | Proppy is aimed at lifting the logic one level above your Components, and only deal with the behaviour of their props. 41 | 42 | While [react-redux](https://github.com/reduxjs/react-redux) allows you to access your single Store from anywhere in the components tree, Proppy allows you to access [providers](../providers). 43 | 44 | And the Redux store, for example, can be one of the providers here. 45 | 46 | Proppy is unopinionated about what you use for your application's state management, and doesn't lock you in to any specific state management or rendering library. 47 | 48 | ## What is the difference between Proppy and Recompose? 49 | 50 | [Recompose](https://github.com/acdlite/recompose) has been the original inspiration for building ProppyJS. 51 | 52 | A few key differences: 53 | 54 | * Not tied to any rendering libraries (like React or Vue.js) 55 | * Allows access to application-wide dependencies (like Redux store, config, etc) 56 | * Deals with only props generation 57 | * Doesn’t create a new component in tree per function 58 | 59 | ## Can I use RxJS with Proppy? 60 | 61 | Absolutely! At the end of the day Proppy only composes props, and allows you to subscribe to them too. 62 | 63 | RxJS fits in very nicely here. With [`proppy-rx`](../packages/proppy-rx) package, you have access to incoming props stream as an Observable, and you are in full control of what props get output via returning Obsevable. 64 | 65 | ## Is there any cost in performance? 66 | 67 | When you use Proppy with any rendering library, you end up wrapping your base components with the `attach` higher-order component. 68 | 69 | This means every time you use the `attach` higher-order component, you are creating one more stateful component which takes care of creating the Proppy instance internally and keep passing the generated props to the base component. 70 | 71 | This layer of abstraction has its costs, because it is adding one more component to your tree. But at the same time, Proppy arms you with functions like `shouldUpdate` which helps you control when new props are passed down to your base component trigerring a re-render. 72 | 73 | In the end, it can be considered that the benefits outweigh any performance cost that is introduced because of this additional component in your tree. 74 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy-preact.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy-preact 3 | importContentFromPackage: proppy-preact 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy-react 3 | importContentFromPackage: proppy-react 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy-redux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy-redux 3 | importContentFromPackage: proppy-redux 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy-rx.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy-rx 3 | importContentFromPackage: proppy-rx 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy-vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy-vue 3 | importContentFromPackage: proppy-vue 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/packages/proppy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: proppy 3 | importContentFromPackage: proppy 4 | sidebarPartial: docsSidebar 5 | --- 6 | -------------------------------------------------------------------------------- /site/content/docs/playground.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Playgound 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Playgound 7 | 8 | Live examples of ProppyJS: 9 | 10 | * React: 11 | * [Source](https://github.com/fahad19/proppy/tree/master/examples/react-playground) 12 | * [CodeSandbox](https://codesandbox.io/s/github/fahad19/proppy/tree/master/examples/react-playground) 13 | * Preact: 14 | * [Source](https://github.com/fahad19/proppy/tree/master/examples/preact-playground) 15 | * [CodeSandbox](https://codesandbox.io/s/github/fahad19/proppy/tree/master/examples/preact-playground) 16 | -------------------------------------------------------------------------------- /site/content/docs/props.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Props 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Props 7 | 8 | Props are read-only objects, and the concept is popularized by rendering libraries like [React](https://reactjs.org/docs/components-and-props.html) and [Vue.js](https://vuejs.org/v2/guide/components-props.html). 9 | 10 | The data available in props are used to render components. 11 | 12 | For example, if you happen to have a `Counter` component that renders the counter's value, it may receive a props object in this shape: 13 | 14 | ```js 15 | const props = { counter: 1 }; 16 | ``` 17 | 18 | In a React example, the props are used like this: 19 | 20 | ```js 21 | // components/Counter.js 22 | import React from 'react'; 23 | 24 | function Counter(props) { 25 | return

Counter value: {props.counter}

; 26 | } 27 | ``` 28 | 29 | ## Handlers 30 | 31 | Props may also contain handlers (functions) which may trigger an update of the props, resulting in your Component being re-rendered. 32 | 33 | Keeping the previous example in mind, we can say that the `Counter` component could receive props in this shape too: 34 | 35 | ```js 36 | { 37 | counter: 1, 38 | setCounter: Function 39 | } 40 | ``` 41 | 42 | The `setCounter` handler could be triggered via an `onClick` event handler for example: 43 | 44 | ```js 45 | // components/Counter.js 46 | import React from 'react'; 47 | 48 | function Counter(props) { 49 | const { counter, setCounter } = props; 50 | 51 | return ( 52 |
53 |

Counter value: {counter}

54 | 55 | 58 |
59 | ); 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /site/content/docs/providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Providers 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Providers 7 | 8 | Providers are common stateful dependencies for your whole application. 9 | 10 | A few examples of such providers include: 11 | 12 | * Configuration 13 | * [Redux](https://redux.js.org/) store 14 | * Current theme 15 | * ...more 16 | 17 | The providers can be expressed in a single object here: 18 | 19 | ```js 20 | const providers = { 21 | store: reduxStore, 22 | config: {}, 23 | theme: 'light' 24 | }; 25 | ``` 26 | 27 | The idea is to share this single `providers` object globally in your application. 28 | -------------------------------------------------------------------------------- /site/content/docs/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing 3 | sidebarPartial: docsSidebar 4 | --- 5 | 6 | # Testing 7 | 8 | Unit testing Proppy is pretty straight forward because of it's functional nature. 9 | 10 | Assuming you are using Proppy and React together, your Component file may look like this: 11 | 12 | ```js 13 | // MyComponent.js 14 | import React from 'react'; 15 | import { compose, withProps } from 'proppy'; 16 | import { attach } from 'proppy-react'; 17 | 18 | const P = compose( 19 | withProps({ foo: 'foo value' }) 20 | ); 21 | 22 | function MyComponent(props) { 23 | return

; 24 | } 25 | 26 | export default attach(P)(MyComponent); 27 | ``` 28 | 29 | To make unit testing easier, we can change our original module to contain only named exports, so that we can import Proppy factory and the base stateless component separately: 30 | 31 | ```js 32 | // MyComponent.js 33 | import React from 'react'; 34 | import { compose, withProps } from 'proppy'; 35 | import { attach } from 'proppy-react'; 36 | 37 | export const P = compose( 38 | withProps({ foo: 'foo value' }) 39 | ); 40 | 41 | export function Base(props) { 42 | return

; 43 | } 44 | 45 | export const MyComponent = attach(P)(Base); 46 | ``` 47 | 48 | Now in our test file, we can import both the Proppy factory and the base component separately and unit test them: 49 | 50 | ```js 51 | // MyComponent.spec.js 52 | import { P, Base } from './MyComponent'; 53 | 54 | describe('MyComponent', () => { 55 | test('Proppy', () => { 56 | const mockedProviders = {}; 57 | const p = P(mockedProviders); 58 | 59 | expect(p.props).toEqual({ 60 | foo: 'foo value' 61 | }); 62 | 63 | p.destroy(); 64 | }); 65 | 66 | test('Component', () => { 67 | // test `Base` React component 68 | }); 69 | }) 70 | ``` 71 | -------------------------------------------------------------------------------- /site/data/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "description": "ProppyJS: JavaScript library for functional composition of props for UI components", 4 | "keywords": "javascript, library, react.js, vue.js, preact, rxjs, composition, props, framework" 5 | }, 6 | "og": { 7 | "title": "ProppyJS: Functional props composition for UI components", 8 | "description": "ProppyJS is a tiny 1.5kB JavaScript library for functionally composing props for your UI components. Supporting integrations with React.js, Vue.js, Preact, Redux, and RxJS.", 9 | "image": "/img/proppy-og.png", 10 | "image:width": "1600", 11 | "image:height": "630", 12 | "type": "website" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /site/data/sidebarLinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Intro", 4 | "links": [ 5 | { "title": "Introduction", "url": "/docs/introduction" } 6 | ] 7 | }, 8 | { 9 | "title": "Concepts", 10 | "links": [ 11 | { "title": "Props", "url": "/docs/props" }, 12 | { "title": "Providers", "url": "/docs/providers" }, 13 | { "title": "Factory & Instance", "url": "/docs/factory-instance" } 14 | ] 15 | }, 16 | { 17 | "title": "Getting started", 18 | "links": [ 19 | { "title": "Quick start", "url": "/docs/quickstart" } 20 | ] 21 | }, 22 | { 23 | "title": "Examples", 24 | "links": [ 25 | { "title": "React: Counter", "url": "/docs/examples/react-counter" }, 26 | { "title": "React: Fetch Request", "url": "/docs/examples/react-fetch-request" }, 27 | { "title": "React: Redux", "url": "/docs/examples/react-redux" }, 28 | { "title": "React: RxJS", "url": "/docs/examples/react-rxjs" }, 29 | { "title": "Vue.js: Counter", "url": "/docs/examples/vue-counter" } 30 | ] 31 | }, 32 | { 33 | "title": "Advanced", 34 | "links": [ 35 | { "title": "Lifecycle", "url": "/docs/lifecycle" }, 36 | { "title": "Testing", "url": "/docs/testing" }, 37 | { "title": "Bundling", "url": "/docs/bundling" } 38 | ] 39 | }, 40 | { 41 | "title": "Packages", 42 | "links": [ 43 | { "title": "proppy", "url": "/docs/packages/proppy" }, 44 | { "title": "proppy-react", "url": "/docs/packages/proppy-react" }, 45 | { "title": "proppy-vue", "url": "/docs/packages/proppy-vue" }, 46 | { "title": "proppy-preact", "url": "/docs/packages/proppy-preact" }, 47 | { "title": "proppy-redux", "url": "/docs/packages/proppy-redux" }, 48 | { "title": "proppy-rx", "url": "/docs/packages/proppy-rx" } 49 | ] 50 | }, 51 | { 52 | "title": "Project", 53 | "links": [ 54 | { "title": "API", "url": "/docs/api" }, 55 | { "title": "Playground", "url": "/docs/playground" }, 56 | { "title": "Change Log", "url": "/docs/changelog" }, 57 | { "title": "Contribution Guide", "url": "/docs/contributing" }, 58 | { "title": "FAQ", "url": "/docs/faq" } 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /site/layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> | ProppyJS 5 | <%= renderPartial('assets_head') %> 6 | 7 | 8 | 9 | 14 | 15 |
16 |
17 |
18 |
19 | <%= renderPartial(sidebarPartial) %> 20 |
21 | 22 |
23 | <%= contents %> 24 | 25 |
26 |
27 |
28 | <% if (prevLink && prevLink.title) { %> 29 | 36 | <% } %> 37 |
38 | 39 |
40 | <% if (nextLink && nextLink.title) { %> 41 | 48 | <% } %> 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | <%= renderPartial('footer') %> 58 | 59 | <%= renderPartial('assets_body') %> 60 | 61 | 62 | -------------------------------------------------------------------------------- /site/layouts/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> | Functional props composition for UI components 5 | <%= renderPartial('assets_head') %> 6 | 7 | 8 | 9 |
10 | 11 |
12 | 17 |
18 | 19 | 20 |
21 |
22 | ProppyJS 23 | 24 |

25 | Tiny 1.5kB utility library enabling 26 |

27 | 28 |

29 | Functional props composition for components 30 |

31 | 32 |
33 | Learn more 34 | Quick start 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 |

Flow of props

43 | 44 |
45 |
46 |
47 |

Various sources

48 | 49 |
    50 |
  • 51 | Redux 52 |

    Redux

    53 |
  • 54 | 55 |
  • 56 | JavaScript 57 |

    JavaScript

    58 |
  • 59 | 60 |
  • 61 | RxJS 62 |

    RxJS

    63 |
  • 64 |
65 | 66 | 67 |
68 |
69 | 70 |
71 |
72 |

Compose as props

73 | 74 | ProppyJS 75 | 76 |

Proppy

77 |
78 |
79 | 80 |
81 |
82 |

Pass to any Component

83 | 84 | 85 | 86 |
    87 |
  • 88 | React 89 |

    React

    90 |
  • 91 | 92 |
  • 93 | Vue 94 |

    Vue.js

    95 |
  • 96 | 97 |
  • 98 | Preact 99 |

    Preact

    100 |
  • 101 |
102 |
103 |
104 |
105 |
106 |
107 | 108 | <%= contents %> 109 | 110 | <%= renderPartial('footer') %> 111 | <%= renderPartial('assets_body') %> 112 | 113 | 114 | -------------------------------------------------------------------------------- /site/partials/assets_body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /site/partials/assets_head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% if (meta && meta.description) { %> 6 | 7 | <% } %> 8 | 9 | <% if (meta && meta.keywords) { %> 10 | 11 | <% } %> 12 | 13 | 14 | 15 | <% if (og && og.title) { %> 16 | 17 | <% } %> 18 | 19 | <% if (og && og.description) { %> 20 | 21 | <% } %> 22 | 23 | <% if (og && og.image) { %> 24 | 25 | <% } %> 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /site/partials/docsSidebar.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /site/partials/footer.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /site/partials/navLinks.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "rules": { 7 | "arrow-parens": false, 8 | "interface-name": false, 9 | "no-empty": false, 10 | "object-literal-sort-keys": false, 11 | "object-literal-shorthand": false, 12 | "only-arrow-functions": false, 13 | "ordered-imports": false, 14 | "quotemark": false, 15 | "space-before-function-paren": false, 16 | "trailing-comma": false, 17 | "variable-name": false 18 | } 19 | } 20 | --------------------------------------------------------------------------------