├── .gitignore ├── src ├── index.js ├── DelegateElement.js └── DelegateContainer.js ├── .babelrc ├── rollup.config.js ├── package.json ├── LICENSE.md ├── README.md └── lib └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import DelegateContainer from './DelegateContainer'; 2 | import DelegateElement from './DelegateElement'; 3 | 4 | export { DelegateContainer, DelegateElement }; -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false, 7 | "loose": true 8 | } 9 | ] 10 | ], 11 | "plugins": [ 12 | "external-helpers" 13 | ] 14 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | 3 | export default { 4 | entry: 'src/index.js', 5 | external: ['preact'], 6 | exports: 'named', 7 | format: 'cjs', 8 | sourceMap: false, 9 | 10 | plugins: [ 11 | babel({ 12 | exclude: 'node_modules/**' 13 | }) 14 | ] 15 | }; -------------------------------------------------------------------------------- /src/DelegateElement.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | 3 | export default class DelegateElement extends Component { 4 | componentDidMount() { 5 | const events = Object.assign({}, this.props); 6 | delete events.children; 7 | 8 | const { __delegateContainer__ } = this.context; 9 | 10 | __delegateContainer__.addEvents(events, this.base); 11 | } 12 | 13 | componentWillUnmount() { 14 | const events = Object.assign({}, this.props); 15 | delete events.children; 16 | 17 | const { __delegateContainer__ } = this.context; 18 | 19 | __delegateContainer__.removeEvents(events, this.base); 20 | } 21 | 22 | render({ children }) { 23 | return children[0]; 24 | } 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-delegate", 3 | "version": "1.1.1", 4 | "description": "Preact delegate DOM events", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "rollup -c -o ./lib/index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/NekR/preact-delegate.git" 13 | }, 14 | "keywords": [ 15 | "preact", 16 | "delegate", 17 | "dom", 18 | "events" 19 | ], 20 | "author": "Arthur Stolyar ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/NekR/preact-delegate/issues" 24 | }, 25 | "homepage": "https://github.com/NekR/preact-delegate#readme", 26 | "devDependencies": { 27 | "babel-plugin-external-helpers": "^6.22.0", 28 | "babel-preset-es2015": "^6.22.0", 29 | "rollup": "^0.41.4", 30 | "rollup-plugin-babel": "^2.7.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Arthur Stolyar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preact-delegate 2 | 3 | Delegate DOM events with Preact (since Preact doesn't do that by default). 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install preact-delegate --save-dev 9 | ``` 10 | 11 | ## Usage 12 | 13 | Just wrap your root element from where to capture events with `DelegateContainer` and then wrap individual elements which should receive events with `DelegateElement`. See example: 14 | 15 | ```js 16 | import { Component } from 'preact'; 17 | import { DelegateContainer, DelegateElement } from 'preact-delegate'; 18 | 19 | class MyComponent extends Component { 20 | constructor(...args) { 21 | super(...args); 22 | 23 | this.onClick = (e) => { 24 | console.log(e.target); 25 | }; 26 | } 27 | 28 | render({ items }) { 29 | return 30 |
31 | {items.map(item => ( 32 |
33 | 34 | 35 | 36 |
37 | ))} 38 |
39 |
40 | } 41 | } 42 | ``` 43 | 44 | ## LICENSE 45 | 46 | [MIT](LICEMSE.md) 47 | -------------------------------------------------------------------------------- /src/DelegateContainer.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | 3 | export default class DelegateContainer extends Component { 4 | constructor(...args) { 5 | super(...args); 6 | 7 | this.events = {}; 8 | 9 | this.onEvent = (event) => { 10 | const items = this.events[event.type]; 11 | 12 | if (!items) { 13 | return; 14 | } 15 | 16 | items.some(item => { 17 | if (item.elem === event.target || item.elem.contains(event.target)) { 18 | item.handler(event); 19 | return true; 20 | } 21 | }); 22 | } 23 | } 24 | 25 | getChildContext() { 26 | return { __delegateContainer__: this }; 27 | } 28 | 29 | componentWillUnmount() { 30 | Object.keys(this.events).forEach(name => { 31 | this.teardownListener(name); 32 | }); 33 | 34 | this.events = {}; 35 | } 36 | 37 | setupListener(name) { 38 | this.base.addEventListener(name, this.onEvent, true); 39 | } 40 | 41 | teardownListener(name) { 42 | this.base.removeEventListener(name, this.onEvent, true); 43 | } 44 | 45 | addEvents(events, base) { 46 | Object.keys(events).forEach(name => { 47 | if (!this.events[name]) { 48 | this.events[name] = []; 49 | this.setupListener(name); 50 | } 51 | 52 | this.events[name].push({ 53 | handler: events[name], 54 | elem: base 55 | }); 56 | }); 57 | } 58 | 59 | removeEvents(events, base) { 60 | Object.keys(events).forEach(name => { 61 | const items = this.events[name]; 62 | const handler = events[name]; 63 | 64 | if (!items) { 65 | return; 66 | } 67 | 68 | let index = -1; 69 | 70 | items.some((item, i) => { 71 | if (item.base === base /*&& item.handler === handler*/) { 72 | index = i; 73 | return true; 74 | } 75 | }); 76 | 77 | if (index !== -1) { 78 | items.splice(index, 1); 79 | } 80 | 81 | if (!items.length) { 82 | this.events[name] = null; 83 | this.teardownListener(name); 84 | } 85 | }); 86 | } 87 | 88 | render({ children }) { 89 | return children[0]; 90 | } 91 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var preact = require('preact'); 6 | 7 | var classCallCheck = function (instance, Constructor) { 8 | if (!(instance instanceof Constructor)) { 9 | throw new TypeError("Cannot call a class as a function"); 10 | } 11 | }; 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | var inherits = function (subClass, superClass) { 24 | if (typeof superClass !== "function" && superClass !== null) { 25 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 26 | } 27 | 28 | subClass.prototype = Object.create(superClass && superClass.prototype, { 29 | constructor: { 30 | value: subClass, 31 | enumerable: false, 32 | writable: true, 33 | configurable: true 34 | } 35 | }); 36 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 37 | }; 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | var possibleConstructorReturn = function (self, call) { 50 | if (!self) { 51 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 52 | } 53 | 54 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 55 | }; 56 | 57 | var DelegateContainer = function (_Component) { 58 | inherits(DelegateContainer, _Component); 59 | 60 | function DelegateContainer() { 61 | classCallCheck(this, DelegateContainer); 62 | 63 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 64 | args[_key] = arguments[_key]; 65 | } 66 | 67 | var _this = possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))); 68 | 69 | _this.events = {}; 70 | 71 | _this.onEvent = function (event) { 72 | var items = _this.events[event.type]; 73 | 74 | if (!items) { 75 | return; 76 | } 77 | 78 | items.some(function (item) { 79 | if (item.elem === event.target || item.elem.contains(event.target)) { 80 | item.handler(event); 81 | return true; 82 | } 83 | }); 84 | }; 85 | return _this; 86 | } 87 | 88 | DelegateContainer.prototype.getChildContext = function getChildContext() { 89 | return { __delegateContainer__: this }; 90 | }; 91 | 92 | DelegateContainer.prototype.componentWillUnmount = function componentWillUnmount() { 93 | var _this2 = this; 94 | 95 | Object.keys(this.events).forEach(function (name) { 96 | _this2.teardownListener(name); 97 | }); 98 | 99 | this.events = {}; 100 | }; 101 | 102 | DelegateContainer.prototype.setupListener = function setupListener(name) { 103 | this.base.addEventListener(name, this.onEvent, true); 104 | }; 105 | 106 | DelegateContainer.prototype.teardownListener = function teardownListener(name) { 107 | this.base.removeEventListener(name, this.onEvent, true); 108 | }; 109 | 110 | DelegateContainer.prototype.addEvents = function addEvents(events, base) { 111 | var _this3 = this; 112 | 113 | Object.keys(events).forEach(function (name) { 114 | if (!_this3.events[name]) { 115 | _this3.events[name] = []; 116 | _this3.setupListener(name); 117 | } 118 | 119 | _this3.events[name].push({ 120 | handler: events[name], 121 | elem: base 122 | }); 123 | }); 124 | }; 125 | 126 | DelegateContainer.prototype.removeEvents = function removeEvents(events, base) { 127 | var _this4 = this; 128 | 129 | Object.keys(events).forEach(function (name) { 130 | var items = _this4.events[name]; 131 | var handler = events[name]; 132 | 133 | if (!items) { 134 | return; 135 | } 136 | 137 | var index = -1; 138 | 139 | items.some(function (item, i) { 140 | if (item.base === base /*&& item.handler === handler*/) { 141 | index = i; 142 | return true; 143 | } 144 | }); 145 | 146 | if (index !== -1) { 147 | items.splice(index, 1); 148 | } 149 | 150 | if (!items.length) { 151 | _this4.events[name] = null; 152 | _this4.teardownListener(name); 153 | } 154 | }); 155 | }; 156 | 157 | DelegateContainer.prototype.render = function render(_ref) { 158 | var children = _ref.children; 159 | 160 | return children[0]; 161 | }; 162 | 163 | return DelegateContainer; 164 | }(preact.Component); 165 | 166 | var DelegateElement = function (_Component) { 167 | inherits(DelegateElement, _Component); 168 | 169 | function DelegateElement() { 170 | classCallCheck(this, DelegateElement); 171 | return possibleConstructorReturn(this, _Component.apply(this, arguments)); 172 | } 173 | 174 | DelegateElement.prototype.componentDidMount = function componentDidMount() { 175 | var events = Object.assign({}, this.props); 176 | delete events.children; 177 | 178 | var __delegateContainer__ = this.context.__delegateContainer__; 179 | 180 | 181 | __delegateContainer__.addEvents(events, this.base); 182 | }; 183 | 184 | DelegateElement.prototype.componentWillUnmount = function componentWillUnmount() { 185 | var events = Object.assign({}, this.props); 186 | delete events.children; 187 | 188 | var __delegateContainer__ = this.context.__delegateContainer__; 189 | 190 | 191 | __delegateContainer__.removeEvents(events, this.base); 192 | }; 193 | 194 | DelegateElement.prototype.render = function render(_ref) { 195 | var children = _ref.children; 196 | 197 | return children[0]; 198 | }; 199 | 200 | return DelegateElement; 201 | }(preact.Component); 202 | 203 | exports.DelegateContainer = DelegateContainer; 204 | exports.DelegateElement = DelegateElement; 205 | --------------------------------------------------------------------------------