├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── BACKERS.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── redom.cjs ├── redom.es.js ├── redom.es.min.js ├── redom.js └── redom.min.js ├── esm ├── create-element.js ├── dispatch.js ├── html.js ├── index.js ├── list.js ├── listpool.js ├── mount.js ├── place.js ├── ref.js ├── router.js ├── setattr.js ├── setchildren.js ├── setstyle.js ├── svg.js ├── text.js ├── unmount.js ├── util.js └── view-factory.js ├── index.d.ts ├── package-lock.json ├── package.json ├── rollup.config.js ├── test └── index.js └── watch.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pakastin 2 | *.ts @raulil 3 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # RE:DOM Contributing Guide 2 | 3 | 4 | The [issue tracker](https://github.com/redom/redom/issues) is the preferred channel for bug reports, feature requests, and submitting pull requests. 5 | 6 | ### Setting up your development environment 7 | 8 | You'll need a recent version of [Node](https://nodejs.org/en/) to work this project. 9 | 10 | Once node is installed, simply clone our repository (or your fork of it) and run `yarn install` or `npm install` 11 | 12 | ```bash 13 | $ git clone git@github.com:redom/redom.git 14 | $ cd redom 15 | $ yarn install 16 | ``` 17 | 18 | ### Running development server 19 | 20 | ```bash 21 | $ yarn run dev # Listen changes and RE:DOM. 22 | ``` 23 | 24 | This should aid you in initial development of a component. It's served on port 8080. 25 | 26 | ### Building 27 | 28 | ```bash 29 | $ yarn run build 30 | ``` 31 | 32 | ### Linting / Testing 33 | 34 | ```bash 35 | $ yarn run lint 36 | $ yarn run test 37 | ``` 38 | 39 | ### Feature requests 40 | 41 | Feature requests are welcome, but please take a moment to find out whether your idea fits with the scope and aims of the project. 42 | It's up to you to make a strong case to convince the project's developers of the merits of this feature. 43 | 44 | To get approval for your feature request, please create an issue on the issue tracker with as much detail and context as possible. 45 | 46 | ### Reporting Bugs 47 | 48 | A bug is a demonstrable, reproducible problem that is caused by the code in the repository. Good bug reports are extremely helpful, so thanks! 49 | 50 | **Guidelines for bug reports:** 51 | 52 | 1. Use the GitHub issue search — check if the issue has already been reported. 53 | 2. Check if the issue has been fixed — try to reproduce it using the latest **develop** branch in the repository. 54 | 3. Isolate the problem - you should provide a live example — ideally also create a reduced test case. 55 | 56 | This [CodePen](https://codepen.io/anon/pen/prvbMp), [JSFiddle](https://jsfiddle.net/h8x8bvn9/2/) are helpful templates you can fork or clone. 57 | 58 | ### Submitting Pull requests 59 | 60 | Good pull requests are an amazing help. 61 | 62 | They should remain focused in scope and avoid containing unrelated commits. 63 | 64 | Adhering to the following process is the best way to get your work included in the project: 65 | 66 | [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes: 67 | 68 | ```bash 69 | $ git clone https://github.com//redom.git 70 | $ cd redom 71 | $ git remote add upstream https://github.com/redom/redom.git 72 | ``` 73 | 74 | If you cloned a while ago, get the latest changes from upstream: 75 | 76 | ```bash 77 | $ git checkout develop 78 | $ git pull [--rebase] upstream develop 79 | ``` 80 | 81 | Create a new topic branch (off the main project **develop** branch) to contain your feature, change, or fix: 82 | 83 | ```bash 84 | git checkout -b 85 | ``` 86 | 87 | Commit your changes in logical chunks. Please adhere to these [guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 88 | 89 | Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. 90 | 91 | Locally merge (or rebase) the upstream development branch into your topic branch: 92 | 93 | ```bash 94 | $ git pull [--rebase] upstream develop 95 | ``` 96 | 97 | Push your topic branch up to your fork: 98 | 99 | ```bash 100 | $ git push origin 101 | ``` 102 | 103 | [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description against the **develop** branch. 104 | 105 | By submitting a patch, you agree to allow the project owner to license your work under the terms of the [MIT License](LICENSE). 106 | 107 | ## Credits 108 | 109 | Thank you to all the people who have already contributed to RE:DOM! 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: pakastin # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: redom 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | custom: # Replace with a single custom sponsorship URL 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: rollup 11 | versions: 12 | - 2.38.1 13 | - 2.38.3 14 | - 2.38.4 15 | - 2.38.5 16 | - 2.39.0 17 | - 2.39.1 18 | - 2.40.0 19 | - 2.41.0 20 | - 2.41.1 21 | - 2.41.2 22 | - 2.41.3 23 | - 2.41.4 24 | - 2.41.5 25 | - 2.42.0 26 | - 2.42.2 27 | - 2.42.3 28 | - 2.42.4 29 | - 2.45.0 30 | - 2.45.1 31 | - 2.45.2 32 | - dependency-name: marked 33 | versions: 34 | - 1.2.8 35 | - 1.2.9 36 | - 2.0.1 37 | - dependency-name: tape 38 | versions: 39 | - 5.1.1 40 | - 5.2.0 41 | - 5.2.1 42 | - dependency-name: terser 43 | versions: 44 | - 5.5.1 45 | - 5.6.0 46 | - dependency-name: ini 47 | versions: 48 | - 1.3.7 49 | - dependency-name: elliptic 50 | versions: 51 | - 6.5.3 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | node_modules 3 | 4 | # Files 5 | *.log 6 | *-lock 7 | *.lock 8 | package-lock.json 9 | 10 | # Other 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .gitignore 3 | rollup.config.js 4 | watch.js 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "20" 4 | addons: 5 | apt: 6 | packages: 7 | - xvfb 8 | install: 9 | - export DISPLAY=':99.0' 10 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 11 | - npm install 12 | -------------------------------------------------------------------------------- /BACKERS.md: -------------------------------------------------------------------------------- 1 | # Sponsors & Backers 2 | 3 | RE:DOM is an MIT-licensed open source project.\ 4 | It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/redom/redom/blob/dev/BACKERS.md). 5 | 6 | **If you'd like to join them, please consider:** 7 | 8 | - [Become a backer or sponsor on OpenCollective](https://opencollective.com/redom). 9 | 10 | ## Backers 11 | 12 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/redom#backer)] 13 | 14 | 15 | 16 | ## Sponsors 17 | 18 | Support this project by becoming a sponsor.\ 19 | Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/redom#sponsor)] 20 | 21 | 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | https://github.com/redom/redom/releases 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016–Present, Juha Lindstedt and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | RE:DOM logo 4 | 5 |
6 | Develop web applications with 100% JavaScript and web standards. 🚀 7 |

8 |

9 |
10 | Version 11 | License 12 | Backers on Open Collective 13 | Sponsors on Open Collective 14 | Join the chat 15 |
16 | Follow @pakastin 17 | Follow @redomjs 18 |
19 |
20 |

21 | 22 | **[RE:DOM](https://redom.js.org)** is a tiny **(2 KB) UI library** by [Juha Lindstedt](https://github.com/pakastin) and [contributors](https://github.com/redom/redom/graphs/contributors), which adds some useful helpers to create DOM elements and keeping them in sync with the data. 23 | 24 | Because RE:DOM is so close to the metal and **doesn't use virtual dom**, it's actually **faster** and uses **less memory** than almost all virtual dom based libraries, including React ([benchmark](https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html)). 25 | 26 | It's also easy to create **reusable components** with RE:DOM. 27 | 28 | Another great benefit is, that you can use just **pure JavaScript**, so no complicated templating languages to learn and hassle with. 29 | 30 | ## Supporting RE:DOM 31 | 32 | RE:DOM is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [backers](./BACKERS.md). 33 | 34 | **If you'd like to join them, please consider:** 35 | 36 | - [Become a backer or sponsor on Open Collective](https://opencollective.com/redom). 37 | 38 | ## Installation 39 | 40 | ```bash 41 | # Using npm 42 | npm install redom 43 | 44 | # Using Yarn 45 | yarn add redom 46 | ``` 47 | 48 | ## Documentation 49 | 50 | To check out [live examples](https://redom.js.org/#todomvc) and docs, visit [RE:DOM](https://redom.js.org/). 51 | 52 | ## Questions 53 | 54 | For questions and support please use [community chat](https://gitter.im/pakastin/redom/). 55 | 56 | ## Tools 57 | 58 | - [NO:DOM](https://github.com/redom/nodom) (server-side rendering) 59 | - [Project generator](https://github.com/redom/redom-cli) 60 | - [Dev tools for Chrome](https://github.com/redom/redom-devtools) 61 | - [Babel RE:DOM JSX transform](https://github.com/tomerigal/babel-plugin-transform-redom-jsx) 62 | 63 | ## Performance 64 | 65 | - [RE:DOM is one of the fastest UI libraries out there.](https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html) 66 | 67 | ## Issues 68 | 69 | Please make sure to read the [Issue Reporting Checklist](./.github/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines may be closed immediately. 70 | 71 | ## Changelog 72 | 73 | Detailed changes for each release are documented in the [release notes](https://github.com/redom/redom/releases). 74 | 75 | ## Contribution 76 | 77 | Please make sure to read the [Contributing Guide](./.github/CONTRIBUTING.md) before making a pull request.\ 78 | Thank you to all the people who already contributed to RE:DOM! 79 | 80 | 81 | 82 | 83 | 84 | ## License 85 | 86 | [MIT](http://opensource.org/licenses/MIT) 87 | 88 | Copyright (c) 2016-present, [Juha Lindstedt](https://github.com/pakastin) and contributors 89 | -------------------------------------------------------------------------------- /dist/redom.cjs: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.redom = {})); 5 | })(this, (function (exports) { 'use strict'; 6 | 7 | function _arrayLikeToArray(r, a) { 8 | (null == a || a > r.length) && (a = r.length); 9 | for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; 10 | return n; 11 | } 12 | function _classCallCheck(a, n) { 13 | if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); 14 | } 15 | function _construct(t, e, r) { 16 | if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); 17 | var o = [null]; 18 | o.push.apply(o, e); 19 | var p = new (t.bind.apply(t, o))(); 20 | return p; 21 | } 22 | function _defineProperties(e, r) { 23 | for (var t = 0; t < r.length; t++) { 24 | var o = r[t]; 25 | o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o); 26 | } 27 | } 28 | function _createClass(e, r, t) { 29 | return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", { 30 | writable: false 31 | }), e; 32 | } 33 | function _createForOfIteratorHelper(r, e) { 34 | var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; 35 | if (!t) { 36 | if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { 37 | t && (r = t); 38 | var n = 0, 39 | F = function () {}; 40 | return { 41 | s: F, 42 | n: function () { 43 | return n >= r.length ? { 44 | done: true 45 | } : { 46 | done: false, 47 | value: r[n++] 48 | }; 49 | }, 50 | e: function (r) { 51 | throw r; 52 | }, 53 | f: F 54 | }; 55 | } 56 | throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 57 | } 58 | var o, 59 | a = true, 60 | u = false; 61 | return { 62 | s: function () { 63 | t = t.call(r); 64 | }, 65 | n: function () { 66 | var r = t.next(); 67 | return a = r.done, r; 68 | }, 69 | e: function (r) { 70 | u = true, o = r; 71 | }, 72 | f: function () { 73 | try { 74 | a || null == t.return || t.return(); 75 | } finally { 76 | if (u) throw o; 77 | } 78 | } 79 | }; 80 | } 81 | function _isNativeReflectConstruct() { 82 | try { 83 | var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 84 | } catch (t) {} 85 | return (_isNativeReflectConstruct = function () { 86 | return !!t; 87 | })(); 88 | } 89 | function _toPrimitive(t, r) { 90 | if ("object" != typeof t || !t) return t; 91 | var e = t[Symbol.toPrimitive]; 92 | if (undefined !== e) { 93 | var i = e.call(t, r); 94 | if ("object" != typeof i) return i; 95 | throw new TypeError("@@toPrimitive must return a primitive value."); 96 | } 97 | return (String )(t); 98 | } 99 | function _toPropertyKey(t) { 100 | var i = _toPrimitive(t, "string"); 101 | return "symbol" == typeof i ? i : i + ""; 102 | } 103 | function _typeof(o) { 104 | "@babel/helpers - typeof"; 105 | 106 | return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { 107 | return typeof o; 108 | } : function (o) { 109 | return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; 110 | }, _typeof(o); 111 | } 112 | function _unsupportedIterableToArray(r, a) { 113 | if (r) { 114 | if ("string" == typeof r) return _arrayLikeToArray(r, a); 115 | var t = {}.toString.call(r).slice(8, -1); 116 | return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : undefined; 117 | } 118 | } 119 | 120 | function createElement(query, ns) { 121 | var _parse = parse(query), 122 | tag = _parse.tag, 123 | id = _parse.id, 124 | className = _parse.className; 125 | var element = ns ? document.createElementNS(ns, tag) : document.createElement(tag); 126 | if (id) { 127 | element.id = id; 128 | } 129 | if (className) { 130 | if (ns) { 131 | element.setAttribute("class", className); 132 | } else { 133 | element.className = className; 134 | } 135 | } 136 | return element; 137 | } 138 | function parse(query) { 139 | var chunks = query.split(/([.#])/); 140 | var className = ""; 141 | var id = ""; 142 | for (var i = 1; i < chunks.length; i += 2) { 143 | switch (chunks[i]) { 144 | case ".": 145 | className += " ".concat(chunks[i + 1]); 146 | break; 147 | case "#": 148 | id = chunks[i + 1]; 149 | } 150 | } 151 | return { 152 | className: className.trim(), 153 | tag: chunks[0] || "div", 154 | id: id 155 | }; 156 | } 157 | 158 | function html(query) { 159 | var element; 160 | var type = _typeof(query); 161 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 162 | args[_key - 1] = arguments[_key]; 163 | } 164 | if (type === "string") { 165 | element = createElement(query); 166 | } else if (type === "function") { 167 | var Query = query; 168 | element = _construct(Query, args); 169 | } else { 170 | throw new Error("At least one argument required"); 171 | } 172 | parseArgumentsInternal(getEl(element), args, true); 173 | return element; 174 | } 175 | var el = html; 176 | var h = html; 177 | html.extend = function extendHtml() { 178 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 179 | args[_key2] = arguments[_key2]; 180 | } 181 | return html.bind.apply(html, [this].concat(args)); 182 | }; 183 | 184 | function unmount(parent, _child) { 185 | var child = _child; 186 | var parentEl = getEl(parent); 187 | var childEl = getEl(child); 188 | if (child === childEl && childEl.__redom_view) { 189 | // try to look up the view if not provided 190 | child = childEl.__redom_view; 191 | } 192 | if (childEl.parentNode) { 193 | doUnmount(child, childEl, parentEl); 194 | parentEl.removeChild(childEl); 195 | } 196 | return child; 197 | } 198 | function doUnmount(child, childEl, parentEl) { 199 | var hooks = childEl.__redom_lifecycle; 200 | if (hooksAreEmpty(hooks)) { 201 | childEl.__redom_lifecycle = {}; 202 | return; 203 | } 204 | var traverse = parentEl; 205 | if (childEl.__redom_mounted) { 206 | trigger(childEl, "onunmount"); 207 | } 208 | while (traverse) { 209 | var parentHooks = traverse.__redom_lifecycle || {}; 210 | for (var hook in hooks) { 211 | if (parentHooks[hook]) { 212 | parentHooks[hook] -= hooks[hook]; 213 | } 214 | } 215 | if (hooksAreEmpty(parentHooks)) { 216 | traverse.__redom_lifecycle = null; 217 | } 218 | traverse = traverse.parentNode; 219 | } 220 | } 221 | function hooksAreEmpty(hooks) { 222 | if (hooks == null) { 223 | return true; 224 | } 225 | for (var key in hooks) { 226 | if (hooks[key]) { 227 | return false; 228 | } 229 | } 230 | return true; 231 | } 232 | 233 | var hookNames = ["onmount", "onremount", "onunmount"]; 234 | var shadowRootAvailable = typeof window !== "undefined" && "ShadowRoot" in window; 235 | function mount(parent, _child, before, replace) { 236 | var child = _child; 237 | var parentEl = getEl(parent); 238 | var childEl = getEl(child); 239 | if (child === childEl && childEl.__redom_view) { 240 | // try to look up the view if not provided 241 | child = childEl.__redom_view; 242 | } 243 | if (child !== childEl) { 244 | childEl.__redom_view = child; 245 | } 246 | var wasMounted = childEl.__redom_mounted; 247 | var oldParent = childEl.parentNode; 248 | if (wasMounted && oldParent !== parentEl) { 249 | doUnmount(child, childEl, oldParent); 250 | } 251 | if (before != null) { 252 | if (replace) { 253 | var beforeEl = getEl(before); 254 | if (beforeEl.__redom_mounted) { 255 | trigger(beforeEl, "onunmount"); 256 | } 257 | parentEl.replaceChild(childEl, beforeEl); 258 | } else { 259 | parentEl.insertBefore(childEl, getEl(before)); 260 | } 261 | } else { 262 | parentEl.appendChild(childEl); 263 | } 264 | doMount(child, childEl, parentEl, oldParent); 265 | return child; 266 | } 267 | function trigger(el, eventName) { 268 | var _view$eventName; 269 | if (eventName === "onmount" || eventName === "onremount") { 270 | el.__redom_mounted = true; 271 | } else if (eventName === "onunmount") { 272 | el.__redom_mounted = false; 273 | } 274 | var hooks = el.__redom_lifecycle; 275 | if (!hooks) { 276 | return; 277 | } 278 | var view = el.__redom_view; 279 | var hookCount = 0; 280 | view === null || view === undefined || (_view$eventName = view[eventName]) === null || _view$eventName === undefined || _view$eventName.call(view); 281 | for (var hook in hooks) { 282 | if (hook) { 283 | hookCount++; 284 | } 285 | } 286 | if (hookCount) { 287 | var traverse = el.firstChild; 288 | while (traverse) { 289 | var next = traverse.nextSibling; 290 | trigger(traverse, eventName); 291 | traverse = next; 292 | } 293 | } 294 | } 295 | function doMount(child, childEl, parentEl, oldParent) { 296 | var _traverse; 297 | if (!childEl.__redom_lifecycle) { 298 | childEl.__redom_lifecycle = {}; 299 | } 300 | var hooks = childEl.__redom_lifecycle; 301 | var remount = parentEl === oldParent; 302 | var hooksFound = false; 303 | var _iterator = _createForOfIteratorHelper(hookNames), 304 | _step; 305 | try { 306 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 307 | var hookName = _step.value; 308 | if (!remount) { 309 | // if already mounted, skip this phase 310 | if (child !== childEl) { 311 | // only Views can have lifecycle events 312 | if (hookName in child) { 313 | hooks[hookName] = (hooks[hookName] || 0) + 1; 314 | } 315 | } 316 | } 317 | if (hooks[hookName]) { 318 | hooksFound = true; 319 | } 320 | } 321 | } catch (err) { 322 | _iterator.e(err); 323 | } finally { 324 | _iterator.f(); 325 | } 326 | if (!hooksFound) { 327 | childEl.__redom_lifecycle = {}; 328 | return; 329 | } 330 | var traverse = parentEl; 331 | var triggered = false; 332 | if (remount || (_traverse = traverse) !== null && _traverse !== undefined && _traverse.__redom_mounted) { 333 | trigger(childEl, remount ? "onremount" : "onmount"); 334 | triggered = true; 335 | } 336 | while (traverse) { 337 | var parent = traverse.parentNode; 338 | if (!traverse.__redom_lifecycle) { 339 | traverse.__redom_lifecycle = {}; 340 | } 341 | var parentHooks = traverse.__redom_lifecycle; 342 | for (var hook in hooks) { 343 | parentHooks[hook] = (parentHooks[hook] || 0) + hooks[hook]; 344 | } 345 | if (triggered) { 346 | break; 347 | } 348 | if (traverse.nodeType === Node.DOCUMENT_NODE || shadowRootAvailable && traverse instanceof ShadowRoot || parent !== null && parent !== undefined && parent.__redom_mounted) { 349 | trigger(traverse, remount ? "onremount" : "onmount"); 350 | triggered = true; 351 | } 352 | traverse = parent; 353 | } 354 | } 355 | 356 | function setStyle(view, arg1, arg2) { 357 | var el = getEl(view); 358 | if (_typeof(arg1) === "object") { 359 | for (var key in arg1) { 360 | setStyleValue(el, key, arg1[key]); 361 | } 362 | } else { 363 | setStyleValue(el, arg1, arg2); 364 | } 365 | } 366 | function setStyleValue(el, key, value) { 367 | el.style[key] = value == null ? "" : value; 368 | } 369 | 370 | var xlinkns = "http://www.w3.org/1999/xlink"; 371 | function setAttr(view, arg1, arg2) { 372 | setAttrInternal(view, arg1, arg2); 373 | } 374 | function setAttrInternal(view, arg1, arg2, initial) { 375 | var el = getEl(view); 376 | var isObj = _typeof(arg1) === "object"; 377 | if (isObj) { 378 | for (var key in arg1) { 379 | setAttrInternal(el, key, arg1[key], initial); 380 | } 381 | } else { 382 | var isSVG = el instanceof SVGElement; 383 | var isFunc = typeof arg2 === "function"; 384 | if (arg1 === "style" && _typeof(arg2) === "object") { 385 | setStyle(el, arg2); 386 | } else if (isSVG && isFunc) { 387 | el[arg1] = arg2; 388 | } else if (arg1 === "dataset") { 389 | setData(el, arg2); 390 | } else if (!isSVG && (arg1 in el || isFunc) && arg1 !== "list") { 391 | el[arg1] = arg2; 392 | } else { 393 | if (isSVG && arg1 === "xlink") { 394 | setXlink(el, arg2); 395 | return; 396 | } 397 | if (initial && arg1 === "class") { 398 | setClassName(el, arg2); 399 | return; 400 | } 401 | if (arg2 == null) { 402 | el.removeAttribute(arg1); 403 | } else { 404 | el.setAttribute(arg1, arg2); 405 | } 406 | } 407 | } 408 | } 409 | function setClassName(el, additionToClassName) { 410 | if (additionToClassName == null) { 411 | el.removeAttribute("class"); 412 | } else if (el.classList) { 413 | el.classList.add(additionToClassName); 414 | } else if (_typeof(el.className) === "object" && el.className && el.className.baseVal) { 415 | el.className.baseVal = "".concat(el.className.baseVal, " ").concat(additionToClassName).trim(); 416 | } else { 417 | el.className = "".concat(el.className, " ").concat(additionToClassName).trim(); 418 | } 419 | } 420 | function setXlink(el, arg1, arg2) { 421 | if (_typeof(arg1) === "object") { 422 | for (var key in arg1) { 423 | setXlink(el, key, arg1[key]); 424 | } 425 | } else { 426 | if (arg2 != null) { 427 | el.setAttributeNS(xlinkns, arg1, arg2); 428 | } else { 429 | el.removeAttributeNS(xlinkns, arg1, arg2); 430 | } 431 | } 432 | } 433 | function setData(el, arg1, arg2) { 434 | if (_typeof(arg1) === "object") { 435 | for (var key in arg1) { 436 | setData(el, key, arg1[key]); 437 | } 438 | } else { 439 | if (arg2 != null) { 440 | el.dataset[arg1] = arg2; 441 | } else { 442 | delete el.dataset[arg1]; 443 | } 444 | } 445 | } 446 | 447 | function text(str) { 448 | return document.createTextNode(str != null ? str : ""); 449 | } 450 | 451 | function parseArgumentsInternal(element, args, initial) { 452 | var _iterator = _createForOfIteratorHelper(args), 453 | _step; 454 | try { 455 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 456 | var arg = _step.value; 457 | if (arg !== 0 && !arg) { 458 | continue; 459 | } 460 | var type = _typeof(arg); 461 | if (type === "function") { 462 | arg(element); 463 | } else if (type === "string" || type === "number") { 464 | element.appendChild(text(arg)); 465 | } else if (isNode(getEl(arg))) { 466 | mount(element, arg); 467 | } else if (arg.length) { 468 | parseArgumentsInternal(element, arg, initial); 469 | } else if (type === "object") { 470 | setAttrInternal(element, arg, null, initial); 471 | } 472 | } 473 | } catch (err) { 474 | _iterator.e(err); 475 | } finally { 476 | _iterator.f(); 477 | } 478 | } 479 | function ensureEl(parent) { 480 | return typeof parent === "string" ? html(parent) : getEl(parent); 481 | } 482 | function getEl(parent) { 483 | return parent.nodeType && parent || !parent.el && parent || getEl(parent.el); 484 | } 485 | function isNode(arg) { 486 | return arg === null || arg === undefined ? undefined : arg.nodeType; 487 | } 488 | 489 | function dispatch(child, data) { 490 | var eventName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "redom"; 491 | var childEl = getEl(child); 492 | var event = new CustomEvent(eventName, { 493 | bubbles: true, 494 | detail: data 495 | }); 496 | childEl.dispatchEvent(event); 497 | } 498 | 499 | function setChildren(parent) { 500 | var parentEl = getEl(parent); 501 | for (var _len = arguments.length, children = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 502 | children[_key - 1] = arguments[_key]; 503 | } 504 | var current = traverse(parent, children, parentEl.firstChild); 505 | while (current) { 506 | var next = current.nextSibling; 507 | unmount(parent, current); 508 | current = next; 509 | } 510 | } 511 | function traverse(parent, children, _current) { 512 | var current = _current; 513 | var childEls = Array(children.length); 514 | for (var i = 0; i < children.length; i++) { 515 | childEls[i] = children[i] && getEl(children[i]); 516 | } 517 | for (var _i = 0; _i < children.length; _i++) { 518 | var child = children[_i]; 519 | if (!child) { 520 | continue; 521 | } 522 | var childEl = childEls[_i]; 523 | if (childEl === current) { 524 | current = current.nextSibling; 525 | continue; 526 | } 527 | if (isNode(childEl)) { 528 | var _current2; 529 | var next = (_current2 = current) === null || _current2 === undefined ? undefined : _current2.nextSibling; 530 | var exists = child.__redom_index != null; 531 | var replace = exists && next === childEls[_i + 1]; 532 | mount(parent, child, current, replace); 533 | if (replace) { 534 | current = next; 535 | } 536 | continue; 537 | } 538 | if (child.length != null) { 539 | current = traverse(parent, child, current); 540 | } 541 | } 542 | return current; 543 | } 544 | 545 | function listPool(View, key, initData) { 546 | return new ListPool(View, key, initData); 547 | } 548 | var ListPool = /*#__PURE__*/function () { 549 | function ListPool(View, key, initData) { 550 | _classCallCheck(this, ListPool); 551 | this.View = View; 552 | this.initData = initData; 553 | this.oldLookup = {}; 554 | this.lookup = {}; 555 | this.oldViews = []; 556 | this.views = []; 557 | if (key != null) { 558 | this.key = typeof key === "function" ? key : propKey(key); 559 | } 560 | } 561 | return _createClass(ListPool, [{ 562 | key: "update", 563 | value: function update(data, context) { 564 | var View = this.View, 565 | key = this.key, 566 | initData = this.initData; 567 | var keySet = key != null; 568 | var oldLookup = this.lookup; 569 | var newLookup = {}; 570 | var newViews = Array(data.length); 571 | var oldViews = this.views; 572 | for (var i = 0; i < data.length; i++) { 573 | var _view$update, _view; 574 | var item = data[i]; 575 | var view = undefined; 576 | if (keySet) { 577 | var id = key(item); 578 | view = oldLookup[id] || new View(initData, item, i, data); 579 | newLookup[id] = view; 580 | view.__redom_id = id; 581 | } else { 582 | view = oldViews[i] || new View(initData, item, i, data); 583 | } 584 | (_view$update = (_view = view).update) === null || _view$update === undefined || _view$update.call(_view, item, i, data, context); 585 | var el = getEl(view.el); 586 | el.__redom_view = view; 587 | newViews[i] = view; 588 | } 589 | this.oldViews = oldViews; 590 | this.views = newViews; 591 | this.oldLookup = oldLookup; 592 | this.lookup = newLookup; 593 | } 594 | }]); 595 | }(); 596 | function propKey(key) { 597 | return function proppedKey(item) { 598 | return item[key]; 599 | }; 600 | } 601 | 602 | function list(parent, View, key, initData) { 603 | return new List(parent, View, key, initData); 604 | } 605 | var List = /*#__PURE__*/function () { 606 | function List(parent, View, key, initData) { 607 | _classCallCheck(this, List); 608 | this.View = View; 609 | this.initData = initData; 610 | this.views = []; 611 | this.pool = new ListPool(View, key, initData); 612 | this.el = ensureEl(parent); 613 | this.keySet = key != null; 614 | } 615 | return _createClass(List, [{ 616 | key: "update", 617 | value: function update(data, context) { 618 | var keySet = this.keySet; 619 | var oldViews = this.views; 620 | this.pool.update(data || [], context); 621 | var _this$pool = this.pool, 622 | views = _this$pool.views, 623 | lookup = _this$pool.lookup; 624 | if (keySet) { 625 | for (var i = 0; i < oldViews.length; i++) { 626 | var oldView = oldViews[i]; 627 | var id = oldView.__redom_id; 628 | if (lookup[id] == null) { 629 | oldView.__redom_index = null; 630 | unmount(this, oldView); 631 | } 632 | } 633 | } 634 | for (var _i = 0; _i < views.length; _i++) { 635 | var view = views[_i]; 636 | view.__redom_index = _i; 637 | } 638 | setChildren(this, views); 639 | if (keySet) { 640 | this.lookup = lookup; 641 | } 642 | this.views = views; 643 | } 644 | }]); 645 | }(); 646 | List.extend = function extendList(parent, View, key, initData) { 647 | return List.bind(List, parent, View, key, initData); 648 | }; 649 | list.extend = List.extend; 650 | 651 | function place(View, initData) { 652 | return new Place(View, initData); 653 | } 654 | var Place = /*#__PURE__*/function () { 655 | function Place(View, initData) { 656 | _classCallCheck(this, Place); 657 | this.el = text(""); 658 | this.visible = false; 659 | this.view = null; 660 | this._placeholder = this.el; 661 | if (View instanceof Node) { 662 | this._el = View; 663 | } else if (View.el instanceof Node) { 664 | this._el = View; 665 | this.view = View; 666 | } else { 667 | this._View = View; 668 | } 669 | this._initData = initData; 670 | } 671 | return _createClass(Place, [{ 672 | key: "update", 673 | value: function update(visible, data) { 674 | var placeholder = this._placeholder; 675 | var parentNode = this.el.parentNode; 676 | if (visible) { 677 | var _this$view, _this$view$update; 678 | if (!this.visible) { 679 | if (this._el) { 680 | mount(parentNode, this._el, placeholder); 681 | unmount(parentNode, placeholder); 682 | this.el = getEl(this._el); 683 | this.visible = visible; 684 | } else { 685 | var View = this._View; 686 | var view = new View(this._initData); 687 | this.el = getEl(view); 688 | this.view = view; 689 | mount(parentNode, view, placeholder); 690 | unmount(parentNode, placeholder); 691 | } 692 | } 693 | (_this$view = this.view) === null || _this$view === undefined || (_this$view$update = _this$view.update) === null || _this$view$update === undefined || _this$view$update.call(_this$view, data); 694 | } else { 695 | if (this.visible) { 696 | if (this._el) { 697 | mount(parentNode, placeholder, this._el); 698 | unmount(parentNode, this._el); 699 | this.el = placeholder; 700 | this.visible = visible; 701 | return; 702 | } 703 | mount(parentNode, placeholder, this.view); 704 | unmount(parentNode, this.view); 705 | this.el = placeholder; 706 | this.view = null; 707 | } 708 | } 709 | this.visible = visible; 710 | } 711 | }]); 712 | }(); 713 | 714 | function ref(ctx, key, value) { 715 | ctx[key] = value; 716 | return value; 717 | } 718 | 719 | function router(parent, views, initData) { 720 | return new Router(parent, views, initData); 721 | } 722 | var Router = /*#__PURE__*/function () { 723 | function Router(parent, views, initData) { 724 | _classCallCheck(this, Router); 725 | this.el = ensureEl(parent); 726 | this.views = views; 727 | this.Views = views; // backwards compatibility 728 | this.initData = initData; 729 | } 730 | return _createClass(Router, [{ 731 | key: "update", 732 | value: function update(route, data) { 733 | var _this$view, _this$view$update; 734 | if (route !== this.route) { 735 | var views = this.views; 736 | var View = views[route]; 737 | this.route = route; 738 | if (View && (View instanceof Node || View.el instanceof Node)) { 739 | this.view = View; 740 | } else { 741 | this.view = View && new View(this.initData, data); 742 | } 743 | setChildren(this.el, [this.view]); 744 | } 745 | (_this$view = this.view) === null || _this$view === undefined || (_this$view$update = _this$view.update) === null || _this$view$update === undefined || _this$view$update.call(_this$view, data, route); 746 | } 747 | }]); 748 | }(); 749 | 750 | var ns = "http://www.w3.org/2000/svg"; 751 | function svg(query) { 752 | var element; 753 | var type = _typeof(query); 754 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 755 | args[_key - 1] = arguments[_key]; 756 | } 757 | if (type === "string") { 758 | element = createElement(query, ns); 759 | } else if (type === "function") { 760 | var Query = query; 761 | element = _construct(Query, args); 762 | } else { 763 | throw new Error("At least one argument required"); 764 | } 765 | parseArgumentsInternal(getEl(element), args, true); 766 | return element; 767 | } 768 | var s = svg; 769 | svg.extend = function extendSvg() { 770 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 771 | args[_key2] = arguments[_key2]; 772 | } 773 | return svg.bind.apply(svg, [this].concat(args)); 774 | }; 775 | svg.ns = ns; 776 | 777 | function viewFactory(views, key) { 778 | if (!views || _typeof(views) !== "object") { 779 | throw new Error("views must be an object"); 780 | } 781 | if (!key || typeof key !== "string") { 782 | throw new Error("key must be a string"); 783 | } 784 | return function factoryView(initData, item, i, data) { 785 | var viewKey = item[key]; 786 | var View = views[viewKey]; 787 | if (View) { 788 | return new View(initData, item, i, data); 789 | } 790 | throw new Error("view ".concat(viewKey, " not found")); 791 | }; 792 | } 793 | 794 | exports.List = List; 795 | exports.ListPool = ListPool; 796 | exports.Place = Place; 797 | exports.Router = Router; 798 | exports.dispatch = dispatch; 799 | exports.el = el; 800 | exports.h = h; 801 | exports.html = html; 802 | exports.list = list; 803 | exports.listPool = listPool; 804 | exports.mount = mount; 805 | exports.place = place; 806 | exports.ref = ref; 807 | exports.router = router; 808 | exports.s = s; 809 | exports.setAttr = setAttr; 810 | exports.setChildren = setChildren; 811 | exports.setData = setData; 812 | exports.setStyle = setStyle; 813 | exports.setXlink = setXlink; 814 | exports.svg = svg; 815 | exports.text = text; 816 | exports.unmount = unmount; 817 | exports.viewFactory = viewFactory; 818 | 819 | })); 820 | -------------------------------------------------------------------------------- /dist/redom.es.js: -------------------------------------------------------------------------------- 1 | function createElement(query, ns) { 2 | const { tag, id, className } = parse(query); 3 | const element = ns 4 | ? document.createElementNS(ns, tag) 5 | : document.createElement(tag); 6 | 7 | if (id) { 8 | element.id = id; 9 | } 10 | 11 | if (className) { 12 | if (ns) { 13 | element.setAttribute("class", className); 14 | } else { 15 | element.className = className; 16 | } 17 | } 18 | 19 | return element; 20 | } 21 | 22 | function parse(query) { 23 | const chunks = query.split(/([.#])/); 24 | let className = ""; 25 | let id = ""; 26 | 27 | for (let i = 1; i < chunks.length; i += 2) { 28 | switch (chunks[i]) { 29 | case ".": 30 | className += ` ${chunks[i + 1]}`; 31 | break; 32 | 33 | case "#": 34 | id = chunks[i + 1]; 35 | } 36 | } 37 | 38 | return { 39 | className: className.trim(), 40 | tag: chunks[0] || "div", 41 | id, 42 | }; 43 | } 44 | 45 | function html(query, ...args) { 46 | let element; 47 | 48 | const type = typeof query; 49 | 50 | if (type === "string") { 51 | element = createElement(query); 52 | } else if (type === "function") { 53 | const Query = query; 54 | element = new Query(...args); 55 | } else { 56 | throw new Error("At least one argument required"); 57 | } 58 | 59 | parseArgumentsInternal(getEl(element), args, true); 60 | 61 | return element; 62 | } 63 | 64 | const el = html; 65 | const h = html; 66 | 67 | html.extend = function extendHtml(...args) { 68 | return html.bind(this, ...args); 69 | }; 70 | 71 | function unmount(parent, _child) { 72 | let child = _child; 73 | const parentEl = getEl(parent); 74 | const childEl = getEl(child); 75 | 76 | if (child === childEl && childEl.__redom_view) { 77 | // try to look up the view if not provided 78 | child = childEl.__redom_view; 79 | } 80 | 81 | if (childEl.parentNode) { 82 | doUnmount(child, childEl, parentEl); 83 | 84 | parentEl.removeChild(childEl); 85 | } 86 | 87 | return child; 88 | } 89 | 90 | function doUnmount(child, childEl, parentEl) { 91 | const hooks = childEl.__redom_lifecycle; 92 | 93 | if (hooksAreEmpty(hooks)) { 94 | childEl.__redom_lifecycle = {}; 95 | return; 96 | } 97 | 98 | let traverse = parentEl; 99 | 100 | if (childEl.__redom_mounted) { 101 | trigger(childEl, "onunmount"); 102 | } 103 | 104 | while (traverse) { 105 | const parentHooks = traverse.__redom_lifecycle || {}; 106 | 107 | for (const hook in hooks) { 108 | if (parentHooks[hook]) { 109 | parentHooks[hook] -= hooks[hook]; 110 | } 111 | } 112 | 113 | if (hooksAreEmpty(parentHooks)) { 114 | traverse.__redom_lifecycle = null; 115 | } 116 | 117 | traverse = traverse.parentNode; 118 | } 119 | } 120 | 121 | function hooksAreEmpty(hooks) { 122 | if (hooks == null) { 123 | return true; 124 | } 125 | for (const key in hooks) { 126 | if (hooks[key]) { 127 | return false; 128 | } 129 | } 130 | return true; 131 | } 132 | 133 | /* global Node, ShadowRoot */ 134 | 135 | 136 | const hookNames = ["onmount", "onremount", "onunmount"]; 137 | const shadowRootAvailable = 138 | typeof window !== "undefined" && "ShadowRoot" in window; 139 | 140 | function mount(parent, _child, before, replace) { 141 | let child = _child; 142 | const parentEl = getEl(parent); 143 | const childEl = getEl(child); 144 | 145 | if (child === childEl && childEl.__redom_view) { 146 | // try to look up the view if not provided 147 | child = childEl.__redom_view; 148 | } 149 | 150 | if (child !== childEl) { 151 | childEl.__redom_view = child; 152 | } 153 | 154 | const wasMounted = childEl.__redom_mounted; 155 | const oldParent = childEl.parentNode; 156 | 157 | if (wasMounted && oldParent !== parentEl) { 158 | doUnmount(child, childEl, oldParent); 159 | } 160 | 161 | if (before != null) { 162 | if (replace) { 163 | const beforeEl = getEl(before); 164 | 165 | if (beforeEl.__redom_mounted) { 166 | trigger(beforeEl, "onunmount"); 167 | } 168 | 169 | parentEl.replaceChild(childEl, beforeEl); 170 | } else { 171 | parentEl.insertBefore(childEl, getEl(before)); 172 | } 173 | } else { 174 | parentEl.appendChild(childEl); 175 | } 176 | 177 | doMount(child, childEl, parentEl, oldParent); 178 | 179 | return child; 180 | } 181 | 182 | function trigger(el, eventName) { 183 | if (eventName === "onmount" || eventName === "onremount") { 184 | el.__redom_mounted = true; 185 | } else if (eventName === "onunmount") { 186 | el.__redom_mounted = false; 187 | } 188 | 189 | const hooks = el.__redom_lifecycle; 190 | 191 | if (!hooks) { 192 | return; 193 | } 194 | 195 | const view = el.__redom_view; 196 | let hookCount = 0; 197 | 198 | view?.[eventName]?.(); 199 | 200 | for (const hook in hooks) { 201 | if (hook) { 202 | hookCount++; 203 | } 204 | } 205 | 206 | if (hookCount) { 207 | let traverse = el.firstChild; 208 | 209 | while (traverse) { 210 | const next = traverse.nextSibling; 211 | 212 | trigger(traverse, eventName); 213 | 214 | traverse = next; 215 | } 216 | } 217 | } 218 | 219 | function doMount(child, childEl, parentEl, oldParent) { 220 | if (!childEl.__redom_lifecycle) { 221 | childEl.__redom_lifecycle = {}; 222 | } 223 | 224 | const hooks = childEl.__redom_lifecycle; 225 | const remount = parentEl === oldParent; 226 | let hooksFound = false; 227 | 228 | for (const hookName of hookNames) { 229 | if (!remount) { 230 | // if already mounted, skip this phase 231 | if (child !== childEl) { 232 | // only Views can have lifecycle events 233 | if (hookName in child) { 234 | hooks[hookName] = (hooks[hookName] || 0) + 1; 235 | } 236 | } 237 | } 238 | if (hooks[hookName]) { 239 | hooksFound = true; 240 | } 241 | } 242 | 243 | if (!hooksFound) { 244 | childEl.__redom_lifecycle = {}; 245 | return; 246 | } 247 | 248 | let traverse = parentEl; 249 | let triggered = false; 250 | 251 | if (remount || traverse?.__redom_mounted) { 252 | trigger(childEl, remount ? "onremount" : "onmount"); 253 | triggered = true; 254 | } 255 | 256 | while (traverse) { 257 | const parent = traverse.parentNode; 258 | 259 | if (!traverse.__redom_lifecycle) { 260 | traverse.__redom_lifecycle = {}; 261 | } 262 | 263 | const parentHooks = traverse.__redom_lifecycle; 264 | 265 | for (const hook in hooks) { 266 | parentHooks[hook] = (parentHooks[hook] || 0) + hooks[hook]; 267 | } 268 | 269 | if (triggered) { 270 | break; 271 | } 272 | if ( 273 | traverse.nodeType === Node.DOCUMENT_NODE || 274 | (shadowRootAvailable && traverse instanceof ShadowRoot) || 275 | parent?.__redom_mounted 276 | ) { 277 | trigger(traverse, remount ? "onremount" : "onmount"); 278 | triggered = true; 279 | } 280 | traverse = parent; 281 | } 282 | } 283 | 284 | function setStyle(view, arg1, arg2) { 285 | const el = getEl(view); 286 | 287 | if (typeof arg1 === "object") { 288 | for (const key in arg1) { 289 | setStyleValue(el, key, arg1[key]); 290 | } 291 | } else { 292 | setStyleValue(el, arg1, arg2); 293 | } 294 | } 295 | 296 | function setStyleValue(el, key, value) { 297 | el.style[key] = value == null ? "" : value; 298 | } 299 | 300 | /* global SVGElement */ 301 | 302 | 303 | const xlinkns = "http://www.w3.org/1999/xlink"; 304 | 305 | function setAttr(view, arg1, arg2) { 306 | setAttrInternal(view, arg1, arg2); 307 | } 308 | 309 | function setAttrInternal(view, arg1, arg2, initial) { 310 | const el = getEl(view); 311 | 312 | const isObj = typeof arg1 === "object"; 313 | 314 | if (isObj) { 315 | for (const key in arg1) { 316 | setAttrInternal(el, key, arg1[key], initial); 317 | } 318 | } else { 319 | const isSVG = el instanceof SVGElement; 320 | const isFunc = typeof arg2 === "function"; 321 | 322 | if (arg1 === "style" && typeof arg2 === "object") { 323 | setStyle(el, arg2); 324 | } else if (isSVG && isFunc) { 325 | el[arg1] = arg2; 326 | } else if (arg1 === "dataset") { 327 | setData(el, arg2); 328 | } else if (!isSVG && (arg1 in el || isFunc) && arg1 !== "list") { 329 | el[arg1] = arg2; 330 | } else { 331 | if (isSVG && arg1 === "xlink") { 332 | setXlink(el, arg2); 333 | return; 334 | } 335 | if (initial && arg1 === "class") { 336 | setClassName(el, arg2); 337 | return; 338 | } 339 | if (arg2 == null) { 340 | el.removeAttribute(arg1); 341 | } else { 342 | el.setAttribute(arg1, arg2); 343 | } 344 | } 345 | } 346 | } 347 | 348 | function setClassName(el, additionToClassName) { 349 | if (additionToClassName == null) { 350 | el.removeAttribute("class"); 351 | } else if (el.classList) { 352 | el.classList.add(additionToClassName); 353 | } else if ( 354 | typeof el.className === "object" && 355 | el.className && 356 | el.className.baseVal 357 | ) { 358 | el.className.baseVal = 359 | `${el.className.baseVal} ${additionToClassName}`.trim(); 360 | } else { 361 | el.className = `${el.className} ${additionToClassName}`.trim(); 362 | } 363 | } 364 | 365 | function setXlink(el, arg1, arg2) { 366 | if (typeof arg1 === "object") { 367 | for (const key in arg1) { 368 | setXlink(el, key, arg1[key]); 369 | } 370 | } else { 371 | if (arg2 != null) { 372 | el.setAttributeNS(xlinkns, arg1, arg2); 373 | } else { 374 | el.removeAttributeNS(xlinkns, arg1, arg2); 375 | } 376 | } 377 | } 378 | 379 | function setData(el, arg1, arg2) { 380 | if (typeof arg1 === "object") { 381 | for (const key in arg1) { 382 | setData(el, key, arg1[key]); 383 | } 384 | } else { 385 | if (arg2 != null) { 386 | el.dataset[arg1] = arg2; 387 | } else { 388 | delete el.dataset[arg1]; 389 | } 390 | } 391 | } 392 | 393 | function text(str) { 394 | return document.createTextNode(str != null ? str : ""); 395 | } 396 | 397 | function parseArgumentsInternal(element, args, initial) { 398 | for (const arg of args) { 399 | if (arg !== 0 && !arg) { 400 | continue; 401 | } 402 | 403 | const type = typeof arg; 404 | 405 | if (type === "function") { 406 | arg(element); 407 | } else if (type === "string" || type === "number") { 408 | element.appendChild(text(arg)); 409 | } else if (isNode(getEl(arg))) { 410 | mount(element, arg); 411 | } else if (arg.length) { 412 | parseArgumentsInternal(element, arg, initial); 413 | } else if (type === "object") { 414 | setAttrInternal(element, arg, null, initial); 415 | } 416 | } 417 | } 418 | 419 | function ensureEl(parent) { 420 | return typeof parent === "string" ? html(parent) : getEl(parent); 421 | } 422 | 423 | function getEl(parent) { 424 | return ( 425 | (parent.nodeType && parent) || (!parent.el && parent) || getEl(parent.el) 426 | ); 427 | } 428 | 429 | function isNode(arg) { 430 | return arg?.nodeType; 431 | } 432 | 433 | function dispatch(child, data, eventName = "redom") { 434 | const childEl = getEl(child); 435 | const event = new CustomEvent(eventName, { bubbles: true, detail: data }); 436 | childEl.dispatchEvent(event); 437 | } 438 | 439 | function setChildren(parent, ...children) { 440 | const parentEl = getEl(parent); 441 | let current = traverse(parent, children, parentEl.firstChild); 442 | 443 | while (current) { 444 | const next = current.nextSibling; 445 | 446 | unmount(parent, current); 447 | 448 | current = next; 449 | } 450 | } 451 | 452 | function traverse(parent, children, _current) { 453 | let current = _current; 454 | 455 | const childEls = Array(children.length); 456 | 457 | for (let i = 0; i < children.length; i++) { 458 | childEls[i] = children[i] && getEl(children[i]); 459 | } 460 | 461 | for (let i = 0; i < children.length; i++) { 462 | const child = children[i]; 463 | 464 | if (!child) { 465 | continue; 466 | } 467 | 468 | const childEl = childEls[i]; 469 | 470 | if (childEl === current) { 471 | current = current.nextSibling; 472 | continue; 473 | } 474 | 475 | if (isNode(childEl)) { 476 | const next = current?.nextSibling; 477 | const exists = child.__redom_index != null; 478 | const replace = exists && next === childEls[i + 1]; 479 | 480 | mount(parent, child, current, replace); 481 | 482 | if (replace) { 483 | current = next; 484 | } 485 | 486 | continue; 487 | } 488 | 489 | if (child.length != null) { 490 | current = traverse(parent, child, current); 491 | } 492 | } 493 | 494 | return current; 495 | } 496 | 497 | function listPool(View, key, initData) { 498 | return new ListPool(View, key, initData); 499 | } 500 | 501 | class ListPool { 502 | constructor(View, key, initData) { 503 | this.View = View; 504 | this.initData = initData; 505 | this.oldLookup = {}; 506 | this.lookup = {}; 507 | this.oldViews = []; 508 | this.views = []; 509 | 510 | if (key != null) { 511 | this.key = typeof key === "function" ? key : propKey(key); 512 | } 513 | } 514 | 515 | update(data, context) { 516 | const { View, key, initData } = this; 517 | const keySet = key != null; 518 | 519 | const oldLookup = this.lookup; 520 | const newLookup = {}; 521 | 522 | const newViews = Array(data.length); 523 | const oldViews = this.views; 524 | 525 | for (let i = 0; i < data.length; i++) { 526 | const item = data[i]; 527 | let view; 528 | 529 | if (keySet) { 530 | const id = key(item); 531 | 532 | view = oldLookup[id] || new View(initData, item, i, data); 533 | newLookup[id] = view; 534 | view.__redom_id = id; 535 | } else { 536 | view = oldViews[i] || new View(initData, item, i, data); 537 | } 538 | view.update?.(item, i, data, context); 539 | 540 | const el = getEl(view.el); 541 | 542 | el.__redom_view = view; 543 | newViews[i] = view; 544 | } 545 | 546 | this.oldViews = oldViews; 547 | this.views = newViews; 548 | 549 | this.oldLookup = oldLookup; 550 | this.lookup = newLookup; 551 | } 552 | } 553 | 554 | function propKey(key) { 555 | return function proppedKey(item) { 556 | return item[key]; 557 | }; 558 | } 559 | 560 | function list(parent, View, key, initData) { 561 | return new List(parent, View, key, initData); 562 | } 563 | 564 | class List { 565 | constructor(parent, View, key, initData) { 566 | this.View = View; 567 | this.initData = initData; 568 | this.views = []; 569 | this.pool = new ListPool(View, key, initData); 570 | this.el = ensureEl(parent); 571 | this.keySet = key != null; 572 | } 573 | 574 | update(data, context) { 575 | const { keySet } = this; 576 | const oldViews = this.views; 577 | 578 | this.pool.update(data || [], context); 579 | 580 | const { views, lookup } = this.pool; 581 | 582 | if (keySet) { 583 | for (let i = 0; i < oldViews.length; i++) { 584 | const oldView = oldViews[i]; 585 | const id = oldView.__redom_id; 586 | 587 | if (lookup[id] == null) { 588 | oldView.__redom_index = null; 589 | unmount(this, oldView); 590 | } 591 | } 592 | } 593 | 594 | for (let i = 0; i < views.length; i++) { 595 | const view = views[i]; 596 | 597 | view.__redom_index = i; 598 | } 599 | 600 | setChildren(this, views); 601 | 602 | if (keySet) { 603 | this.lookup = lookup; 604 | } 605 | this.views = views; 606 | } 607 | } 608 | 609 | List.extend = function extendList(parent, View, key, initData) { 610 | return List.bind(List, parent, View, key, initData); 611 | }; 612 | 613 | list.extend = List.extend; 614 | 615 | /* global Node */ 616 | 617 | 618 | function place(View, initData) { 619 | return new Place(View, initData); 620 | } 621 | 622 | class Place { 623 | constructor(View, initData) { 624 | this.el = text(""); 625 | this.visible = false; 626 | this.view = null; 627 | this._placeholder = this.el; 628 | 629 | if (View instanceof Node) { 630 | this._el = View; 631 | } else if (View.el instanceof Node) { 632 | this._el = View; 633 | this.view = View; 634 | } else { 635 | this._View = View; 636 | } 637 | 638 | this._initData = initData; 639 | } 640 | 641 | update(visible, data) { 642 | const placeholder = this._placeholder; 643 | const parentNode = this.el.parentNode; 644 | 645 | if (visible) { 646 | if (!this.visible) { 647 | if (this._el) { 648 | mount(parentNode, this._el, placeholder); 649 | unmount(parentNode, placeholder); 650 | 651 | this.el = getEl(this._el); 652 | this.visible = visible; 653 | } else { 654 | const View = this._View; 655 | const view = new View(this._initData); 656 | 657 | this.el = getEl(view); 658 | this.view = view; 659 | 660 | mount(parentNode, view, placeholder); 661 | unmount(parentNode, placeholder); 662 | } 663 | } 664 | this.view?.update?.(data); 665 | } else { 666 | if (this.visible) { 667 | if (this._el) { 668 | mount(parentNode, placeholder, this._el); 669 | unmount(parentNode, this._el); 670 | 671 | this.el = placeholder; 672 | this.visible = visible; 673 | 674 | return; 675 | } 676 | mount(parentNode, placeholder, this.view); 677 | unmount(parentNode, this.view); 678 | 679 | this.el = placeholder; 680 | this.view = null; 681 | } 682 | } 683 | this.visible = visible; 684 | } 685 | } 686 | 687 | function ref(ctx, key, value) { 688 | ctx[key] = value; 689 | return value; 690 | } 691 | 692 | /* global Node */ 693 | 694 | 695 | function router(parent, views, initData) { 696 | return new Router(parent, views, initData); 697 | } 698 | 699 | class Router { 700 | constructor(parent, views, initData) { 701 | this.el = ensureEl(parent); 702 | this.views = views; 703 | this.Views = views; // backwards compatibility 704 | this.initData = initData; 705 | } 706 | 707 | update(route, data) { 708 | if (route !== this.route) { 709 | const views = this.views; 710 | const View = views[route]; 711 | 712 | this.route = route; 713 | 714 | if (View && (View instanceof Node || View.el instanceof Node)) { 715 | this.view = View; 716 | } else { 717 | this.view = View && new View(this.initData, data); 718 | } 719 | 720 | setChildren(this.el, [this.view]); 721 | } 722 | this.view?.update?.(data, route); 723 | } 724 | } 725 | 726 | const ns = "http://www.w3.org/2000/svg"; 727 | 728 | function svg(query, ...args) { 729 | let element; 730 | 731 | const type = typeof query; 732 | 733 | if (type === "string") { 734 | element = createElement(query, ns); 735 | } else if (type === "function") { 736 | const Query = query; 737 | element = new Query(...args); 738 | } else { 739 | throw new Error("At least one argument required"); 740 | } 741 | 742 | parseArgumentsInternal(getEl(element), args, true); 743 | 744 | return element; 745 | } 746 | 747 | const s = svg; 748 | 749 | svg.extend = function extendSvg(...args) { 750 | return svg.bind(this, ...args); 751 | }; 752 | 753 | svg.ns = ns; 754 | 755 | function viewFactory(views, key) { 756 | if (!views || typeof views !== "object") { 757 | throw new Error("views must be an object"); 758 | } 759 | if (!key || typeof key !== "string") { 760 | throw new Error("key must be a string"); 761 | } 762 | return function factoryView(initData, item, i, data) { 763 | const viewKey = item[key]; 764 | const View = views[viewKey]; 765 | 766 | if (View) { 767 | return new View(initData, item, i, data); 768 | } 769 | 770 | throw new Error(`view ${viewKey} not found`); 771 | }; 772 | } 773 | 774 | export { List, ListPool, Place, Router, dispatch, el, h, html, list, listPool, mount, place, ref, router, s, setAttr, setChildren, setData, setStyle, setXlink, svg, text, unmount, viewFactory }; 775 | -------------------------------------------------------------------------------- /dist/redom.es.min.js: -------------------------------------------------------------------------------- 1 | function createElement(query,ns){const{tag:tag,id:id,className:className}=parse(query);const element=ns?document.createElementNS(ns,tag):document.createElement(tag);if(id){element.id=id}if(className){if(ns){element.setAttribute("class",className)}else{element.className=className}}return element}function parse(query){const chunks=query.split(/([.#])/);let className="";let id="";for(let i=1;i r.length) && (a = r.length); 9 | for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; 10 | return n; 11 | } 12 | function _classCallCheck(a, n) { 13 | if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); 14 | } 15 | function _construct(t, e, r) { 16 | if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); 17 | var o = [null]; 18 | o.push.apply(o, e); 19 | var p = new (t.bind.apply(t, o))(); 20 | return p; 21 | } 22 | function _defineProperties(e, r) { 23 | for (var t = 0; t < r.length; t++) { 24 | var o = r[t]; 25 | o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o); 26 | } 27 | } 28 | function _createClass(e, r, t) { 29 | return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", { 30 | writable: false 31 | }), e; 32 | } 33 | function _createForOfIteratorHelper(r, e) { 34 | var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; 35 | if (!t) { 36 | if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { 37 | t && (r = t); 38 | var n = 0, 39 | F = function () {}; 40 | return { 41 | s: F, 42 | n: function () { 43 | return n >= r.length ? { 44 | done: true 45 | } : { 46 | done: false, 47 | value: r[n++] 48 | }; 49 | }, 50 | e: function (r) { 51 | throw r; 52 | }, 53 | f: F 54 | }; 55 | } 56 | throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 57 | } 58 | var o, 59 | a = true, 60 | u = false; 61 | return { 62 | s: function () { 63 | t = t.call(r); 64 | }, 65 | n: function () { 66 | var r = t.next(); 67 | return a = r.done, r; 68 | }, 69 | e: function (r) { 70 | u = true, o = r; 71 | }, 72 | f: function () { 73 | try { 74 | a || null == t.return || t.return(); 75 | } finally { 76 | if (u) throw o; 77 | } 78 | } 79 | }; 80 | } 81 | function _isNativeReflectConstruct() { 82 | try { 83 | var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 84 | } catch (t) {} 85 | return (_isNativeReflectConstruct = function () { 86 | return !!t; 87 | })(); 88 | } 89 | function _toPrimitive(t, r) { 90 | if ("object" != typeof t || !t) return t; 91 | var e = t[Symbol.toPrimitive]; 92 | if (undefined !== e) { 93 | var i = e.call(t, r); 94 | if ("object" != typeof i) return i; 95 | throw new TypeError("@@toPrimitive must return a primitive value."); 96 | } 97 | return (String )(t); 98 | } 99 | function _toPropertyKey(t) { 100 | var i = _toPrimitive(t, "string"); 101 | return "symbol" == typeof i ? i : i + ""; 102 | } 103 | function _typeof(o) { 104 | "@babel/helpers - typeof"; 105 | 106 | return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { 107 | return typeof o; 108 | } : function (o) { 109 | return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; 110 | }, _typeof(o); 111 | } 112 | function _unsupportedIterableToArray(r, a) { 113 | if (r) { 114 | if ("string" == typeof r) return _arrayLikeToArray(r, a); 115 | var t = {}.toString.call(r).slice(8, -1); 116 | return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : undefined; 117 | } 118 | } 119 | 120 | function createElement(query, ns) { 121 | var _parse = parse(query), 122 | tag = _parse.tag, 123 | id = _parse.id, 124 | className = _parse.className; 125 | var element = ns ? document.createElementNS(ns, tag) : document.createElement(tag); 126 | if (id) { 127 | element.id = id; 128 | } 129 | if (className) { 130 | if (ns) { 131 | element.setAttribute("class", className); 132 | } else { 133 | element.className = className; 134 | } 135 | } 136 | return element; 137 | } 138 | function parse(query) { 139 | var chunks = query.split(/([.#])/); 140 | var className = ""; 141 | var id = ""; 142 | for (var i = 1; i < chunks.length; i += 2) { 143 | switch (chunks[i]) { 144 | case ".": 145 | className += " ".concat(chunks[i + 1]); 146 | break; 147 | case "#": 148 | id = chunks[i + 1]; 149 | } 150 | } 151 | return { 152 | className: className.trim(), 153 | tag: chunks[0] || "div", 154 | id: id 155 | }; 156 | } 157 | 158 | function html(query) { 159 | var element; 160 | var type = _typeof(query); 161 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 162 | args[_key - 1] = arguments[_key]; 163 | } 164 | if (type === "string") { 165 | element = createElement(query); 166 | } else if (type === "function") { 167 | var Query = query; 168 | element = _construct(Query, args); 169 | } else { 170 | throw new Error("At least one argument required"); 171 | } 172 | parseArgumentsInternal(getEl(element), args, true); 173 | return element; 174 | } 175 | var el = html; 176 | var h = html; 177 | html.extend = function extendHtml() { 178 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 179 | args[_key2] = arguments[_key2]; 180 | } 181 | return html.bind.apply(html, [this].concat(args)); 182 | }; 183 | 184 | function unmount(parent, _child) { 185 | var child = _child; 186 | var parentEl = getEl(parent); 187 | var childEl = getEl(child); 188 | if (child === childEl && childEl.__redom_view) { 189 | // try to look up the view if not provided 190 | child = childEl.__redom_view; 191 | } 192 | if (childEl.parentNode) { 193 | doUnmount(child, childEl, parentEl); 194 | parentEl.removeChild(childEl); 195 | } 196 | return child; 197 | } 198 | function doUnmount(child, childEl, parentEl) { 199 | var hooks = childEl.__redom_lifecycle; 200 | if (hooksAreEmpty(hooks)) { 201 | childEl.__redom_lifecycle = {}; 202 | return; 203 | } 204 | var traverse = parentEl; 205 | if (childEl.__redom_mounted) { 206 | trigger(childEl, "onunmount"); 207 | } 208 | while (traverse) { 209 | var parentHooks = traverse.__redom_lifecycle || {}; 210 | for (var hook in hooks) { 211 | if (parentHooks[hook]) { 212 | parentHooks[hook] -= hooks[hook]; 213 | } 214 | } 215 | if (hooksAreEmpty(parentHooks)) { 216 | traverse.__redom_lifecycle = null; 217 | } 218 | traverse = traverse.parentNode; 219 | } 220 | } 221 | function hooksAreEmpty(hooks) { 222 | if (hooks == null) { 223 | return true; 224 | } 225 | for (var key in hooks) { 226 | if (hooks[key]) { 227 | return false; 228 | } 229 | } 230 | return true; 231 | } 232 | 233 | var hookNames = ["onmount", "onremount", "onunmount"]; 234 | var shadowRootAvailable = typeof window !== "undefined" && "ShadowRoot" in window; 235 | function mount(parent, _child, before, replace) { 236 | var child = _child; 237 | var parentEl = getEl(parent); 238 | var childEl = getEl(child); 239 | if (child === childEl && childEl.__redom_view) { 240 | // try to look up the view if not provided 241 | child = childEl.__redom_view; 242 | } 243 | if (child !== childEl) { 244 | childEl.__redom_view = child; 245 | } 246 | var wasMounted = childEl.__redom_mounted; 247 | var oldParent = childEl.parentNode; 248 | if (wasMounted && oldParent !== parentEl) { 249 | doUnmount(child, childEl, oldParent); 250 | } 251 | if (before != null) { 252 | if (replace) { 253 | var beforeEl = getEl(before); 254 | if (beforeEl.__redom_mounted) { 255 | trigger(beforeEl, "onunmount"); 256 | } 257 | parentEl.replaceChild(childEl, beforeEl); 258 | } else { 259 | parentEl.insertBefore(childEl, getEl(before)); 260 | } 261 | } else { 262 | parentEl.appendChild(childEl); 263 | } 264 | doMount(child, childEl, parentEl, oldParent); 265 | return child; 266 | } 267 | function trigger(el, eventName) { 268 | var _view$eventName; 269 | if (eventName === "onmount" || eventName === "onremount") { 270 | el.__redom_mounted = true; 271 | } else if (eventName === "onunmount") { 272 | el.__redom_mounted = false; 273 | } 274 | var hooks = el.__redom_lifecycle; 275 | if (!hooks) { 276 | return; 277 | } 278 | var view = el.__redom_view; 279 | var hookCount = 0; 280 | view === null || view === undefined || (_view$eventName = view[eventName]) === null || _view$eventName === undefined || _view$eventName.call(view); 281 | for (var hook in hooks) { 282 | if (hook) { 283 | hookCount++; 284 | } 285 | } 286 | if (hookCount) { 287 | var traverse = el.firstChild; 288 | while (traverse) { 289 | var next = traverse.nextSibling; 290 | trigger(traverse, eventName); 291 | traverse = next; 292 | } 293 | } 294 | } 295 | function doMount(child, childEl, parentEl, oldParent) { 296 | var _traverse; 297 | if (!childEl.__redom_lifecycle) { 298 | childEl.__redom_lifecycle = {}; 299 | } 300 | var hooks = childEl.__redom_lifecycle; 301 | var remount = parentEl === oldParent; 302 | var hooksFound = false; 303 | var _iterator = _createForOfIteratorHelper(hookNames), 304 | _step; 305 | try { 306 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 307 | var hookName = _step.value; 308 | if (!remount) { 309 | // if already mounted, skip this phase 310 | if (child !== childEl) { 311 | // only Views can have lifecycle events 312 | if (hookName in child) { 313 | hooks[hookName] = (hooks[hookName] || 0) + 1; 314 | } 315 | } 316 | } 317 | if (hooks[hookName]) { 318 | hooksFound = true; 319 | } 320 | } 321 | } catch (err) { 322 | _iterator.e(err); 323 | } finally { 324 | _iterator.f(); 325 | } 326 | if (!hooksFound) { 327 | childEl.__redom_lifecycle = {}; 328 | return; 329 | } 330 | var traverse = parentEl; 331 | var triggered = false; 332 | if (remount || (_traverse = traverse) !== null && _traverse !== undefined && _traverse.__redom_mounted) { 333 | trigger(childEl, remount ? "onremount" : "onmount"); 334 | triggered = true; 335 | } 336 | while (traverse) { 337 | var parent = traverse.parentNode; 338 | if (!traverse.__redom_lifecycle) { 339 | traverse.__redom_lifecycle = {}; 340 | } 341 | var parentHooks = traverse.__redom_lifecycle; 342 | for (var hook in hooks) { 343 | parentHooks[hook] = (parentHooks[hook] || 0) + hooks[hook]; 344 | } 345 | if (triggered) { 346 | break; 347 | } 348 | if (traverse.nodeType === Node.DOCUMENT_NODE || shadowRootAvailable && traverse instanceof ShadowRoot || parent !== null && parent !== undefined && parent.__redom_mounted) { 349 | trigger(traverse, remount ? "onremount" : "onmount"); 350 | triggered = true; 351 | } 352 | traverse = parent; 353 | } 354 | } 355 | 356 | function setStyle(view, arg1, arg2) { 357 | var el = getEl(view); 358 | if (_typeof(arg1) === "object") { 359 | for (var key in arg1) { 360 | setStyleValue(el, key, arg1[key]); 361 | } 362 | } else { 363 | setStyleValue(el, arg1, arg2); 364 | } 365 | } 366 | function setStyleValue(el, key, value) { 367 | el.style[key] = value == null ? "" : value; 368 | } 369 | 370 | var xlinkns = "http://www.w3.org/1999/xlink"; 371 | function setAttr(view, arg1, arg2) { 372 | setAttrInternal(view, arg1, arg2); 373 | } 374 | function setAttrInternal(view, arg1, arg2, initial) { 375 | var el = getEl(view); 376 | var isObj = _typeof(arg1) === "object"; 377 | if (isObj) { 378 | for (var key in arg1) { 379 | setAttrInternal(el, key, arg1[key], initial); 380 | } 381 | } else { 382 | var isSVG = el instanceof SVGElement; 383 | var isFunc = typeof arg2 === "function"; 384 | if (arg1 === "style" && _typeof(arg2) === "object") { 385 | setStyle(el, arg2); 386 | } else if (isSVG && isFunc) { 387 | el[arg1] = arg2; 388 | } else if (arg1 === "dataset") { 389 | setData(el, arg2); 390 | } else if (!isSVG && (arg1 in el || isFunc) && arg1 !== "list") { 391 | el[arg1] = arg2; 392 | } else { 393 | if (isSVG && arg1 === "xlink") { 394 | setXlink(el, arg2); 395 | return; 396 | } 397 | if (initial && arg1 === "class") { 398 | setClassName(el, arg2); 399 | return; 400 | } 401 | if (arg2 == null) { 402 | el.removeAttribute(arg1); 403 | } else { 404 | el.setAttribute(arg1, arg2); 405 | } 406 | } 407 | } 408 | } 409 | function setClassName(el, additionToClassName) { 410 | if (additionToClassName == null) { 411 | el.removeAttribute("class"); 412 | } else if (el.classList) { 413 | el.classList.add(additionToClassName); 414 | } else if (_typeof(el.className) === "object" && el.className && el.className.baseVal) { 415 | el.className.baseVal = "".concat(el.className.baseVal, " ").concat(additionToClassName).trim(); 416 | } else { 417 | el.className = "".concat(el.className, " ").concat(additionToClassName).trim(); 418 | } 419 | } 420 | function setXlink(el, arg1, arg2) { 421 | if (_typeof(arg1) === "object") { 422 | for (var key in arg1) { 423 | setXlink(el, key, arg1[key]); 424 | } 425 | } else { 426 | if (arg2 != null) { 427 | el.setAttributeNS(xlinkns, arg1, arg2); 428 | } else { 429 | el.removeAttributeNS(xlinkns, arg1, arg2); 430 | } 431 | } 432 | } 433 | function setData(el, arg1, arg2) { 434 | if (_typeof(arg1) === "object") { 435 | for (var key in arg1) { 436 | setData(el, key, arg1[key]); 437 | } 438 | } else { 439 | if (arg2 != null) { 440 | el.dataset[arg1] = arg2; 441 | } else { 442 | delete el.dataset[arg1]; 443 | } 444 | } 445 | } 446 | 447 | function text(str) { 448 | return document.createTextNode(str != null ? str : ""); 449 | } 450 | 451 | function parseArgumentsInternal(element, args, initial) { 452 | var _iterator = _createForOfIteratorHelper(args), 453 | _step; 454 | try { 455 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 456 | var arg = _step.value; 457 | if (arg !== 0 && !arg) { 458 | continue; 459 | } 460 | var type = _typeof(arg); 461 | if (type === "function") { 462 | arg(element); 463 | } else if (type === "string" || type === "number") { 464 | element.appendChild(text(arg)); 465 | } else if (isNode(getEl(arg))) { 466 | mount(element, arg); 467 | } else if (arg.length) { 468 | parseArgumentsInternal(element, arg, initial); 469 | } else if (type === "object") { 470 | setAttrInternal(element, arg, null, initial); 471 | } 472 | } 473 | } catch (err) { 474 | _iterator.e(err); 475 | } finally { 476 | _iterator.f(); 477 | } 478 | } 479 | function ensureEl(parent) { 480 | return typeof parent === "string" ? html(parent) : getEl(parent); 481 | } 482 | function getEl(parent) { 483 | return parent.nodeType && parent || !parent.el && parent || getEl(parent.el); 484 | } 485 | function isNode(arg) { 486 | return arg === null || arg === undefined ? undefined : arg.nodeType; 487 | } 488 | 489 | function dispatch(child, data) { 490 | var eventName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "redom"; 491 | var childEl = getEl(child); 492 | var event = new CustomEvent(eventName, { 493 | bubbles: true, 494 | detail: data 495 | }); 496 | childEl.dispatchEvent(event); 497 | } 498 | 499 | function setChildren(parent) { 500 | var parentEl = getEl(parent); 501 | for (var _len = arguments.length, children = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 502 | children[_key - 1] = arguments[_key]; 503 | } 504 | var current = traverse(parent, children, parentEl.firstChild); 505 | while (current) { 506 | var next = current.nextSibling; 507 | unmount(parent, current); 508 | current = next; 509 | } 510 | } 511 | function traverse(parent, children, _current) { 512 | var current = _current; 513 | var childEls = Array(children.length); 514 | for (var i = 0; i < children.length; i++) { 515 | childEls[i] = children[i] && getEl(children[i]); 516 | } 517 | for (var _i = 0; _i < children.length; _i++) { 518 | var child = children[_i]; 519 | if (!child) { 520 | continue; 521 | } 522 | var childEl = childEls[_i]; 523 | if (childEl === current) { 524 | current = current.nextSibling; 525 | continue; 526 | } 527 | if (isNode(childEl)) { 528 | var _current2; 529 | var next = (_current2 = current) === null || _current2 === undefined ? undefined : _current2.nextSibling; 530 | var exists = child.__redom_index != null; 531 | var replace = exists && next === childEls[_i + 1]; 532 | mount(parent, child, current, replace); 533 | if (replace) { 534 | current = next; 535 | } 536 | continue; 537 | } 538 | if (child.length != null) { 539 | current = traverse(parent, child, current); 540 | } 541 | } 542 | return current; 543 | } 544 | 545 | function listPool(View, key, initData) { 546 | return new ListPool(View, key, initData); 547 | } 548 | var ListPool = /*#__PURE__*/function () { 549 | function ListPool(View, key, initData) { 550 | _classCallCheck(this, ListPool); 551 | this.View = View; 552 | this.initData = initData; 553 | this.oldLookup = {}; 554 | this.lookup = {}; 555 | this.oldViews = []; 556 | this.views = []; 557 | if (key != null) { 558 | this.key = typeof key === "function" ? key : propKey(key); 559 | } 560 | } 561 | return _createClass(ListPool, [{ 562 | key: "update", 563 | value: function update(data, context) { 564 | var View = this.View, 565 | key = this.key, 566 | initData = this.initData; 567 | var keySet = key != null; 568 | var oldLookup = this.lookup; 569 | var newLookup = {}; 570 | var newViews = Array(data.length); 571 | var oldViews = this.views; 572 | for (var i = 0; i < data.length; i++) { 573 | var _view$update, _view; 574 | var item = data[i]; 575 | var view = undefined; 576 | if (keySet) { 577 | var id = key(item); 578 | view = oldLookup[id] || new View(initData, item, i, data); 579 | newLookup[id] = view; 580 | view.__redom_id = id; 581 | } else { 582 | view = oldViews[i] || new View(initData, item, i, data); 583 | } 584 | (_view$update = (_view = view).update) === null || _view$update === undefined || _view$update.call(_view, item, i, data, context); 585 | var el = getEl(view.el); 586 | el.__redom_view = view; 587 | newViews[i] = view; 588 | } 589 | this.oldViews = oldViews; 590 | this.views = newViews; 591 | this.oldLookup = oldLookup; 592 | this.lookup = newLookup; 593 | } 594 | }]); 595 | }(); 596 | function propKey(key) { 597 | return function proppedKey(item) { 598 | return item[key]; 599 | }; 600 | } 601 | 602 | function list(parent, View, key, initData) { 603 | return new List(parent, View, key, initData); 604 | } 605 | var List = /*#__PURE__*/function () { 606 | function List(parent, View, key, initData) { 607 | _classCallCheck(this, List); 608 | this.View = View; 609 | this.initData = initData; 610 | this.views = []; 611 | this.pool = new ListPool(View, key, initData); 612 | this.el = ensureEl(parent); 613 | this.keySet = key != null; 614 | } 615 | return _createClass(List, [{ 616 | key: "update", 617 | value: function update(data, context) { 618 | var keySet = this.keySet; 619 | var oldViews = this.views; 620 | this.pool.update(data || [], context); 621 | var _this$pool = this.pool, 622 | views = _this$pool.views, 623 | lookup = _this$pool.lookup; 624 | if (keySet) { 625 | for (var i = 0; i < oldViews.length; i++) { 626 | var oldView = oldViews[i]; 627 | var id = oldView.__redom_id; 628 | if (lookup[id] == null) { 629 | oldView.__redom_index = null; 630 | unmount(this, oldView); 631 | } 632 | } 633 | } 634 | for (var _i = 0; _i < views.length; _i++) { 635 | var view = views[_i]; 636 | view.__redom_index = _i; 637 | } 638 | setChildren(this, views); 639 | if (keySet) { 640 | this.lookup = lookup; 641 | } 642 | this.views = views; 643 | } 644 | }]); 645 | }(); 646 | List.extend = function extendList(parent, View, key, initData) { 647 | return List.bind(List, parent, View, key, initData); 648 | }; 649 | list.extend = List.extend; 650 | 651 | function place(View, initData) { 652 | return new Place(View, initData); 653 | } 654 | var Place = /*#__PURE__*/function () { 655 | function Place(View, initData) { 656 | _classCallCheck(this, Place); 657 | this.el = text(""); 658 | this.visible = false; 659 | this.view = null; 660 | this._placeholder = this.el; 661 | if (View instanceof Node) { 662 | this._el = View; 663 | } else if (View.el instanceof Node) { 664 | this._el = View; 665 | this.view = View; 666 | } else { 667 | this._View = View; 668 | } 669 | this._initData = initData; 670 | } 671 | return _createClass(Place, [{ 672 | key: "update", 673 | value: function update(visible, data) { 674 | var placeholder = this._placeholder; 675 | var parentNode = this.el.parentNode; 676 | if (visible) { 677 | var _this$view, _this$view$update; 678 | if (!this.visible) { 679 | if (this._el) { 680 | mount(parentNode, this._el, placeholder); 681 | unmount(parentNode, placeholder); 682 | this.el = getEl(this._el); 683 | this.visible = visible; 684 | } else { 685 | var View = this._View; 686 | var view = new View(this._initData); 687 | this.el = getEl(view); 688 | this.view = view; 689 | mount(parentNode, view, placeholder); 690 | unmount(parentNode, placeholder); 691 | } 692 | } 693 | (_this$view = this.view) === null || _this$view === undefined || (_this$view$update = _this$view.update) === null || _this$view$update === undefined || _this$view$update.call(_this$view, data); 694 | } else { 695 | if (this.visible) { 696 | if (this._el) { 697 | mount(parentNode, placeholder, this._el); 698 | unmount(parentNode, this._el); 699 | this.el = placeholder; 700 | this.visible = visible; 701 | return; 702 | } 703 | mount(parentNode, placeholder, this.view); 704 | unmount(parentNode, this.view); 705 | this.el = placeholder; 706 | this.view = null; 707 | } 708 | } 709 | this.visible = visible; 710 | } 711 | }]); 712 | }(); 713 | 714 | function ref(ctx, key, value) { 715 | ctx[key] = value; 716 | return value; 717 | } 718 | 719 | function router(parent, views, initData) { 720 | return new Router(parent, views, initData); 721 | } 722 | var Router = /*#__PURE__*/function () { 723 | function Router(parent, views, initData) { 724 | _classCallCheck(this, Router); 725 | this.el = ensureEl(parent); 726 | this.views = views; 727 | this.Views = views; // backwards compatibility 728 | this.initData = initData; 729 | } 730 | return _createClass(Router, [{ 731 | key: "update", 732 | value: function update(route, data) { 733 | var _this$view, _this$view$update; 734 | if (route !== this.route) { 735 | var views = this.views; 736 | var View = views[route]; 737 | this.route = route; 738 | if (View && (View instanceof Node || View.el instanceof Node)) { 739 | this.view = View; 740 | } else { 741 | this.view = View && new View(this.initData, data); 742 | } 743 | setChildren(this.el, [this.view]); 744 | } 745 | (_this$view = this.view) === null || _this$view === undefined || (_this$view$update = _this$view.update) === null || _this$view$update === undefined || _this$view$update.call(_this$view, data, route); 746 | } 747 | }]); 748 | }(); 749 | 750 | var ns = "http://www.w3.org/2000/svg"; 751 | function svg(query) { 752 | var element; 753 | var type = _typeof(query); 754 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 755 | args[_key - 1] = arguments[_key]; 756 | } 757 | if (type === "string") { 758 | element = createElement(query, ns); 759 | } else if (type === "function") { 760 | var Query = query; 761 | element = _construct(Query, args); 762 | } else { 763 | throw new Error("At least one argument required"); 764 | } 765 | parseArgumentsInternal(getEl(element), args, true); 766 | return element; 767 | } 768 | var s = svg; 769 | svg.extend = function extendSvg() { 770 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 771 | args[_key2] = arguments[_key2]; 772 | } 773 | return svg.bind.apply(svg, [this].concat(args)); 774 | }; 775 | svg.ns = ns; 776 | 777 | function viewFactory(views, key) { 778 | if (!views || _typeof(views) !== "object") { 779 | throw new Error("views must be an object"); 780 | } 781 | if (!key || typeof key !== "string") { 782 | throw new Error("key must be a string"); 783 | } 784 | return function factoryView(initData, item, i, data) { 785 | var viewKey = item[key]; 786 | var View = views[viewKey]; 787 | if (View) { 788 | return new View(initData, item, i, data); 789 | } 790 | throw new Error("view ".concat(viewKey, " not found")); 791 | }; 792 | } 793 | 794 | exports.List = List; 795 | exports.ListPool = ListPool; 796 | exports.Place = Place; 797 | exports.Router = Router; 798 | exports.dispatch = dispatch; 799 | exports.el = el; 800 | exports.h = h; 801 | exports.html = html; 802 | exports.list = list; 803 | exports.listPool = listPool; 804 | exports.mount = mount; 805 | exports.place = place; 806 | exports.ref = ref; 807 | exports.router = router; 808 | exports.s = s; 809 | exports.setAttr = setAttr; 810 | exports.setChildren = setChildren; 811 | exports.setData = setData; 812 | exports.setStyle = setStyle; 813 | exports.setXlink = setXlink; 814 | exports.svg = svg; 815 | exports.text = text; 816 | exports.unmount = unmount; 817 | exports.viewFactory = viewFactory; 818 | 819 | })); 820 | -------------------------------------------------------------------------------- /dist/redom.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).redom={})}(this,(function(e){"use strict";function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var l,a=!0,u=!1;return{s:function(){i=i.call(e)},n:function(){var e=i.next();return a=e.done,e},e:function(e){u=!0,l=e},f:function(){try{a||null==i.return||i.return()}finally{if(u)throw l}}}}function l(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(l=function(){return!!e})()}function a(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var i=n.call(e,t);if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e,"string");return"symbol"==typeof t?t:t+""}function u(e){return u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},u(e)}function s(e,t){var n=function(e){for(var t=e.split(/([.#])/),n="",i="",r=1;r1?r-1:0),l=1;l1?n-1:0),r=1;r1?r-1:0),l=1;l2&&void 0!==arguments[2]?arguments[2]:"redom",i=V(e),r=new CustomEvent(n,{bubbles:!0,detail:t});i.dispatchEvent(r)},e.el=c,e.h=v,e.html=f,e.list=L,e.listPool=function(e,t,n){return new P(e,t,n)},e.mount=w,e.place=function(e,t){return new R(e,t)},e.ref=function(e,t,n){return e[t]=n,n},e.router=function(e,t,n){return new B(e,t,n)},e.s=M,e.setAttr=function(e,t,n){S(e,t,n)},e.setChildren=D,e.setData=A,e.setStyle=b,e.setXlink=k,e.svg=q,e.text=x,e.unmount=d,e.viewFactory=function(e,t){if(!e||"object"!==u(e))throw new Error("views must be an object");if(!t||"string"!=typeof t)throw new Error("key must be a string");return function(n,i,r,o){var l=i[t],a=e[l];if(a)return new a(n,i,r,o);throw new Error("view ".concat(l," not found"))}}})); -------------------------------------------------------------------------------- /esm/create-element.js: -------------------------------------------------------------------------------- 1 | export function createElement(query, ns) { 2 | const { tag, id, className } = parse(query); 3 | const element = ns 4 | ? document.createElementNS(ns, tag) 5 | : document.createElement(tag); 6 | 7 | if (id) { 8 | element.id = id; 9 | } 10 | 11 | if (className) { 12 | if (ns) { 13 | element.setAttribute("class", className); 14 | } else { 15 | element.className = className; 16 | } 17 | } 18 | 19 | return element; 20 | } 21 | 22 | function parse(query) { 23 | const chunks = query.split(/([.#])/); 24 | let className = ""; 25 | let id = ""; 26 | 27 | for (let i = 1; i < chunks.length; i += 2) { 28 | switch (chunks[i]) { 29 | case ".": 30 | className += ` ${chunks[i + 1]}`; 31 | break; 32 | 33 | case "#": 34 | id = chunks[i + 1]; 35 | } 36 | } 37 | 38 | return { 39 | className: className.trim(), 40 | tag: chunks[0] || "div", 41 | id, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /esm/dispatch.js: -------------------------------------------------------------------------------- 1 | import { getEl } from "./util.js"; 2 | 3 | export function dispatch(child, data, eventName = "redom") { 4 | const childEl = getEl(child); 5 | const event = new CustomEvent(eventName, { bubbles: true, detail: data }); 6 | childEl.dispatchEvent(event); 7 | } 8 | -------------------------------------------------------------------------------- /esm/html.js: -------------------------------------------------------------------------------- 1 | import { createElement } from "./create-element.js"; 2 | import { parseArgumentsInternal, getEl } from "./util.js"; 3 | 4 | export function html(query, ...args) { 5 | let element; 6 | 7 | const type = typeof query; 8 | 9 | if (type === "string") { 10 | element = createElement(query); 11 | } else if (type === "function") { 12 | const Query = query; 13 | element = new Query(...args); 14 | } else { 15 | throw new Error("At least one argument required"); 16 | } 17 | 18 | parseArgumentsInternal(getEl(element), args, true); 19 | 20 | return element; 21 | } 22 | 23 | export const el = html; 24 | export const h = html; 25 | 26 | html.extend = function extendHtml(...args) { 27 | return html.bind(this, ...args); 28 | }; 29 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | export { dispatch } from "./dispatch.js"; 2 | export { el, h, html } from "./html.js"; 3 | export { list, List } from "./list.js"; 4 | export { listPool, ListPool } from "./listpool.js"; 5 | export { mount } from "./mount.js"; 6 | export { unmount } from "./unmount.js"; 7 | export { place, Place } from "./place.js"; 8 | export { ref } from "./ref.js"; 9 | export { router, Router } from "./router.js"; 10 | export { setAttr, setXlink, setData } from "./setattr.js"; 11 | export { setStyle } from "./setstyle.js"; 12 | export { setChildren } from "./setchildren.js"; 13 | export { s, svg } from "./svg.js"; 14 | export { text } from "./text.js"; 15 | export { viewFactory } from "./view-factory.js"; 16 | -------------------------------------------------------------------------------- /esm/list.js: -------------------------------------------------------------------------------- 1 | import { setChildren } from "./setchildren.js"; 2 | import { ensureEl } from "./util.js"; 3 | import { unmount } from "./unmount.js"; 4 | import { ListPool } from "./listpool.js"; 5 | 6 | export function list(parent, View, key, initData) { 7 | return new List(parent, View, key, initData); 8 | } 9 | 10 | export class List { 11 | constructor(parent, View, key, initData) { 12 | this.View = View; 13 | this.initData = initData; 14 | this.views = []; 15 | this.pool = new ListPool(View, key, initData); 16 | this.el = ensureEl(parent); 17 | this.keySet = key != null; 18 | } 19 | 20 | update(data, context) { 21 | const { keySet } = this; 22 | const oldViews = this.views; 23 | 24 | this.pool.update(data || [], context); 25 | 26 | const { views, lookup } = this.pool; 27 | 28 | if (keySet) { 29 | for (let i = 0; i < oldViews.length; i++) { 30 | const oldView = oldViews[i]; 31 | const id = oldView.__redom_id; 32 | 33 | if (lookup[id] == null) { 34 | oldView.__redom_index = null; 35 | unmount(this, oldView); 36 | } 37 | } 38 | } 39 | 40 | for (let i = 0; i < views.length; i++) { 41 | const view = views[i]; 42 | 43 | view.__redom_index = i; 44 | } 45 | 46 | setChildren(this, views); 47 | 48 | if (keySet) { 49 | this.lookup = lookup; 50 | } 51 | this.views = views; 52 | } 53 | } 54 | 55 | List.extend = function extendList(parent, View, key, initData) { 56 | return List.bind(List, parent, View, key, initData); 57 | }; 58 | 59 | list.extend = List.extend; 60 | -------------------------------------------------------------------------------- /esm/listpool.js: -------------------------------------------------------------------------------- 1 | import { getEl } from "./util.js"; 2 | 3 | export function listPool(View, key, initData) { 4 | return new ListPool(View, key, initData); 5 | } 6 | 7 | export class ListPool { 8 | constructor(View, key, initData) { 9 | this.View = View; 10 | this.initData = initData; 11 | this.oldLookup = {}; 12 | this.lookup = {}; 13 | this.oldViews = []; 14 | this.views = []; 15 | 16 | if (key != null) { 17 | this.key = typeof key === "function" ? key : propKey(key); 18 | } 19 | } 20 | 21 | update(data, context) { 22 | const { View, key, initData } = this; 23 | const keySet = key != null; 24 | 25 | const oldLookup = this.lookup; 26 | const newLookup = {}; 27 | 28 | const newViews = Array(data.length); 29 | const oldViews = this.views; 30 | 31 | for (let i = 0; i < data.length; i++) { 32 | const item = data[i]; 33 | let view; 34 | 35 | if (keySet) { 36 | const id = key(item); 37 | 38 | view = oldLookup[id] || new View(initData, item, i, data); 39 | newLookup[id] = view; 40 | view.__redom_id = id; 41 | } else { 42 | view = oldViews[i] || new View(initData, item, i, data); 43 | } 44 | view.update?.(item, i, data, context); 45 | 46 | const el = getEl(view.el); 47 | 48 | el.__redom_view = view; 49 | newViews[i] = view; 50 | } 51 | 52 | this.oldViews = oldViews; 53 | this.views = newViews; 54 | 55 | this.oldLookup = oldLookup; 56 | this.lookup = newLookup; 57 | } 58 | } 59 | 60 | function propKey(key) { 61 | return function proppedKey(item) { 62 | return item[key]; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /esm/mount.js: -------------------------------------------------------------------------------- 1 | /* global Node, ShadowRoot */ 2 | 3 | import { getEl } from "./util.js"; 4 | import { doUnmount } from "./unmount.js"; 5 | 6 | const hookNames = ["onmount", "onremount", "onunmount"]; 7 | const shadowRootAvailable = 8 | typeof window !== "undefined" && "ShadowRoot" in window; 9 | 10 | export function mount(parent, _child, before, replace) { 11 | let child = _child; 12 | const parentEl = getEl(parent); 13 | const childEl = getEl(child); 14 | 15 | if (child === childEl && childEl.__redom_view) { 16 | // try to look up the view if not provided 17 | child = childEl.__redom_view; 18 | } 19 | 20 | if (child !== childEl) { 21 | childEl.__redom_view = child; 22 | } 23 | 24 | const wasMounted = childEl.__redom_mounted; 25 | const oldParent = childEl.parentNode; 26 | 27 | if (wasMounted && oldParent !== parentEl) { 28 | doUnmount(child, childEl, oldParent); 29 | } 30 | 31 | if (before != null) { 32 | if (replace) { 33 | const beforeEl = getEl(before); 34 | 35 | if (beforeEl.__redom_mounted) { 36 | trigger(beforeEl, "onunmount"); 37 | } 38 | 39 | parentEl.replaceChild(childEl, beforeEl); 40 | } else { 41 | parentEl.insertBefore(childEl, getEl(before)); 42 | } 43 | } else { 44 | parentEl.appendChild(childEl); 45 | } 46 | 47 | doMount(child, childEl, parentEl, oldParent); 48 | 49 | return child; 50 | } 51 | 52 | export function trigger(el, eventName) { 53 | if (eventName === "onmount" || eventName === "onremount") { 54 | el.__redom_mounted = true; 55 | } else if (eventName === "onunmount") { 56 | el.__redom_mounted = false; 57 | } 58 | 59 | const hooks = el.__redom_lifecycle; 60 | 61 | if (!hooks) { 62 | return; 63 | } 64 | 65 | const view = el.__redom_view; 66 | let hookCount = 0; 67 | 68 | view?.[eventName]?.(); 69 | 70 | for (const hook in hooks) { 71 | if (hook) { 72 | hookCount++; 73 | } 74 | } 75 | 76 | if (hookCount) { 77 | let traverse = el.firstChild; 78 | 79 | while (traverse) { 80 | const next = traverse.nextSibling; 81 | 82 | trigger(traverse, eventName); 83 | 84 | traverse = next; 85 | } 86 | } 87 | } 88 | 89 | function doMount(child, childEl, parentEl, oldParent) { 90 | if (!childEl.__redom_lifecycle) { 91 | childEl.__redom_lifecycle = {}; 92 | } 93 | 94 | const hooks = childEl.__redom_lifecycle; 95 | const remount = parentEl === oldParent; 96 | let hooksFound = false; 97 | 98 | for (const hookName of hookNames) { 99 | if (!remount) { 100 | // if already mounted, skip this phase 101 | if (child !== childEl) { 102 | // only Views can have lifecycle events 103 | if (hookName in child) { 104 | hooks[hookName] = (hooks[hookName] || 0) + 1; 105 | } 106 | } 107 | } 108 | if (hooks[hookName]) { 109 | hooksFound = true; 110 | } 111 | } 112 | 113 | if (!hooksFound) { 114 | childEl.__redom_lifecycle = {}; 115 | return; 116 | } 117 | 118 | let traverse = parentEl; 119 | let triggered = false; 120 | 121 | if (remount || traverse?.__redom_mounted) { 122 | trigger(childEl, remount ? "onremount" : "onmount"); 123 | triggered = true; 124 | } 125 | 126 | while (traverse) { 127 | const parent = traverse.parentNode; 128 | 129 | if (!traverse.__redom_lifecycle) { 130 | traverse.__redom_lifecycle = {}; 131 | } 132 | 133 | const parentHooks = traverse.__redom_lifecycle; 134 | 135 | for (const hook in hooks) { 136 | parentHooks[hook] = (parentHooks[hook] || 0) + hooks[hook]; 137 | } 138 | 139 | if (triggered) { 140 | break; 141 | } 142 | if ( 143 | traverse.nodeType === Node.DOCUMENT_NODE || 144 | (shadowRootAvailable && traverse instanceof ShadowRoot) || 145 | parent?.__redom_mounted 146 | ) { 147 | trigger(traverse, remount ? "onremount" : "onmount"); 148 | triggered = true; 149 | } 150 | traverse = parent; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /esm/place.js: -------------------------------------------------------------------------------- 1 | /* global Node */ 2 | 3 | import { text } from "./text.js"; 4 | import { mount } from "./mount.js"; 5 | import { unmount } from "./unmount.js"; 6 | import { getEl } from "./util.js"; 7 | 8 | export function place(View, initData) { 9 | return new Place(View, initData); 10 | } 11 | 12 | export class Place { 13 | constructor(View, initData) { 14 | this.el = text(""); 15 | this.visible = false; 16 | this.view = null; 17 | this._placeholder = this.el; 18 | 19 | if (View instanceof Node) { 20 | this._el = View; 21 | } else if (View.el instanceof Node) { 22 | this._el = View; 23 | this.view = View; 24 | } else { 25 | this._View = View; 26 | } 27 | 28 | this._initData = initData; 29 | } 30 | 31 | update(visible, data) { 32 | const placeholder = this._placeholder; 33 | const parentNode = this.el.parentNode; 34 | 35 | if (visible) { 36 | if (!this.visible) { 37 | if (this._el) { 38 | mount(parentNode, this._el, placeholder); 39 | unmount(parentNode, placeholder); 40 | 41 | this.el = getEl(this._el); 42 | this.visible = visible; 43 | } else { 44 | const View = this._View; 45 | const view = new View(this._initData); 46 | 47 | this.el = getEl(view); 48 | this.view = view; 49 | 50 | mount(parentNode, view, placeholder); 51 | unmount(parentNode, placeholder); 52 | } 53 | } 54 | this.view?.update?.(data); 55 | } else { 56 | if (this.visible) { 57 | if (this._el) { 58 | mount(parentNode, placeholder, this._el); 59 | unmount(parentNode, this._el); 60 | 61 | this.el = placeholder; 62 | this.visible = visible; 63 | 64 | return; 65 | } 66 | mount(parentNode, placeholder, this.view); 67 | unmount(parentNode, this.view); 68 | 69 | this.el = placeholder; 70 | this.view = null; 71 | } 72 | } 73 | this.visible = visible; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /esm/ref.js: -------------------------------------------------------------------------------- 1 | export function ref(ctx, key, value) { 2 | ctx[key] = value; 3 | return value; 4 | } 5 | -------------------------------------------------------------------------------- /esm/router.js: -------------------------------------------------------------------------------- 1 | /* global Node */ 2 | 3 | import { ensureEl } from "./util.js"; 4 | import { setChildren } from "./setchildren.js"; 5 | 6 | export function router(parent, views, initData) { 7 | return new Router(parent, views, initData); 8 | } 9 | 10 | export class Router { 11 | constructor(parent, views, initData) { 12 | this.el = ensureEl(parent); 13 | this.views = views; 14 | this.Views = views; // backwards compatibility 15 | this.initData = initData; 16 | } 17 | 18 | update(route, data) { 19 | if (route !== this.route) { 20 | const views = this.views; 21 | const View = views[route]; 22 | 23 | this.route = route; 24 | 25 | if (View && (View instanceof Node || View.el instanceof Node)) { 26 | this.view = View; 27 | } else { 28 | this.view = View && new View(this.initData, data); 29 | } 30 | 31 | setChildren(this.el, [this.view]); 32 | } 33 | this.view?.update?.(data, route); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /esm/setattr.js: -------------------------------------------------------------------------------- 1 | /* global SVGElement */ 2 | 3 | import { setStyle } from "./setstyle.js"; 4 | import { getEl } from "./util.js"; 5 | 6 | const xlinkns = "http://www.w3.org/1999/xlink"; 7 | 8 | export function setAttr(view, arg1, arg2) { 9 | setAttrInternal(view, arg1, arg2); 10 | } 11 | 12 | export function setAttrInternal(view, arg1, arg2, initial) { 13 | const el = getEl(view); 14 | 15 | const isObj = typeof arg1 === "object"; 16 | 17 | if (isObj) { 18 | for (const key in arg1) { 19 | setAttrInternal(el, key, arg1[key], initial); 20 | } 21 | } else { 22 | const isSVG = el instanceof SVGElement; 23 | const isFunc = typeof arg2 === "function"; 24 | 25 | if (arg1 === "style" && typeof arg2 === "object") { 26 | setStyle(el, arg2); 27 | } else if (isSVG && isFunc) { 28 | el[arg1] = arg2; 29 | } else if (arg1 === "dataset") { 30 | setData(el, arg2); 31 | } else if (!isSVG && (arg1 in el || isFunc) && arg1 !== "list") { 32 | el[arg1] = arg2; 33 | } else { 34 | if (isSVG && arg1 === "xlink") { 35 | setXlink(el, arg2); 36 | return; 37 | } 38 | if (initial && arg1 === "class") { 39 | setClassName(el, arg2); 40 | return; 41 | } 42 | if (arg2 == null) { 43 | el.removeAttribute(arg1); 44 | } else { 45 | el.setAttribute(arg1, arg2); 46 | } 47 | } 48 | } 49 | } 50 | 51 | function setClassName(el, additionToClassName) { 52 | if (additionToClassName == null) { 53 | el.removeAttribute("class"); 54 | } else if (el.classList) { 55 | el.classList.add(additionToClassName); 56 | } else if ( 57 | typeof el.className === "object" && 58 | el.className && 59 | el.className.baseVal 60 | ) { 61 | el.className.baseVal = 62 | `${el.className.baseVal} ${additionToClassName}`.trim(); 63 | } else { 64 | el.className = `${el.className} ${additionToClassName}`.trim(); 65 | } 66 | } 67 | 68 | export function setXlink(el, arg1, arg2) { 69 | if (typeof arg1 === "object") { 70 | for (const key in arg1) { 71 | setXlink(el, key, arg1[key]); 72 | } 73 | } else { 74 | if (arg2 != null) { 75 | el.setAttributeNS(xlinkns, arg1, arg2); 76 | } else { 77 | el.removeAttributeNS(xlinkns, arg1, arg2); 78 | } 79 | } 80 | } 81 | 82 | export function setData(el, arg1, arg2) { 83 | if (typeof arg1 === "object") { 84 | for (const key in arg1) { 85 | setData(el, key, arg1[key]); 86 | } 87 | } else { 88 | if (arg2 != null) { 89 | el.dataset[arg1] = arg2; 90 | } else { 91 | delete el.dataset[arg1]; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /esm/setchildren.js: -------------------------------------------------------------------------------- 1 | import { mount } from "./mount.js"; 2 | import { unmount } from "./unmount.js"; 3 | import { getEl, isNode } from "./util.js"; 4 | 5 | export function setChildren(parent, ...children) { 6 | const parentEl = getEl(parent); 7 | let current = traverse(parent, children, parentEl.firstChild); 8 | 9 | while (current) { 10 | const next = current.nextSibling; 11 | 12 | unmount(parent, current); 13 | 14 | current = next; 15 | } 16 | } 17 | 18 | function traverse(parent, children, _current) { 19 | let current = _current; 20 | 21 | const childEls = Array(children.length); 22 | 23 | for (let i = 0; i < children.length; i++) { 24 | childEls[i] = children[i] && getEl(children[i]); 25 | } 26 | 27 | for (let i = 0; i < children.length; i++) { 28 | const child = children[i]; 29 | 30 | if (!child) { 31 | continue; 32 | } 33 | 34 | const childEl = childEls[i]; 35 | 36 | if (childEl === current) { 37 | current = current.nextSibling; 38 | continue; 39 | } 40 | 41 | if (isNode(childEl)) { 42 | const next = current?.nextSibling; 43 | const exists = child.__redom_index != null; 44 | const replace = exists && next === childEls[i + 1]; 45 | 46 | mount(parent, child, current, replace); 47 | 48 | if (replace) { 49 | current = next; 50 | } 51 | 52 | continue; 53 | } 54 | 55 | if (child.length != null) { 56 | current = traverse(parent, child, current); 57 | } 58 | } 59 | 60 | return current; 61 | } 62 | -------------------------------------------------------------------------------- /esm/setstyle.js: -------------------------------------------------------------------------------- 1 | import { getEl } from "./util.js"; 2 | 3 | export function setStyle(view, arg1, arg2) { 4 | const el = getEl(view); 5 | 6 | if (typeof arg1 === "object") { 7 | for (const key in arg1) { 8 | setStyleValue(el, key, arg1[key]); 9 | } 10 | } else { 11 | setStyleValue(el, arg1, arg2); 12 | } 13 | } 14 | 15 | function setStyleValue(el, key, value) { 16 | el.style[key] = value == null ? "" : value; 17 | } 18 | -------------------------------------------------------------------------------- /esm/svg.js: -------------------------------------------------------------------------------- 1 | import { createElement } from "./create-element.js"; 2 | import { parseArgumentsInternal, getEl } from "./util.js"; 3 | 4 | const ns = "http://www.w3.org/2000/svg"; 5 | 6 | export function svg(query, ...args) { 7 | let element; 8 | 9 | const type = typeof query; 10 | 11 | if (type === "string") { 12 | element = createElement(query, ns); 13 | } else if (type === "function") { 14 | const Query = query; 15 | element = new Query(...args); 16 | } else { 17 | throw new Error("At least one argument required"); 18 | } 19 | 20 | parseArgumentsInternal(getEl(element), args, true); 21 | 22 | return element; 23 | } 24 | 25 | export const s = svg; 26 | 27 | svg.extend = function extendSvg(...args) { 28 | return svg.bind(this, ...args); 29 | }; 30 | 31 | svg.ns = ns; 32 | -------------------------------------------------------------------------------- /esm/text.js: -------------------------------------------------------------------------------- 1 | export function text(str) { 2 | return document.createTextNode(str != null ? str : ""); 3 | } 4 | -------------------------------------------------------------------------------- /esm/unmount.js: -------------------------------------------------------------------------------- 1 | import { getEl } from "./util.js"; 2 | import { trigger } from "./mount.js"; 3 | 4 | export function unmount(parent, _child) { 5 | let child = _child; 6 | const parentEl = getEl(parent); 7 | const childEl = getEl(child); 8 | 9 | if (child === childEl && childEl.__redom_view) { 10 | // try to look up the view if not provided 11 | child = childEl.__redom_view; 12 | } 13 | 14 | if (childEl.parentNode) { 15 | doUnmount(child, childEl, parentEl); 16 | 17 | parentEl.removeChild(childEl); 18 | } 19 | 20 | return child; 21 | } 22 | 23 | export function doUnmount(child, childEl, parentEl) { 24 | const hooks = childEl.__redom_lifecycle; 25 | 26 | if (hooksAreEmpty(hooks)) { 27 | childEl.__redom_lifecycle = {}; 28 | return; 29 | } 30 | 31 | let traverse = parentEl; 32 | 33 | if (childEl.__redom_mounted) { 34 | trigger(childEl, "onunmount"); 35 | } 36 | 37 | while (traverse) { 38 | const parentHooks = traverse.__redom_lifecycle || {}; 39 | 40 | for (const hook in hooks) { 41 | if (parentHooks[hook]) { 42 | parentHooks[hook] -= hooks[hook]; 43 | } 44 | } 45 | 46 | if (hooksAreEmpty(parentHooks)) { 47 | traverse.__redom_lifecycle = null; 48 | } 49 | 50 | traverse = traverse.parentNode; 51 | } 52 | } 53 | 54 | function hooksAreEmpty(hooks) { 55 | if (hooks == null) { 56 | return true; 57 | } 58 | for (const key in hooks) { 59 | if (hooks[key]) { 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | -------------------------------------------------------------------------------- /esm/util.js: -------------------------------------------------------------------------------- 1 | import { html } from "./html.js"; 2 | import { mount } from "./mount.js"; 3 | import { setAttrInternal } from "./setattr.js"; 4 | import { text } from "./text.js"; 5 | 6 | export function parseArguments(element, args) { 7 | parseArgumentsInternal(element, args); 8 | } 9 | 10 | export function parseArgumentsInternal(element, args, initial) { 11 | for (const arg of args) { 12 | if (arg !== 0 && !arg) { 13 | continue; 14 | } 15 | 16 | const type = typeof arg; 17 | 18 | if (type === "function") { 19 | arg(element); 20 | } else if (type === "string" || type === "number") { 21 | element.appendChild(text(arg)); 22 | } else if (isNode(getEl(arg))) { 23 | mount(element, arg); 24 | } else if (arg.length) { 25 | parseArgumentsInternal(element, arg, initial); 26 | } else if (type === "object") { 27 | setAttrInternal(element, arg, null, initial); 28 | } 29 | } 30 | } 31 | 32 | export function ensureEl(parent) { 33 | return typeof parent === "string" ? html(parent) : getEl(parent); 34 | } 35 | 36 | export function getEl(parent) { 37 | return ( 38 | (parent.nodeType && parent) || (!parent.el && parent) || getEl(parent.el) 39 | ); 40 | } 41 | 42 | export function isNode(arg) { 43 | return arg?.nodeType; 44 | } 45 | -------------------------------------------------------------------------------- /esm/view-factory.js: -------------------------------------------------------------------------------- 1 | export function viewFactory(views, key) { 2 | if (!views || typeof views !== "object") { 3 | throw new Error("views must be an object"); 4 | } 5 | if (!key || typeof key !== "string") { 6 | throw new Error("key must be a string"); 7 | } 8 | return function factoryView(initData, item, i, data) { 9 | const viewKey = item[key]; 10 | const View = views[viewKey]; 11 | 12 | if (View) { 13 | return new View(initData, item, i, data); 14 | } 15 | 16 | throw new Error(`view ${viewKey} not found`); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for redom 3.12 2 | // Project: https://github.com/redom/redom/, https://redom.js.org 3 | // Definitions by: Rauli Laine 4 | // Felix Nehrke 5 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 6 | // TypeScript Version: 2.2 7 | 8 | export type RedomElement = Node | RedomComponent; 9 | export type RedomQuery = string | RedomElement; 10 | export type RedomMiddleware = (el: HTMLElement | SVGElement) => void; 11 | export type RedomQueryArgumentValue = RedomElement | string | number | { [key: string]: any } | RedomMiddleware; 12 | export type RedomQueryArgument = RedomQueryArgumentValue | RedomQueryArgumentValue[]; 13 | export type RedomElQuery = string | Node | RedomComponentCreator; 14 | 15 | export interface RedomComponent { 16 | el: HTMLElement | SVGElement | RedomComponent; 17 | 18 | update?(item: any, index: number, data: any, context?: any): void; 19 | 20 | onmount?(): void; 21 | 22 | onremount?(): void; 23 | 24 | onunmount?(): void; 25 | } 26 | 27 | export interface RedomComponentClass { 28 | new (): RedomComponent; 29 | } 30 | 31 | export type RedomComponentConstructor = RedomComponentClass; 32 | export type RedomComponentFactoryFunction = () => RedomComponent 33 | export type RedomComponentCreator = RedomComponentConstructor | RedomComponentFactoryFunction 34 | 35 | export class ListPool { 36 | constructor(View: RedomComponentConstructor, key?: string, initData?: any); 37 | 38 | update(data: any[], context?: any): void; 39 | } 40 | 41 | export class List implements RedomComponent { 42 | el: HTMLElement | SVGElement; 43 | 44 | constructor(parent: RedomQuery, View: RedomComponentCreator, key?: string, initData?: any); 45 | 46 | update(data: any[], context?: any): void; 47 | 48 | onmount?(): void; 49 | 50 | onremount?(): void; 51 | 52 | onunmount?(): void; 53 | 54 | static extend(parent: RedomQuery, View: RedomComponentConstructor, key?: string, initData?: any): RedomComponentConstructor; 55 | } 56 | 57 | export class Place implements RedomComponent { 58 | el: HTMLElement | SVGElement; 59 | 60 | constructor(View: RedomComponentConstructor, initData?: any); 61 | 62 | update(visible: boolean, data?: any): void; 63 | } 64 | 65 | export class Router implements RedomComponent { 66 | el: HTMLElement | SVGElement; 67 | 68 | constructor(parent: RedomQuery, Views: RouterDictionary, initData?: any); 69 | 70 | update(route: string, data?: any): void; 71 | } 72 | 73 | export interface RouterDictionary { 74 | [key: string]: RedomComponentConstructor; 75 | } 76 | 77 | type HTMLElementOfStringLiteral = 78 | Q extends 'a' ? HTMLAnchorElement: 79 | Q extends 'area' ? HTMLAreaElement: 80 | Q extends 'audio' ? HTMLAudioElement: 81 | Q extends 'base' ? HTMLBaseElement: 82 | Q extends 'body' ? HTMLBodyElement: 83 | Q extends 'br' ? HTMLBRElement: 84 | Q extends 'button' ? HTMLButtonElement: 85 | Q extends 'canvas' ? HTMLCanvasElement: 86 | Q extends 'data' ? HTMLDataElement: 87 | Q extends 'datalist' ? HTMLDataListElement: 88 | Q extends 'details' ? HTMLDetailsElement: 89 | Q extends 'div' ? HTMLDivElement: 90 | Q extends 'dl' ? HTMLDListElement: 91 | Q extends 'embed' ? HTMLEmbedElement: 92 | Q extends 'fieldset' ? HTMLFieldSetElement: 93 | Q extends 'form' ? HTMLFormElement: 94 | Q extends 'hr' ? HTMLHRElement: 95 | Q extends 'head' ? HTMLHeadElement: 96 | Q extends 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' ? HTMLHeadingElement: 97 | Q extends 'html' ? HTMLHtmlElement: 98 | Q extends 'iframe' ? HTMLIFrameElement: 99 | Q extends 'img' ? HTMLImageElement: 100 | Q extends 'input' ? HTMLInputElement: 101 | Q extends 'label' ? HTMLLabelElement: 102 | Q extends 'legend' ? HTMLLegendElement: 103 | Q extends 'li' ? HTMLLIElement: 104 | Q extends 'link' ? HTMLLinkElement: 105 | Q extends 'map' ? HTMLMapElement: 106 | Q extends 'meta' ? HTMLMetaElement: 107 | Q extends 'meter' ? HTMLMeterElement: 108 | Q extends 'del' | 'ins' ? HTMLModElement: 109 | Q extends 'object' ? HTMLObjectElement: 110 | Q extends 'ol' ? HTMLOListElement: 111 | Q extends 'optgroup' ? HTMLOptGroupElement: 112 | Q extends 'option' ? HTMLOptionElement: 113 | Q extends 'output' ? HTMLOutputElement: 114 | Q extends 'p' ? HTMLParagraphElement: 115 | Q extends 'param' ? HTMLParamElement: 116 | Q extends 'pre' ? HTMLPreElement: 117 | Q extends 'progress' ? HTMLProgressElement: 118 | Q extends 'blockquote' | 'q' ? HTMLQuoteElement: 119 | Q extends 'script' ? HTMLScriptElement: 120 | Q extends 'select' ? HTMLSelectElement: 121 | Q extends 'slot' ? HTMLSlotElement: 122 | Q extends 'source' ? HTMLSourceElement: 123 | Q extends 'span' ? HTMLSpanElement: 124 | Q extends 'style' ? HTMLStyleElement: 125 | Q extends 'caption' ? HTMLTableCaptionElement: 126 | Q extends 'th' | 'td' ? HTMLTableCellElement: 127 | Q extends 'col' | 'colgroup' ? HTMLTableColElement: 128 | Q extends 'table' ? HTMLTableElement: 129 | Q extends 'tr' ? HTMLTableRowElement: 130 | Q extends 'thead' | 'tbody' | 'tfoot' ? HTMLTableSectionElement: 131 | Q extends 'template' ? HTMLTemplateElement: 132 | Q extends 'textarea' ? HTMLTextAreaElement: 133 | Q extends 'time' ? HTMLTimeElement: 134 | Q extends 'title' ? HTMLTitleElement: 135 | Q extends 'track' ? HTMLTrackElement: 136 | Q extends 'ul' ? HTMLUListElement: 137 | Q extends 'video' ? HTMLVideoElement: 138 | Q extends 'svg' ? SVGElement: 139 | HTMLElement 140 | 141 | type RedomElementOfElQuery = 142 | Q extends Node ? Q: 143 | Q extends RedomComponentClass ? InstanceType: 144 | Q extends RedomComponentFactoryFunction ? ReturnType: 145 | Q extends string ? HTMLElementOfStringLiteral: 146 | never 147 | 148 | export function html(query: Q, ...args: RedomQueryArgument[]): RedomElementOfElQuery; 149 | export function h(query: Q, ...args: RedomQueryArgument[]): RedomElementOfElQuery; 150 | export function el(query: Q, ...args: RedomQueryArgument[]): RedomElementOfElQuery; 151 | 152 | export function listPool(View: RedomComponentConstructor, key?: string, initData?: any): ListPool; 153 | export function list(parent: RedomQuery, View: RedomComponentConstructor, key?: string, initData?: any): List; 154 | 155 | export function mount(parent: RedomElement, child: T, before?: RedomElement, replace?: boolean): T; 156 | export function mount(parent: RedomElement, child: RedomElement, before?: RedomElement, replace?: boolean): RedomElement; 157 | export function unmount(parent: RedomElement, child: RedomElement): RedomElement; 158 | 159 | export function place(View: RedomComponentConstructor, initData?: any): Place; 160 | 161 | export function router(parent: RedomQuery, Views: RouterDictionary, initData?: any): Router; 162 | 163 | export function setAttr(view: RedomElement, arg1: string | object, arg2?: string): void; 164 | 165 | export function setStyle(view: RedomElement, arg1: string | object, arg2?: string): void; 166 | 167 | export function setChildren(parent: RedomElement, children: RedomElement[]): void; 168 | 169 | export function svg(query: RedomQuery, ...args: RedomQueryArgument[]): SVGElement; 170 | export function s(query: RedomQuery, ...args: RedomQueryArgument[]): SVGElement; 171 | 172 | export function text(str: string): Text; 173 | 174 | export namespace list { 175 | function extend(parent: RedomQuery, View: RedomComponentConstructor, key?: string, initData?: any): RedomComponentConstructor; 176 | } 177 | 178 | export namespace svg { 179 | function extend(query: RedomQuery): RedomComponentConstructor; 180 | } 181 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redom", 3 | "version": "4.3.0", 4 | "description": "Tiny turboboosted JavaScript library for creating user interfaces.", 5 | "main": "./dist/redom.cjs", 6 | "exports": { 7 | "import": { 8 | "default": "./esm/index.js", 9 | "types": "./index.d.ts" 10 | }, 11 | "require": { 12 | "default": "./dist/redom.cjs", 13 | "types": "./index.d.ts" 14 | } 15 | }, 16 | "type": "module", 17 | "sideEffects": false, 18 | "scripts": { 19 | "dev": "node watch", 20 | "dist": "gh-pages -d dist", 21 | "build-es": "rollup -i esm/index.js -f es -o dist/redom.es.js", 22 | "build-js": "rollup -c -n redom -f umd -i esm/index.js -o dist/redom.js", 23 | "build": "npm run build-es && npm run build-js && cp dist/redom.js dist/redom.cjs", 24 | "minify": "npm run minify-js && npm run minify-es", 25 | "minify-js": "terser dist/redom.js -cmo dist/redom.min.js", 26 | "minify-es": "terser dist/redom.es.js -o dist/redom.es.min.js", 27 | "preversion": "npm run build && npm run minify && npm run test", 28 | "postversion": "git push --tags", 29 | "prepublish": "npm run build && npm run minify && npm run test", 30 | "postpublish": "npm run dist", 31 | "test": "node test/index.js" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/redom/redom.git" 36 | }, 37 | "keywords": [ 38 | "redom", 39 | "javascript", 40 | "tiny", 41 | "dom", 42 | "library" 43 | ], 44 | "author": "Juha Lindstedt", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/redom/redom/issues" 48 | }, 49 | "homepage": "https://redom.js.org", 50 | "devDependencies": { 51 | "@babel/preset-env": "^7.26.0", 52 | "@rollup/plugin-babel": "^6.0.4", 53 | "gh-pages": "~6.3.0", 54 | "jsdom": "^26.0.0", 55 | "marked": "~15.0.6", 56 | "rollup": "~4.30.1", 57 | "rollup-plugin-buble": "~0.19.8", 58 | "rollup-plugin-node-resolve": "~5.2.0", 59 | "terser": "~5.37.0", 60 | "teston": "^0.9.6" 61 | }, 62 | "collective": { 63 | "type": "opencollective", 64 | "url": "https://opencollective.com/redom", 65 | "logo": "https://opencollective.com/redom/logo.txt" 66 | }, 67 | "funding": { 68 | "type": "opencollective", 69 | "url": "https://opencollective.com/redom" 70 | }, 71 | "types": "index.d.ts" 72 | } 73 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | 3 | export default { 4 | plugins: [babel({ babelHelpers: "bundled", presets: ["@babel/preset-env"] })], 5 | }; 6 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import t from "teston"; 2 | import { JSDOM } from "jsdom"; 3 | import { 4 | dispatch, 5 | el, 6 | html, 7 | list, 8 | listPool, 9 | place, 10 | router, 11 | svg, 12 | mount, 13 | unmount, 14 | setChildren, 15 | setAttr, 16 | setStyle, 17 | setXlink, 18 | setData, 19 | text, 20 | viewFactory, 21 | } from "../esm/index.js"; 22 | 23 | const { window } = new JSDOM(""); 24 | const { document } = window; 25 | 26 | global.document = document; 27 | global.dispatchEvent = window.dispatchEvent; 28 | global.CustomEvent = window.CustomEvent; 29 | global.Node = window.Node; 30 | 31 | global.SVGElement = window.SVGElement; 32 | global.createElement = document.createElement; 33 | 34 | document.createElement = function (tagName) { 35 | var element = createElement.call(document, tagName); 36 | 37 | element.style.webkitTestPrefixes = ""; 38 | 39 | return element; 40 | }; 41 | t("exports utils", function (t) { 42 | t.plan(2); 43 | t.ok(setAttr != null); 44 | t.ok(setStyle != null); 45 | }); 46 | 47 | t("dispatch", function (t) { 48 | t.plan(1); 49 | var div = el("div"); 50 | var childDiv = el("div"); 51 | 52 | div.appendChild(childDiv); 53 | 54 | div.addEventListener("redom", (e) => { 55 | t.equals(e.detail.hello, "world"); 56 | }); 57 | dispatch(childDiv, { hello: "world" }); 58 | }); 59 | 60 | t("element creation", function (t) { 61 | t("without tagName", function (t) { 62 | t.plan(1); 63 | var div = el(""); 64 | t.equals(div.outerHTML, "
"); 65 | }); 66 | t("just tagName", function (t) { 67 | t.plan(1); 68 | var hello = el("p", "Hello world!"); 69 | t.equals(hello.outerHTML, "

Hello world!

"); 70 | }); 71 | t("with Component constructor", function (t) { 72 | t.plan(2); 73 | var hello = el(function () { 74 | this.el = el("p"); 75 | }, "Hello world!"); 76 | t.equals(hello.el.outerHTML, "

Hello world!

"); 77 | 78 | var hello2 = svg(function () { 79 | this.el = svg("circle"); 80 | }, "Hello world!"); 81 | t.equals(hello2.el.outerHTML, "Hello world!"); 82 | }); 83 | t("one class", function (t) { 84 | t.plan(1); 85 | var hello = el("p.hello", "Hello world!"); 86 | t.equals(hello.outerHTML, '

Hello world!

'); 87 | }); 88 | t("append number", function (t) { 89 | t.plan(3); 90 | var one = el("div", 1); 91 | var minus = el("div", -1); 92 | var zero = el("div", 0); 93 | t.equals(one.outerHTML, "
1
"); 94 | t.equals(minus.outerHTML, "
-1
"); 95 | t.equals(zero.outerHTML, "
0
"); 96 | }); 97 | t("multiple class", function (t) { 98 | t.plan(1); 99 | var hello = el("p.hello.world", "Hello world!"); 100 | t.equals(hello.outerHTML, '

Hello world!

'); 101 | }); 102 | t("multiple class, mixed + setAttr + remove attribute", function (t) { 103 | t.plan(3); 104 | 105 | var hello = el("p.hello", { class: "world" }, "Hello world!"); 106 | t.equals(hello.outerHTML, '

Hello world!

'); 107 | 108 | setAttr(hello, { class: "world" }); 109 | t.equals(hello.outerHTML, '

Hello world!

'); 110 | 111 | setAttr(hello, { class: null }); 112 | t.equals(hello.outerHTML, "

Hello world!

"); 113 | }); 114 | t("append text", function (t) { 115 | t.plan(1); 116 | var hello = el("p", "Hello", " ", "world!"); 117 | t.equals(hello.outerHTML, "

Hello world!

"); 118 | }); 119 | t("ID", function (t) { 120 | t.plan(1); 121 | var hello = el("p#hello", "Hello world!"); 122 | t.equals(hello.outerHTML, '

Hello world!

'); 123 | }); 124 | t("styles with object + remove style", function (t) { 125 | t.plan(2); 126 | 127 | var hello = el("p", { style: { color: "red", opacity: 0 } }); 128 | t.equals(hello.outerHTML, '

'); 129 | 130 | setStyle(hello, "opacity", null); 131 | t.equals(hello.outerHTML, '

'); 132 | }); 133 | t("styles with String", function (t) { 134 | t.plan(1); 135 | var hello = el("p", { style: "color: red;" }); 136 | t.equals(hello.outerHTML, '

'); 137 | }); 138 | t("event handlers", function (t) { 139 | t.plan(1); 140 | var hello = el("p", { onclick: (e) => t.pass() }, "Hello world!"); 141 | hello.click(); 142 | }); 143 | t("attributes", function (t) { 144 | t.plan(1); 145 | var hello = el("p", { foo: "bar", zero: 0 }, "Hello world!"); 146 | t.equals(hello.outerHTML, '

Hello world!

'); 147 | }); 148 | t("children", function (t) { 149 | t.plan(1); 150 | var app = el("app", el("h1", "Hello world!")); 151 | t.equals(app.outerHTML, "

Hello world!

"); 152 | }); 153 | t("child views", function (t) { 154 | t.plan(1); 155 | function Test() { 156 | this.el = el("test"); 157 | } 158 | var app = el("app", new Test()); 159 | t.equals(app.outerHTML, ""); 160 | }); 161 | t("child view composition", function (t) { 162 | t.plan(1); 163 | function Test() { 164 | this.el = new (function () { 165 | this.el = el("test"); 166 | })(); 167 | } 168 | var app = el("app", new Test()); 169 | t.equals(app.outerHTML, ""); 170 | }); 171 | t("array", function (t) { 172 | t.plan(1); 173 | var ul = el( 174 | "ul", 175 | [1, 2, 3].map(function (i) { 176 | return el("li", i); 177 | }), 178 | ); 179 | t.equals(ul.outerHTML, "
  • 1
  • 2
  • 3
"); 180 | }); 181 | t("dataset + remove", function (t) { 182 | t.plan(2); 183 | 184 | var p = el("p", { dataset: { a: "test" } }); 185 | 186 | t.equals(p.outerHTML, '

'); 187 | 188 | setData(p, "a", null); 189 | 190 | t.equals(p.outerHTML, "

"); 191 | }); 192 | t("input list attribute", function (t) { 193 | t.plan(1); 194 | 195 | var input = el("input", { list: "asd" }); 196 | t.equals(input.outerHTML, ''); 197 | }); 198 | t("middleware", function (t) { 199 | t.plan(1); 200 | var app = el( 201 | "app", 202 | function (el) { 203 | el.setAttribute("ok", "!"); 204 | }, 205 | el("h1", "Hello world!"), 206 | ); 207 | t.equals(app.outerHTML, '

Hello world!

'); 208 | }); 209 | t("extend cached", function (t) { 210 | t.plan(1); 211 | 212 | var H1 = el.extend("h1"); 213 | var h1 = H1("Hello world!"); 214 | 215 | t.equals(h1.outerHTML, "

Hello world!

"); 216 | }); 217 | t("extend", function (t) { 218 | t.plan(1); 219 | 220 | var H2 = el.extend("h2"); 221 | var h2 = H2("Hello world!"); 222 | 223 | t.equals(h2.outerHTML, "

Hello world!

"); 224 | }); 225 | t("lifecycle events", function (t) { 226 | t.plan(1); 227 | var eventsFired = { 228 | onmount: 0, 229 | onremount: 0, 230 | onunmount: 0, 231 | }; 232 | function Item(id) { 233 | this.el = el("p"); 234 | this.onmount = function () { 235 | eventsFired.onmount++; 236 | }; 237 | this.onremount = function () { 238 | eventsFired.onremount++; 239 | }; 240 | this.onunmount = function () { 241 | eventsFired.onunmount++; 242 | }; 243 | } 244 | var item = new Item(1); 245 | var item2 = new Item(2); 246 | mount(document.body, item); // mount 247 | mount(document.head, item2); // mount 248 | mount(document.body, item2); // unmount & mount 249 | mount(document.body, item.el); // remount, test view lookup (__redom_view) 250 | unmount(document.body, item); // unmount 251 | mount(document.body, item, item2, true); // replace (unmount + mount) 252 | t.deepEqual(eventsFired, { 253 | onmount: 4, 254 | onremount: 1, 255 | onunmount: 3, 256 | }); 257 | }); 258 | t("component lifecycle events inside node element", function (t) { 259 | t.plan(1); 260 | var eventsFired = {}; 261 | function Item() { 262 | this.el = el("p"); 263 | this.onmount = function () { 264 | eventsFired.onmount = true; 265 | }; 266 | this.onremount = function () { 267 | eventsFired.onremount = true; 268 | }; 269 | this.onunmount = function () { 270 | eventsFired.onunmount = true; 271 | }; 272 | } 273 | var item = el("wrapper", new Item()); 274 | mount(document.body, item); 275 | mount(document.body, item); 276 | unmount(document.body, item); 277 | t.deepEqual(eventsFired, { 278 | onmount: true, 279 | onremount: true, 280 | onunmount: true, 281 | }); 282 | }); 283 | t( 284 | "lifecycle events on component when child unmounted using setChildren", 285 | function (t) { 286 | t.plan(1); 287 | var eventsFired = { 288 | onmount: 0, 289 | onunmount: 0, 290 | }; 291 | function Item() { 292 | this.el = el("p"); 293 | this.onmount = function () { 294 | eventsFired.onmount++; 295 | }; 296 | this.onunmount = function () { 297 | eventsFired.onunmount++; 298 | }; 299 | } 300 | var item = new Item(); 301 | var item2 = new Item(); 302 | mount(document.body, item); 303 | setChildren(item.el, [el("p")]); 304 | setChildren(item.el, [item2]); 305 | unmount(document.body, item); 306 | t.deepEqual(eventsFired, { 307 | onmount: 2, 308 | onunmount: 2, 309 | }); 310 | }, 311 | ); 312 | t( 313 | "lifecycle events on component when child with hooks unmounted using setChildren", 314 | function (t) { 315 | t.plan(1); 316 | var eventsFired = { 317 | onmount: 0, 318 | onunmount: 0, 319 | }; 320 | function MountHook() { 321 | this.el = el("p"); 322 | this.onmount = function () { 323 | eventsFired.onmount++; 324 | }; 325 | } 326 | function UnmountHook() { 327 | this.el = el("p"); 328 | this.onunmount = function () { 329 | eventsFired.onunmount++; 330 | }; 331 | } 332 | var mh = new MountHook(); 333 | var uh = new UnmountHook(); 334 | var uh2 = new UnmountHook(); 335 | mount(document.body, uh); 336 | setChildren(uh.el, [mh]); 337 | setChildren(uh.el, [uh2]); 338 | unmount(document.body, uh); 339 | t.deepEqual(eventsFired, { 340 | onmount: 1, 341 | onunmount: 2, 342 | }); 343 | }, 344 | ); 345 | t("setChildren", function (t) { 346 | t.plan(4); 347 | var h1 = el.extend("h1"); 348 | var a = h1("a"); 349 | var b = h1("b"); 350 | var c = text("c"); 351 | setChildren(document.body, [a, b]); 352 | t.equals(document.body.innerHTML, "

a

b

"); 353 | setChildren(document.body, a); 354 | t.equals(document.body.innerHTML, "

a

"); 355 | 356 | setChildren(document.body, [[a]], [b, [c]]); 357 | t.equals(document.body.innerHTML, "

a

b

c"); 358 | 359 | setChildren( 360 | document.body, 361 | el("select", el("option", { value: 1 }), el("option", { value: 2 })), 362 | ); 363 | t.equals( 364 | document.body.innerHTML, 365 | '', 366 | ); 367 | }); 368 | t("throw error when no arguments", function (t) { 369 | t.plan(1); 370 | t.throws(el, new Error("At least one argument required")); 371 | }); 372 | t("html alias", function (t) { 373 | t.plan(1); 374 | t.equals(el, html); 375 | }); 376 | }); 377 | 378 | t("listPool", function (t) { 379 | t.plan(1); 380 | 381 | listPool(function () {}, null, null); 382 | 383 | t.pass(); 384 | }); 385 | 386 | t("list", function (t) { 387 | t("without key", function (t) { 388 | t.plan(1); 389 | 390 | function Item() { 391 | this.el = el("li"); 392 | this.update = (data) => { 393 | this.el.textContent = data; 394 | }; 395 | } 396 | 397 | var items = list("ul", Item); 398 | items.update(); // empty list 399 | items.update([1, 2, 3]); 400 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 401 | }); 402 | t("with context", function (t) { 403 | t.plan(1); 404 | 405 | function Item() { 406 | this.el = el("li"); 407 | this.update = (data, id, items, context) => { 408 | this.el.textContent = context + data; 409 | }; 410 | } 411 | 412 | var items = list(el("ul"), Item); 413 | items.update(); 414 | items.update([1, 2, 3], 3); 415 | t.equals(items.el.outerHTML, "
  • 4
  • 5
  • 6
"); 416 | }); 417 | t("element parent", function (t) { 418 | t.plan(1); 419 | 420 | function Item() { 421 | this.el = el("li"); 422 | this.update = (data) => { 423 | this.el.textContent = data; 424 | }; 425 | } 426 | 427 | var items = list(el("ul"), Item); 428 | items.update(); // empty list 429 | items.update([1, 2, 3]); 430 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 431 | }); 432 | t("component parent", function (t) { 433 | t.plan(1); 434 | 435 | function Ul() { 436 | this.el = el("ul"); 437 | } 438 | 439 | function Item() { 440 | this.el = el("li"); 441 | this.update = (data) => { 442 | this.el.textContent = data; 443 | }; 444 | } 445 | 446 | var ul = new Ul(); 447 | 448 | var items = list(ul, Item); 449 | items.update(); // empty list 450 | items.update([1, 2, 3]); 451 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 452 | }); 453 | t("component parent composition", function (t) { 454 | t.plan(1); 455 | 456 | function Ul() { 457 | this.el = new (function () { 458 | this.el = el("ul"); 459 | })(); 460 | } 461 | 462 | function Item() { 463 | this.el = el("li"); 464 | this.update = (data) => { 465 | this.el.textContent = data; 466 | }; 467 | } 468 | 469 | var ul = new Ul(); 470 | 471 | var items = list(ul, Item); 472 | items.update(); // empty list 473 | items.update([1, 2, 3]); 474 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 475 | }); 476 | t("with key", function (t) { 477 | t.plan(4); 478 | 479 | function Item() { 480 | this.el = el("li"); 481 | this.update = function (data) { 482 | this.el.textContent = data.id; 483 | if (this.data) { 484 | t.equals(this.data.id, data.id); 485 | } 486 | this.data = data; 487 | }; 488 | } 489 | 490 | var items = list("ul", Item, "id"); 491 | 492 | items.update([{ id: 1 }, { id: 2 }, { id: 3 }]); 493 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 494 | items.update([{ id: 2 }, { id: 3 }, { id: 4 }]); 495 | t.equals(items.el.outerHTML, "
  • 2
  • 3
  • 4
"); 496 | }); 497 | t("with function key", function (t) { 498 | t.plan(4); 499 | 500 | function Item() { 501 | this.el = el("li"); 502 | this.update = (data) => { 503 | this.el.textContent = data.id; 504 | if (this.data) { 505 | t.equals(this.data.id, data.id); 506 | } 507 | this.data = data; 508 | }; 509 | } 510 | 511 | var items = list("ul", Item, (item) => item.id); 512 | 513 | items.update([{ id: 1 }, { id: 2 }, { id: 3 }]); 514 | t.equals(items.el.outerHTML, "
  • 1
  • 2
  • 3
"); 515 | items.update([{ id: 2 }, { id: 3 }, { id: 4 }]); 516 | t.equals(items.el.outerHTML, "
  • 2
  • 3
  • 4
"); 517 | }); 518 | t("adding / removing", function (t) { 519 | t.plan(3); 520 | 521 | function Item() { 522 | this.el = el("li"); 523 | this.update = (data) => { 524 | this.el.textContent = data; 525 | }; 526 | } 527 | 528 | var items = list("ul", Item); 529 | 530 | items.update([1]); 531 | t.equals(items.el.outerHTML, "
  • 1
"); 532 | items.update([1, 2]); 533 | t.equals(items.el.outerHTML, "
  • 1
  • 2
"); 534 | items.update([2]); 535 | t.equals(items.el.outerHTML, "
  • 2
"); 536 | }); 537 | t("extend", function (t) { 538 | t.plan(1); 539 | 540 | function Td() { 541 | this.el = el("td"); 542 | this.update = function (data) { 543 | this.el.textContent = data; 544 | }; 545 | } 546 | var Tr = list.extend("tr", Td); 547 | var Table = list.extend("table", Tr); 548 | 549 | var table = new Table(); 550 | 551 | table.update([ 552 | [1, 2, 3], 553 | [4, 5, 6], 554 | [7, 8, 9], 555 | ]); 556 | t.equals( 557 | table.el.outerHTML, 558 | "
123
456
789
", 559 | ); 560 | }); 561 | t("mount / unmount / remount", function (t) { 562 | t.plan(8); 563 | function Test() { 564 | this.el = el("test"); 565 | } 566 | Test.prototype.onmount = function () { 567 | t.pass(); 568 | }; 569 | Test.prototype.onremount = function () { 570 | t.pass(); 571 | }; 572 | Test.prototype.onunmount = function () { 573 | t.pass(); 574 | }; 575 | var test = new Test(); 576 | setChildren(document.body, []); 577 | mount(document.body, test); // ONMOUNT pass - 1 578 | mount(document.body, test); // ONREMOUNT pass - 2 579 | t.equals(document.body.outerHTML, ""); // pass - 3 580 | unmount(document.body, test.el); // ONUNMOUNT - 4 581 | mount(document.body, test.el); // ONMOUNT - 5 582 | mount(document.body, test.el); // ONREMOUNT - 6 583 | unmount(document.body, test); // ONUNMOUNT - 7 584 | t.equals(document.body.outerHTML, ""); // pass - 8 585 | }); 586 | t("special cases", function (t) { 587 | t.plan(1); 588 | function Td() { 589 | this.el = el("td"); 590 | } 591 | Td.prototype.update = function (data) { 592 | this.el.textContent = data; 593 | }; 594 | function Tr() { 595 | this.el = list("tr", Td); 596 | } 597 | Tr.prototype.update = function (data) { 598 | this.el.update(data); 599 | }; 600 | function Table() { 601 | this.el = list("table", Tr); 602 | } 603 | Table.prototype.update = function (data) { 604 | this.el.update(data); 605 | }; 606 | var table = new Table(); 607 | table.update([[1, 2, 3]]); 608 | setChildren(document.body, []); 609 | mount(document.body, table); 610 | t.equals( 611 | document.body.innerHTML, 612 | "
123
", 613 | ); 614 | }); 615 | t("unmounting unmounted", function (t) { 616 | t.plan(2); 617 | function Test() { 618 | this.el = el("div"); 619 | } 620 | var test = new Test(); 621 | unmount(document.body, test); 622 | mount(document.body, test); 623 | t.equals(document.body.contains(test.el), true); 624 | unmount(document.body, test); 625 | t.equals(document.body.contains(test.el), false); 626 | }); 627 | }); 628 | 629 | t("SVG", function (t) { 630 | t("creation", function (t) { 631 | t.plan(2); 632 | 633 | var circle = svg("circle"); 634 | t.equals(circle instanceof SVGElement, true); 635 | t.equals(circle.outerHTML, ""); 636 | }); 637 | t("one class", function (t) { 638 | t.plan(2); 639 | var circle = svg("circle.giraffe"); 640 | t.equals(circle instanceof SVGElement, true); 641 | t.equals(circle.outerHTML, ''); 642 | }); 643 | t("multiple class", function (t) { 644 | t.plan(2); 645 | var circle = svg("circle.giraffe.dog"); 646 | t.equals(circle instanceof SVGElement, true); 647 | t.equals(circle.outerHTML, ''); 648 | }); 649 | t("ID", function (t) { 650 | t.plan(2); 651 | var circle = svg("circle#monkey"); 652 | t.equals(circle instanceof SVGElement, true); 653 | t.equals(circle.outerHTML, ''); 654 | }); 655 | t("parameters", function (t) { 656 | t.plan(1); 657 | 658 | var circle = svg("circle", { cx: 1, cy: 2, r: 3 }); 659 | t.equals(circle.outerHTML, ''); 660 | }); 661 | t("event handler", function (t) { 662 | t.plan(1); 663 | 664 | var circle = svg("circle", { onclick: (e) => t.pass() }); 665 | circle.dispatchEvent(new CustomEvent("click", {})); 666 | }); 667 | t("Style string", function (t) { 668 | t.plan(1); 669 | 670 | var circle = svg("circle", { style: "color: red;" }); 671 | t.equals(circle.outerHTML, ''); 672 | }); 673 | t("Style object", function (t) { 674 | t.plan(1); 675 | 676 | var circle = svg("circle", { style: { color: "red" } }); 677 | t.equals(circle.outerHTML, ''); 678 | }); 679 | t("with text", function (t) { 680 | t.plan(1); 681 | 682 | var text = svg("text", "Hello!"); 683 | t.equals(text.outerHTML, "Hello!"); 684 | }); 685 | t("append text", function (t) { 686 | t.plan(1); 687 | 688 | var text = svg("text", "Hello", " ", "world!"); 689 | t.equals(text.outerHTML, "Hello world!"); 690 | }); 691 | t("extend cached", function (t) { 692 | t.plan(1); 693 | 694 | var Circle = svg.extend("circle"); 695 | var circle = new Circle(); 696 | t.equals(circle.outerHTML, ""); 697 | }); 698 | t("extend", function (t) { 699 | t.plan(1); 700 | 701 | var Line = svg.extend("line"); 702 | var line = new Line(); 703 | t.equals(line.outerHTML, ""); 704 | }); 705 | t("children", function (t) { 706 | t.plan(1); 707 | 708 | var graphic = svg("svg", svg("circle", { cx: 1, cy: 2, r: 3 })); 709 | t.equals( 710 | graphic.outerHTML, 711 | '', 712 | ); 713 | }); 714 | t("child view", function (t) { 715 | t.plan(1); 716 | 717 | function Circle() { 718 | this.el = svg("circle", { cx: 1, cy: 2, r: 3 }); 719 | } 720 | 721 | var graphic = svg("svg", new Circle()); 722 | t.equals( 723 | graphic.outerHTML, 724 | '', 725 | ); 726 | }); 727 | t("middleware", function (t) { 728 | t.plan(1); 729 | 730 | var graphic = svg( 731 | "svg", 732 | function (svg) { 733 | svg.setAttribute("ok", "!"); 734 | }, 735 | svg("circle", { cx: 1, cy: 2, r: 3 }), 736 | ); 737 | t.equals( 738 | graphic.outerHTML, 739 | '', 740 | ); 741 | }); 742 | t("throw error when no arguments", function (t) { 743 | t.plan(1); 744 | t.throws(svg, new Error("At least one argument required")); 745 | }); 746 | t("xlink + remove", function (t) { 747 | t.plan(2); 748 | 749 | var use = svg("use", { xlink: { href: "#menu" } }); 750 | t.equals(use.outerHTML, ''); 751 | 752 | setXlink(use, "href", null); 753 | t.equals(use.outerHTML, ""); 754 | }); 755 | }); 756 | 757 | t("router", function (t) { 758 | t.plan(2); 759 | function A() { 760 | this.el = el("a"); 761 | } 762 | A.prototype.update = function (val) { 763 | this.el.textContent = val; 764 | }; 765 | 766 | function B() { 767 | this.el = el("b"); 768 | } 769 | 770 | B.prototype.update = function (val) { 771 | this.el.textContent = val; 772 | }; 773 | 774 | var _router = router(".test", { 775 | a: A, 776 | b: B, 777 | }); 778 | _router.update("a", 1); 779 | t.equals(_router.el.outerHTML, ''); 780 | _router.update("b", 2); 781 | t.equals(_router.el.outerHTML, '
2
'); 782 | }); 783 | t("router with elements", function (t) { 784 | t.plan(2); 785 | 786 | var _router = router(".test", { 787 | a: el(".a"), 788 | b: el(".b"), 789 | }); 790 | 791 | _router.update("a"); 792 | t.equals( 793 | _router.el.outerHTML, 794 | '
', 795 | ); 796 | 797 | _router.update("b"); 798 | t.equals( 799 | _router.el.outerHTML, 800 | '
', 801 | ); 802 | }); 803 | t("router with component instances", function (t) { 804 | t.plan(2); 805 | 806 | function A() { 807 | this.el = el(".a"); 808 | } 809 | 810 | function B() { 811 | this.el = el(".b"); 812 | } 813 | 814 | var _router = router(".test", { 815 | a: new A(), 816 | b: new B(), 817 | }); 818 | 819 | _router.update("a"); 820 | t.equals( 821 | _router.el.outerHTML, 822 | '
', 823 | ); 824 | 825 | _router.update("b"); 826 | t.equals( 827 | _router.el.outerHTML, 828 | '
', 829 | ); 830 | }); 831 | t("lifecycle event order consistency check", function (t) { 832 | t.plan(1); 833 | var logs = []; 834 | 835 | var nApexes = 3; 836 | var nLeaves = 2; 837 | var nBranches = 1; 838 | 839 | function Base(name, content) { 840 | var _el = html("", content); 841 | 842 | function onmount() { 843 | logs.push(name + " mounted: " + typeof _el.getBoundingClientRect()); 844 | } 845 | 846 | function onunmount() { 847 | logs.push(name + " unmount: " + typeof _el.getBoundingClientRect()); 848 | } 849 | 850 | return { el: _el, onmount, onunmount }; 851 | } 852 | 853 | function Apex() { 854 | return Base("Apex"); 855 | } 856 | 857 | function Leaf() { 858 | var size = nApexes; 859 | var apexes = []; 860 | for (var i = 0; i < size; i++) { 861 | apexes.push(Apex()); 862 | } 863 | return Base("Leaf", apexes); 864 | } 865 | 866 | function Branch() { 867 | var size = nLeaves; 868 | var leaves = []; 869 | for (var i = 0; i < size; i++) { 870 | leaves.push(Leaf()); 871 | } 872 | return Base("Branch", leaves); 873 | } 874 | 875 | function Tree() { 876 | var size = nBranches; 877 | var branches = []; 878 | for (var i = 0; i < size; i++) { 879 | branches.push(Branch()); 880 | } 881 | return Base("Tree", branches); 882 | } 883 | 884 | var expectedLog = []; 885 | // onmount -- mounted 886 | expectedLog.push("Tree mounted: object"); 887 | for (let i = 0; i < nBranches; i++) { 888 | expectedLog.push("Branch mounted: object"); 889 | for (let j = 0; j < nLeaves; j++) { 890 | expectedLog.push("Leaf mounted: object"); 891 | for (let k = 0; k < nApexes; k++) { 892 | expectedLog.push("Apex mounted: object"); 893 | } 894 | } 895 | } 896 | 897 | // onunmount -- unmounting 898 | expectedLog.push("Tree unmount: object"); 899 | for (let i = 0; i < nBranches; i++) { 900 | expectedLog.push("Branch unmount: object"); 901 | for (let j = 0; j < nLeaves; j++) { 902 | expectedLog.push("Leaf unmount: object"); 903 | for (let k = 0; k < nApexes; k++) { 904 | expectedLog.push("Apex unmount: object"); 905 | } 906 | } 907 | } 908 | 909 | var tree = Tree(); 910 | mount(document.body, tree); 911 | unmount(document.body, tree); 912 | 913 | t.deepEqual(logs, expectedLog); 914 | }); 915 | 916 | t("element place", function (t) { 917 | t.plan(3); 918 | 919 | var elementPlace = place(el("h1", "Hello RE:DOM!")); 920 | 921 | setChildren(document.body, []); 922 | 923 | mount(document.body, elementPlace); 924 | mount(document.body, el("p", "After")); 925 | t.equals(document.body.innerHTML, "

After

"); 926 | 927 | elementPlace.update(true); 928 | t.equals(document.body.innerHTML, "

Hello RE:DOM!

After

"); 929 | 930 | elementPlace.update(false); 931 | t.equals(document.body.innerHTML, "

After

"); 932 | }); 933 | 934 | t("extended element place", function (t) { 935 | t.plan(3); 936 | 937 | var elementPlace = place(el.extend("h1", "Hello RE:DOM!")); 938 | 939 | setChildren(document.body, []); 940 | 941 | mount(document.body, elementPlace); 942 | mount(document.body, el("p", "After")); 943 | t.equals(document.body.innerHTML, "

After

"); 944 | 945 | elementPlace.update(true); 946 | t.equals(document.body.innerHTML, "

Hello RE:DOM!

After

"); 947 | 948 | elementPlace.update(false); 949 | t.equals(document.body.innerHTML, "

After

"); 950 | 951 | elementPlace.update(true); 952 | }); 953 | 954 | t("component place", function (t) { 955 | t.plan(3); 956 | 957 | function B(initData) { 958 | this.el = el(".b", "place!"); 959 | 960 | t.equals(initData, 1); 961 | } 962 | 963 | B.prototype.update = function (data) { 964 | this.el.textContent = data; 965 | }; 966 | 967 | function A() { 968 | this.el = el(".a", (this.place = place(B, 1))); 969 | } 970 | 971 | var a = new A(); 972 | 973 | mount(document.body, a); 974 | 975 | a.place.update(true, 2); 976 | 977 | t.equals(a.el.innerHTML, '
2
'); 978 | 979 | a.place.update(false, 2); 980 | 981 | t.equals(a.el.innerHTML, ""); 982 | unmount(document.body, a); 983 | }); 984 | 985 | t("component instance place", function (t) { 986 | t.plan(2); 987 | 988 | function B(initData) { 989 | this.el = el(".b", "place!"); 990 | } 991 | 992 | B.prototype.update = function (data) { 993 | this.el.textContent = data; 994 | }; 995 | 996 | function A() { 997 | this.el = el(".a", (this.place = place(new B()))); 998 | } 999 | 1000 | var a = new A(); 1001 | 1002 | mount(document.body, a); 1003 | 1004 | a.place.update(true, 2); 1005 | 1006 | t.equals(a.el.innerHTML, '
2
'); 1007 | 1008 | a.place.update(false); 1009 | 1010 | t.equals(a.el.innerHTML, ""); 1011 | unmount(document.body, a); 1012 | }); 1013 | 1014 | t("component moved below non-redom element", function (t) { 1015 | t.plan(3); 1016 | var div = document.createElement("div"); 1017 | document.body.appendChild(div); 1018 | var targetDiv = document.createElement("div"); 1019 | document.body.appendChild(targetDiv); 1020 | 1021 | function Item() { 1022 | this.el = el("p"); 1023 | this.onmount = function () {}; 1024 | } 1025 | 1026 | var item = new Item(); 1027 | mount(div, item); 1028 | t.deepEqual(div.__redom_lifecycle, { onmount: 1 }); 1029 | 1030 | targetDiv.appendChild(div); 1031 | t.ok(targetDiv && targetDiv.__redom_lifecycle == null); 1032 | 1033 | unmount(div, item); 1034 | t.ok(targetDiv && targetDiv.__redom_lifecycle == null); 1035 | }); 1036 | 1037 | t("optimized list diff", function (t) { 1038 | t.plan(1); 1039 | var remounts = 0; 1040 | 1041 | function Item() { 1042 | this.el = el("p"); 1043 | this.onremount = function () { 1044 | remounts++; 1045 | }; 1046 | } 1047 | var items = list(el("list"), Item, "id"); 1048 | 1049 | items.update( 1050 | "a b c d e f g".split(" ").map(function (id) { 1051 | return { id: id }; 1052 | }), 1053 | ); 1054 | items.update( 1055 | "a e c d b f g".split(" ").map(function (id) { 1056 | return { id: id }; 1057 | }), 1058 | ); 1059 | 1060 | t.equals(remounts, 1); 1061 | }); 1062 | 1063 | t("view factory", function (t) { 1064 | t.plan(4); 1065 | 1066 | function A() { 1067 | this.el = el("a"); 1068 | t.ok("A class constructor called"); 1069 | } 1070 | 1071 | A.prototype.update = function () { 1072 | t.ok("A class update called"); 1073 | }; 1074 | 1075 | function B() { 1076 | this.el = el("b"); 1077 | t.ok("B class constructor called"); 1078 | } 1079 | 1080 | B.prototype.update = function () { 1081 | t.ok("B class update called"); 1082 | }; 1083 | 1084 | var items = list( 1085 | "list", 1086 | viewFactory( 1087 | { 1088 | a: A, 1089 | b: B, 1090 | }, 1091 | "type", 1092 | ), 1093 | "id", 1094 | { a: 1, b: 2 }, 1095 | ); 1096 | 1097 | items.update([{ type: "a" }, { type: "b" }]); 1098 | }); 1099 | 1100 | t("view factory edge cases", function (t) { 1101 | t.plan(3); 1102 | try { 1103 | viewFactory(); 1104 | } catch (err) { 1105 | t.equals(err.message, "views must be an object"); 1106 | } 1107 | try { 1108 | viewFactory({}); 1109 | } catch (err) { 1110 | t.equals(err.message, "key must be a string"); 1111 | } 1112 | var items = list("list", viewFactory({}, "type")); 1113 | 1114 | try { 1115 | items.update([{ type: "a" }]); 1116 | } catch (err) { 1117 | t.equals(err.message, "view a not found"); 1118 | } 1119 | }); 1120 | -------------------------------------------------------------------------------- /watch.js: -------------------------------------------------------------------------------- 1 | import cp from "child_process"; 2 | import fs from "fs"; 3 | 4 | fs.watch("esm", run("build")); 5 | fs.watch("dist/redom.js", () => run("minify")); // don't do init run 6 | fs.watch("test/index.js", run("test")); 7 | 8 | function run(script) { 9 | const child = cp.spawn("npm", ["run", script]); 10 | 11 | child.stdout.pipe(process.stdout); 12 | child.stderr.pipe(process.stderr); 13 | 14 | return () => run(script); 15 | } 16 | --------------------------------------------------------------------------------