${state.title}
- List of ${state.paragraphs.length} paragraphs: -- ${
- state.paragraphs
- .map(p => `
- ${p.title} `) - }
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..943f5e9f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # not working due missing www. +open_collective: hyperHTML +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: https://www.patreon.com/webreflection diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..58f187be --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 00000000..73cf8d65 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: build + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: npm test + - run: npm run coverage --if-present + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 58b805fe..3cb01d4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store -node_modules/ \ No newline at end of file +node_modules/ +coverage/ + diff --git a/.npmignore b/.npmignore index f4794d23..2b02e166 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,14 @@ +coverage/* +esm/.eslintrc +logo/* node_modules/* test/* +_config.yml .DS_Store .gitignore -.travis.yml \ No newline at end of file +.travis.yml +.github/ISSUE_TEMPLATE.md +babel-plugins.json +CHANGELOG.md +package-lock.json +rollup.config.js diff --git a/.travis.yml b/.travis.yml index 85e9c12e..cc127136 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: node_js node_js: - - 7 + - stable git: depth: 1 branches: only: - master + - /^greenkeeper/.*$/ +after_success: + - "npm run coveralls" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..60ac2164 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,147 @@ +# hyper(html) Changelog + +### v.2.23 + * monkey patched rollup generated code to export once the same module shared within sub-modules + +### v2.22 + * using latest domtagger + +### v2.21 + * refactored out all dependencies + +### v2.20 + * re-tested every single supported browser nd fixed few outstanding issues with the 2.19 release + +### v2.19 + * refactored out most of the code + * finally managed to have coveralls show coverage stats + * attributes can have spaces around as per DOM standard - [#244](https://github.com/WebReflection/hyperHTML/issues/224) + * fixed SVG (non-critical) errors when interpolations are used for numerically expected values + * fixed minor issues with Edge attributes + * changed the unique id so if any of your logic was trusting `_hyper: ....;` comments you need to update your logic - [#300](https://github.com/WebReflection/hyperHTML/issues/300) + +### v2.16.8 + * improved MutationObserver and fallback so that double `dis/connected` events won't happen again + * exposed `observe` utility for 3rd parts so that it is possible to observe any node, not only those defined via template literals. Once observed, a node can have `connected` and `disconnected` listeners that will be triggered automatically. + +### v2.16 + * modified `Wire` class to better handle "_same target_" case, making the `haunted.html` demo work same way as if it was bound to the node, through `valueOf()` invoke which would result in just exactly the same node if the wired content produced a node instead of a fragment. While regular users won't be affected, this is an implementation detail that changes a lot for libraries integrating `hyperHTML.wire` in their logic, making wires as fast as `bind` in most component related use cases. + +### v2.15 + * added [invokable slots](https://github.com/WebReflection/hyperHTML/pull/282#issuecomment-433614081) to let developers explore patterns through callbacks that will receive a unique live node for weak references while rendered. + +### v2.14 + * updated [domdiff](https://github.com/WebReflection/domdiff#domdiff) to match [petit-dom](https://github.com/yelouafi/petit-dom) performance + * up to 3X performance on huge lists + * improved reliability over random changes + * unfortunately there's a +0.6K overall size increase due amount of extra logic involved + +### v2.13.2 + * added support for custom CSS properties as object keys. + +### v2.13.1 + * worked around [TypeScript transpilation bug with Template Literals](https://twitter.com/WebReflection/status/1038115439539363840). + +### v2.13 + * added the ability to define custom attributes via `hyperHTML.define("hyper-attribute", callback)`, so that `
` would invoke `callback(target, anyValue)` where `p` would be the target.
+
+### v2.12
+ * added `hyper.Component#dispatch(type, detail)` method to simplify events dispatching between lightweight components, bubbling a cancelable Custom Event with a `.component` property that points at the dispatcher, while the `event.currentTarget` will be the first node found within the component render.
+
+### v2.11
+ * updated [domdiff](https://github.com/WebReflection/domdiff#domdiff) to v1.0
+
+### v2.10.12
+ * patched missing `.children` in SVG node in IE / Edge https://github.com/WebReflection/hyperHTML/issues/244
+
+### v2.10.10
+ * updated [domdiff](https://github.com/WebReflection/domdiff#domdiff) to solve issue #243 (breaking with some sorted list)
+
+### v2.10.5
+ * various fixes and changes after [changes applied to ECMAScript 2015](https://github.com/tc39/ecma262/pull/890)
+
+### v2.8.0
+ * updated [domdiff](https://github.com/WebReflection/domdiff#domdiff) engine to boost performance with segments and lists
+
+### v2.7.2
+ * fixed #218 which was a variant of #200
+
+### v2.7.0
+ * the `Component.for(obj)` is now created first time via `new Component(obj)` - #216
+
+### v2.6.0
+ * declarative hyper.Component via `Component.for(context, uid?)` - #202
+ * hyperHTML TypeScript information - #201
+
+### v2.5.12
+ * fixed #200: textarea/style with initial undefined value
+
+### v2.5.11
+ * fixed #198: connected/disconnected events for nested components
+
+### v2.5.10
+ * more rigid / explicit RegExp to avoid glitches with self-closing tags
+
+### v2.5.8
+ * improved `VOID_ELEMENTS` regular expression (aligned with the _viperHTML_ one)
+
+### v2.5.7
+ * fixed `no.js` patch when wrong count of args is passed
+
+### v2.5.6
+ * added `no.js` file for environments without the ability to use modern JS or based on other languages such Dart.
+
+### v2.5.5
+ * build runs on macOS too
+ * added umd.js file
+
+### v2.5.2
+ * fixed weird SVG case (see #172)
+
+### v2.5.1
+ * improved self-closing reliability recycling and sharing attributes RegExp
+
+### v2.5.0
+ * updated `domdiff` library to the latest version
+ * implemented self-closing tags (and after various tests)
+
+### v2.4.3
+ * ensure attributes values are updated when different from previous one
+ * avoid the usage of the word `global` in the whole code
+
+### v2.4.2
+ * fix scripts with actual content too.
+
+### v2.4.1
+ * fix a bug with scripts that don't trigger network requests in both Firefox and Safari (see bug #152)
+
+### v2.4.0
+ * created a `Wire` class to handle via `domdiff` multiple wired nodes.
+ * brought back multi nodes per wire, a feature lost since **v2.0**
+ * simplified `Component` handling too, making it compatible again with multi wired content.
+ * fixed some check to make IE9+ tests green again
+
+### v2.3.0
+ * dropped the `engine` already. Too complex, no real benefits, refactored the whole internal logic to use [domdiff](https://github.com/WebReflection/domdiff) instead. Deprecated [hyperhtml-majinbuu](https://github.com/WebReflection/hyperhtml-majinbuu) and solved diffing "_forever_".
+
+### v2.2.0
+ * the whole `hyperHTML.engine` has been refactored to use [dom-splicer](https://github.com/WebReflection/dom-splicer) as an effort to make engine development easier
+
+### v2.1.3
+ * the MutationObserver is installed only once and only if there are components that have _on(dis)?connect_ handlers.
+
+### v2.1.2
+ * using a new folders convention with `esm/index.js` as main module and `cjs/index.js` as transformed artifact. This plays very well with bundlers when you `import {hyper} from 'hyperhtml/esm'` or `const {hyper} = require('hyperhtml/cjs');`
+
+### v2.1.1
+ * fast changes where prepending or appending same lists; now dropping upfront or removing at the end are part of the fast path too.
+
+### v2.1.0
+
+ * created a simple default merge engine focused on performance
+ * remove majinbuu as core dependency, created [hyperhtml-majinbuu](https://github.com/WebReflection/hyperhtml-majinbuu) project to swap it back via `hyperHTML.engine = require('hyperhtml-majinbuu')` or as ESM
+ * reduced final bundle size down to 4.1K via brotli
+
+## v2.0.0
+
+Refactoring following ticket #140
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..fb3f291f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+# Contribute
+
+## Introduction
+
+First, thank you for considering contributing to hyperhtml! It's people like you that make the open source community such a great community! ๐
+
+We welcome any type of contribution, not only code. You can help with
+- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
+- **Marketing**: writing blog posts, howto's, printing stickers, ...
+- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
+- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
+- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/hyperhtml).
+
+## Your First Contribution
+
+Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
+
+## Submitting code
+
+Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
+
+## Code review process
+
+The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
+It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
+
+## Financial contributions
+
+We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/hyperhtml).
+Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
+
+## Questions
+
+If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
+You can also reach us at hello@hyperhtml.opencollective.com.
+
+## Credits
+
+### Contributors
+
+Thank you to all the people who have already contributed to hyperhtml!
+
+
+
+### Backers
+
+Thank you to all our backers! [[Become a backer](https://opencollective.com/hyperhtml#backer)]
+
+
+
+
+### Sponsors
+
+Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/hyperhtml#sponsor))
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HELPERS.md b/HELPERS.md
new file mode 100644
index 00000000..d0f8aefb
--- /dev/null
+++ b/HELPERS.md
@@ -0,0 +1,57 @@
+## [babel-plugin-remove-ungap]
+
+Remove the [@ungap ponyfill modules] from your bundle. This will decrease the size of
+your bundle if you are targeting modern browsers only or if your build already includes
+other polyfills. This has been tested with [hyperHTML] and [lighterhtml] bundles.
+
+
+## [babel-plugin-template-html-minifier]
+
+Run [html-minifier] on hyperHTML templates.
+
+
+## [babel-plugin-bare-import-rewrite]
+
+This can be used as an alternative to [rollup-plugin-node-resolve], or can be used with certain node.js
+web servers to allow browsing live from source.
+
+Known web server integrations:
+* [fastify-babel] plugin for [fastify] enables running any babel plugins, generally expects `payload.filename` as set by [fastify-static]
+* [express-transform-bare-module-specifiers] for [express] servers
+
+
+## [vinyl-rollup]
+
+This module copies the output of rollup builds to a stream of vinyl-fs objects for [gulp].
+In addition it optionally adds files from modules that were bundled into the stream. This
+makes it easy to ensure that LICENSE and package.json files associated with bundled modules
+are published on the web server without publishing node.js server-side dependencies to the web.
+This can also be used to copy complete modules if required for licensing or if bundled code
+requires additional assets that are not part of the bundled JS (images for example).
+
+
+## [babel-plugin-bundled-import-meta]
+
+If `node_modules/some-web-component/index.js` uses `import.meta.url` to calculate the actual
+path to `node_modules/some-web-components/image.png`, rollup does not compensate. This babel
+plugin rewrites references to `import.meta.url` so it points to the original location where
+it is expected that the additional assets (images and such) can be found. This plugin works
+well with `vinyl-rollup` with `copyModules: true`.
+
+
+[babel-plugin-remove-ungap]: https://github.com/cfware/babel-plugin-remove-ungap#readme
+[@ungap ponyfill modules]: https://github.com/ungap/ungap.github.io#readme
+[hyperHTML]: https://github.com/WebReflection/hyperHTML#readme
+[lighterhtml]: https://github.com/WebReflection/lighterhtml#readme
+[babel-plugin-template-html-minifier]: https://github.com/cfware/babel-plugin-template-html-minifier#readme
+[html-minifier]: https://github.com/kangax/html-minifier#readme
+[babel-plugin-bare-import-rewrite]: https://github.com/cfware/babel-plugin-bare-import-rewrite#readme
+[rollup-plugin-node-resolve]: https://github.com/rollup/rollup-plugin-node-resolve#readme
+[fastify]: https://github.com/fastify/fastify#readme
+[fastify-babel]: https://github.com/cfware/fastify-babel#readme
+[fastify-static]: https://github.com/fastify/fastify-static#readme
+[express-transform-bare-module-specifiers]: https://github.com/nodecg/express-transform-bare-module-specifiers#readme
+[express]: https://github.com/expressjs/express#readme
+[vinyl-rollup]: https://github.com/cfware/vinyl-rollup#readme
+[gulp]: https://github.com/gulpjs/gulp#readme
+[babel-plugin-bundled-import-meta]: https://github.com/cfware/babel-plugin-bundled-import-meta#readme
diff --git a/LICENSE.txt b/LICENSE.txt
index deddd54d..9b3119f5 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,19 +1,15 @@
-Copyright (C) 2017 by Andrea Giammarchi - @WebReflection
+ISC License
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Copyright (c) 2017, Andrea Giammarchi, @WebReflection
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
index 831b5323..72f7e51a 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,125 @@
-# hyperHTML [](https://travis-ci.org/WebReflection/hyperHTML)
+# hyper(HTML)
+
+### Maintainance Only
+
+This module is great, works great, and served me greatly, but there's a pletora of modern, faster, more capable alternatives me, among many other OSS developers, offer so that if obvious bugs are proven to exist, these will be fixed, but there won't be a major release and I won't remove legacy support for stuff that, as previously mentioned, works just fine and it's battle-tested from IE to the latest Chrome.
+
+Removing that legacy support brings pretty much nothing in terms of size too: this module is already smaller than 90% of alternatives out there, dropping 0.xK so that there's less code that, behind feature detection, is not even used in modern browsers, won't benefit anyone.
+
+Thansk for your understanding and for not opening PRs which goal is to drop a check for legacy browsers ... these won't likely be merged ever as that'd be a major release update and I don't think anyone is interested in that.
+
+### ๐ฃ Community Announcement
+
+Please ask questions in the [dedicated discussions repository](https://github.com/WebReflection/discussions), to help the community around this project grow โฅ
+
+- - -
+
+
+
+A **Fast & Light Virtual DOM Alternative**.
+
+- - -
+
+[](https://github.com/WebReflection/donate) [](#backers) [](#sponsors) 
+
+[](https://coveralls.io/github/WebReflection/hyperHTML?branch=master)
+[](https://travis-ci.org/WebReflection/hyperHTML)
+[](https://opensource.org/licenses/ISC)
+[](https://greenkeeper.io/) 
+
+- - -
+
+Following an overview of projects related, or inspired by, _hyperHTML_. For a deep comparison of current libraries, feel free to [check this gist out](https://gist.github.com/WebReflection/761052d6dae7c8207d2fcba7cdede295).
+
+
+## ยตhtml
+
+The latest, smallest, iteration of all best concept from this library since 2017, have been packaged in _~2.5K_. If it's extreme minimalism and great _DX_ that you are after, check [uhtml](https://github.com/WebReflection/uhtml#readme) out.
+
+
+## hypersimple
+
+If you've just started with template literals based projects and you like components, or you'd like to understand what's _hyperHTML_ capable of, give [hypersimple](https://github.com/WebReflection/hypersimple#readme) a try ๐
+
+
+## lighterhtml ๐ก
+
+This little brother is "_showing off_" these days, claiming better performance and unprecedented ease of use.
+
+[GitHub Repository](https://github.com/WebReflection/lighterhtml)
+
+
+## Neverland ๐๐ฆ
+
+If you like React hooks concept, don't miss this little wrap that adds 0._something_ overhead to the already lightweight hyperHTML, bringing in very similar concepts.
+
+[Blog Post](https://medium.com/@WebReflection/neverland-the-hyperhtmls-hook-a0c3e11324bb)
+
+[GitHub Repository](https://github.com/WebReflection/neverland)
+
+## Haunted ๐ฆ ๐
+
+If you also like React hooks mechanism and you'd like to combine these via hyperHTML or [HyperHTMLElement](https://github.com/WebReflection/hyperHTML-Element), try [haunted](https://github.com/matthewp/haunted#haunted--) out!
+
+
+## Bundlers
+
+You can require or import _hyperHTML_ with any bundler and in different ways.
+
+If requiring or importing from `"hyperhtml"` doesn't work, try requiring from `"hyperhtml/cjs"` for CommonJS friendly bundlers (WebPack), or `"hyperhtml/esm"` for ESM compatible bundlers (Rollup).
+
+See [HELPERS.md](./HELPERS.md) for a list of additional tools which can be helpful for building hyperHTML based web applications.
-A Fast & Light Virtual DOM Alternative
- - -
+## Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/hyperhtml#sponsor)]
+
+
+
+## Backers
+
+Thank you to all our backers! ๐ [[Become a backer](https://opencollective.com/hyperhtml#backer)]
+
+
+
+## Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+
+
+- - -
+
+### 2.34 Highlights
+
+ * the new `?boolean=${value}` syntax from [ยตhtml](https://github.com/WebReflection/uhtml#readme) has landed in *hyperHTML* too. Feel free to [read this long discussion](https://github.com/WebReflection/discussions/discussions/13) to better understand *why* this syntax is necessary.
+
+### V2.5 Highlights
+
+ * `
This is: ${'text'}
`;``` for text, and ```render`${'html' || node || array}
`;``` for other cases. - An array wil result into html, if its content has strings, or a document fragment, if it contains nodes. - I've thought a pinch of extra handy magic would've been nice there ๐. +## HTML Syntax Highlight - * _can I use different renders for a single node?_ - Sure thing. However, the best performance gain is reached with nodes that always use the same template string. - If you have a very unpredictable conditional template, you might want to create two different nodes and apply `hyperHTML` with the same template for both of them, swapping them when necessary. - In every other case, the new template will create new content and map it once per change. +If you are using Visual Studio Code you can install `literally-html` to highlight all literals handled by `hyperHTML` and others. - * _is this project just the same as [yo-yo](https://github.com/maxogden/yo-yo) or [bel](https://github.com/shama/bel) ?_ - First of all, I didn't even know those projects were existing when I've written `hyperHTML`, and while the goal is quiet similar, the implementation is very different. - For instance, `hyperHTML` performance seems to be superior than [yo-yo-perf](https://github.com/shama/yo-yo-perf). - You can directly test [hyperHTML DBMonster](https://webreflection.github.io/hyperHTML/test/dbmonster.html) benchmark and see it goes _N_ times faster than `yo-yo` version on both Desktop and Mobile browsers ๐. + +## Prettier Templates -### Caveats +If you'd like to make your templates prettier than usual, don't miss this plugin: https://github.com/sgtpep/prettier-plugin-html-template-literals -Following a list of `hyperHTML` caveats (so far just one). +## Questions ? -#### Quotes are mandatory for dynamic attributes -To achieve best performance at setup time, a special `` is used the first time as template values. +Please ask anything you'd like to know in [StackOverflow](https://stackoverflow.com) using the tag [`hyperhtml`](https://stackoverflow.com/questions/tagged/hyperhtml) so that others can benefit from answers and examples. -This makes it possible to quickly walk through the DOM tree and setup behaviors, but it's also the value looked for within attributes. +#### hyper or lit ? -Unfortunately, if you have html such `${{user}}
`; + define: (intent, callback) => { + if (intent.indexOf('-') < 0) { + if (!(intent in intents)) { + length = keys.push(intent); + } + intents[intent] = callback; + } else { + attributes[intent] = callback; + } + }, + + // this method is used internally as last resort + // to retrieve a value out of an object + invoke: (object, callback) => { + for (let i = 0; i < length; i++) { + let key = keys[i]; + if (hasOwnProperty.call(object, key)) { + return intents[key](object[key], callback); + } + } + } +}; diff --git a/cjs/objects/Updates.js b/cjs/objects/Updates.js new file mode 100644 index 00000000..0df2e324 --- /dev/null +++ b/cjs/objects/Updates.js @@ -0,0 +1,377 @@ +'use strict'; +const CustomEvent = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/custom-event')); +const WeakSet = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/essential-weakset')); +const isArray = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/is-array')); +const createContent = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/create-content')); + +const disconnected = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('disconnected')); +const domdiff = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('domdiff')); +const domtagger = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('domtagger')); +const hyperStyle = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('hyperhtml-style')); +const Wire = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('hyperhtml-wire')); + +const { + CONNECTED, DISCONNECTED, DOCUMENT_FRAGMENT_NODE, OWNER_SVG_ELEMENT +} = require('../shared/constants.js'); + +const Component = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('../classes/Component.js')); +const Intent = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('./Intent.js')); + +const componentType = Component.prototype.nodeType; +const wireType = Wire.prototype.nodeType; + +const observe = disconnected({Event: CustomEvent, WeakSet}); + +exports.Tagger = Tagger; +exports.observe = observe; + +// returns an intent to explicitly inject content as html +const asHTML = html => ({html}); + +// returns nodes from wires and components +const asNode = (item, i) => { + switch (item.nodeType) { + case wireType: + // in the Wire case, the content can be + // removed, post-pended, inserted, or pre-pended and + // all these cases are handled by domdiff already + /* istanbul ignore next */ + return (1 / i) < 0 ? + (i ? item.remove(true) : item.lastChild) : + (i ? item.valueOf(true) : item.firstChild); + case componentType: + return asNode(item.render(), i); + default: + return item; + } +} + +// returns true if domdiff can handle the value +const canDiff = value => 'ELEMENT_NODE' in value; + +// borrowed from uhandlers +// https://github.com/WebReflection/uhandlers +const booleanSetter = (node, key, oldValue) => newValue => { + if (oldValue !== !!newValue) { + if ((oldValue = !!newValue)) + node.setAttribute(key, ''); + else + node.removeAttribute(key); + } +}; + +const hyperSetter = (node, name, svg) => svg ? + value => { + try { + node[name] = value; + } + catch (nope) { + node.setAttribute(name, value); + } + } : + value => { + node[name] = value; + }; + +// when a Promise is used as interpolation value +// its result must be parsed once resolved. +// This callback is in charge of understanding what to do +// with a returned value once the promise is resolved. +const invokeAtDistance = (value, callback) => { + callback(value.placeholder); + if ('text' in value) { + Promise.resolve(value.text).then(String).then(callback); + } else if ('any' in value) { + Promise.resolve(value.any).then(callback); + } else if ('html' in value) { + Promise.resolve(value.html).then(asHTML).then(callback); + } else { + Promise.resolve(Intent.invoke(value, callback)).then(callback); + } +}; + +// quick and dirty way to check for Promise/ish values +const isPromise_ish = value => value != null && 'then' in value; + +// list of attributes that should not be directly assigned +const readOnly = /^(?:form|list)$/i; + +// reused every slice time +const slice = [].slice; + +// simplifies text node creation +const text = (node, text) => node.ownerDocument.createTextNode(text); + +function Tagger(type) { + this.type = type; + return domtagger(this); +} + +Tagger.prototype = { + + // there are four kind of attributes, and related behavior: + // * events, with a name starting with `on`, to add/remove event listeners + // * special, with a name present in their inherited prototype, accessed directly + // * regular, accessed through get/setAttribute standard DOM methods + // * style, the only regular attribute that also accepts an object as value + // so that you can style=${{width: 120}}. In this case, the behavior has been + // fully inspired by Preact library and its simplicity. + attribute(node, name, original) { + const isSVG = OWNER_SVG_ELEMENT in node; + let oldValue; + // if the attribute is the style one + // handle it differently from others + if (name === 'style') + return hyperStyle(node, original, isSVG); + // direct accessors for and friends + else if (name.slice(0, 1) === '.') + return hyperSetter(node, name.slice(1), isSVG); + // boolean accessors for and friends + else if (name.slice(0, 1) === '?') + return booleanSetter(node, name.slice(1)); + // the name is an event one, + // add/remove event listeners accordingly + else if (/^on/.test(name)) { + let type = name.slice(2); + if (type === CONNECTED || type === DISCONNECTED) { + observe(node); + } + else if (name.toLowerCase() + in node) { + type = type.toLowerCase(); + } + return newValue => { + if (oldValue !== newValue) { + if (oldValue) + node.removeEventListener(type, oldValue, false); + oldValue = newValue; + if (newValue) + node.addEventListener(type, newValue, false); + } + }; + } + // the attribute is special ('value' in input) + // and it's not SVG *or* the name is exactly data, + // in this case assign the value directly + else if ( + name === 'data' || + (!isSVG && name in node && !readOnly.test(name)) + ) { + return newValue => { + if (oldValue !== newValue) { + oldValue = newValue; + if (node[name] !== newValue && newValue == null) { + // cleanup on null to avoid silly IE/Edge bug + node[name] = ''; + node.removeAttribute(name); + } + else + node[name] = newValue; + } + }; + } + else if (name in Intent.attributes) { + oldValue; + return any => { + const newValue = Intent.attributes[name](node, any); + if (oldValue !== newValue) { + oldValue = newValue; + if (newValue == null) + node.removeAttribute(name); + else + node.setAttribute(name, newValue); + } + }; + } + // in every other case, use the attribute node as it is + // update only the value, set it as node only when/if needed + else { + let owner = false; + const attribute = original.cloneNode(true); + return newValue => { + if (oldValue !== newValue) { + oldValue = newValue; + if (attribute.value !== newValue) { + if (newValue == null) { + if (owner) { + owner = false; + node.removeAttributeNode(attribute); + } + attribute.value = newValue; + } else { + attribute.value = newValue; + if (!owner) { + owner = true; + node.setAttributeNode(attribute); + } + } + } + } + }; + } + }, + + // in a hyper(node)`${{user}}
`; + define: (intent, callback) => { + if (intent.indexOf('-') < 0) { + if (!(intent in intents)) { + length = keys.push(intent); + } + intents[intent] = callback; + } else { + attributes[intent] = callback; + } + }, + + // this method is used internally as last resort + // to retrieve a value out of an object + invoke: (object, callback) => { + for (let i = 0; i < length; i++) { + let key = keys[i]; + if (hasOwnProperty.call(object, key)) { + return intents[key](object[key], callback); + } + } + } +}; diff --git a/esm/objects/Updates.js b/esm/objects/Updates.js new file mode 100644 index 00000000..103b45ff --- /dev/null +++ b/esm/objects/Updates.js @@ -0,0 +1,377 @@ +import CustomEvent from '@ungap/custom-event'; +import WeakSet from '@ungap/essential-weakset'; +import isArray from '@ungap/is-array'; +import createContent from '@ungap/create-content'; + +import disconnected from 'disconnected'; +import domdiff from 'domdiff'; +import domtagger from 'domtagger'; +import hyperStyle from 'hyperhtml-style'; +import Wire from 'hyperhtml-wire'; + +import { + CONNECTED, DISCONNECTED, + DOCUMENT_FRAGMENT_NODE, + OWNER_SVG_ELEMENT +} from '../shared/constants.js'; + +import Component from '../classes/Component.js'; +import Intent from './Intent.js'; + +const componentType = Component.prototype.nodeType; +const wireType = Wire.prototype.nodeType; + +const observe = disconnected({Event: CustomEvent, WeakSet}); + +export {Tagger, observe}; + +// returns an intent to explicitly inject content as html +const asHTML = html => ({html}); + +// returns nodes from wires and components +const asNode = (item, i) => { + switch (item.nodeType) { + case wireType: + // in the Wire case, the content can be + // removed, post-pended, inserted, or pre-pended and + // all these cases are handled by domdiff already + /* istanbul ignore next */ + return (1 / i) < 0 ? + (i ? item.remove(true) : item.lastChild) : + (i ? item.valueOf(true) : item.firstChild); + case componentType: + return asNode(item.render(), i); + default: + return item; + } +} + +// returns true if domdiff can handle the value +const canDiff = value => 'ELEMENT_NODE' in value; + +// borrowed from uhandlers +// https://github.com/WebReflection/uhandlers +const booleanSetter = (node, key, oldValue) => newValue => { + if (oldValue !== !!newValue) { + if ((oldValue = !!newValue)) + node.setAttribute(key, ''); + else + node.removeAttribute(key); + } +}; + +const hyperSetter = (node, name, svg) => svg ? + value => { + try { + node[name] = value; + } + catch (nope) { + node.setAttribute(name, value); + } + } : + value => { + node[name] = value; + }; + +// when a Promise is used as interpolation value +// its result must be parsed once resolved. +// This callback is in charge of understanding what to do +// with a returned value once the promise is resolved. +const invokeAtDistance = (value, callback) => { + callback(value.placeholder); + if ('text' in value) { + Promise.resolve(value.text).then(String).then(callback); + } else if ('any' in value) { + Promise.resolve(value.any).then(callback); + } else if ('html' in value) { + Promise.resolve(value.html).then(asHTML).then(callback); + } else { + Promise.resolve(Intent.invoke(value, callback)).then(callback); + } +}; + +// quick and dirty way to check for Promise/ish values +const isPromise_ish = value => value != null && 'then' in value; + +// list of attributes that should not be directly assigned +const readOnly = /^(?:form|list)$/i; + +// reused every slice time +const slice = [].slice; + +// simplifies text node creation +const text = (node, text) => node.ownerDocument.createTextNode(text); + +function Tagger(type) { + this.type = type; + return domtagger(this); +} + +Tagger.prototype = { + + // there are four kind of attributes, and related behavior: + // * events, with a name starting with `on`, to add/remove event listeners + // * special, with a name present in their inherited prototype, accessed directly + // * regular, accessed through get/setAttribute standard DOM methods + // * style, the only regular attribute that also accepts an object as value + // so that you can style=${{width: 120}}. In this case, the behavior has been + // fully inspired by Preact library and its simplicity. + attribute(node, name, original) { + const isSVG = OWNER_SVG_ELEMENT in node; + let oldValue; + // if the attribute is the style one + // handle it differently from others + if (name === 'style') + return hyperStyle(node, original, isSVG); + // direct accessors for and friends + else if (name.slice(0, 1) === '.') + return hyperSetter(node, name.slice(1), isSVG); + // boolean accessors for and friends + else if (name.slice(0, 1) === '?') + return booleanSetter(node, name.slice(1)); + // the name is an event one, + // add/remove event listeners accordingly + else if (/^on/.test(name)) { + let type = name.slice(2); + if (type === CONNECTED || type === DISCONNECTED) { + observe(node); + } + else if (name.toLowerCase() + in node) { + type = type.toLowerCase(); + } + return newValue => { + if (oldValue !== newValue) { + if (oldValue) + node.removeEventListener(type, oldValue, false); + oldValue = newValue; + if (newValue) + node.addEventListener(type, newValue, false); + } + }; + } + // the attribute is special ('value' in input) + // and it's not SVG *or* the name is exactly data, + // in this case assign the value directly + else if ( + name === 'data' || + (!isSVG && name in node && !readOnly.test(name)) + ) { + return newValue => { + if (oldValue !== newValue) { + oldValue = newValue; + if (node[name] !== newValue && newValue == null) { + // cleanup on null to avoid silly IE/Edge bug + node[name] = ''; + node.removeAttribute(name); + } + else + node[name] = newValue; + } + }; + } + else if (name in Intent.attributes) { + oldValue; + return any => { + const newValue = Intent.attributes[name](node, any); + if (oldValue !== newValue) { + oldValue = newValue; + if (newValue == null) + node.removeAttribute(name); + else + node.setAttribute(name, newValue); + } + }; + } + // in every other case, use the attribute node as it is + // update only the value, set it as node only when/if needed + else { + let owner = false; + const attribute = original.cloneNode(true); + return newValue => { + if (oldValue !== newValue) { + oldValue = newValue; + if (attribute.value !== newValue) { + if (newValue == null) { + if (owner) { + owner = false; + node.removeAttributeNode(attribute); + } + attribute.value = newValue; + } else { + attribute.value = newValue; + if (!owner) { + owner = true; + node.setAttributeNode(attribute); + } + } + } + } + }; + } + }, + + // in a hyper(node)`${{user}}
`; + define: function define(intent, callback) { + if (intent.indexOf('-') < 0) { + if (!(intent in intents)) { + length = keys.push(intent); + } + + intents[intent] = callback; + } else { + attributes[intent] = callback; + } + }, + // this method is used internally as last resort + // to retrieve a value out of an object + invoke: function invoke(object, callback) { + for (var i = 0; i < length; i++) { + var key = keys[i]; + + if (hasOwnProperty.call(object, key)) { + return intents[key](object[key], callback); + } + } + } + }; + + var isArray = Array.isArray || + /* istanbul ignore next */ + function (toString) { + /* istanbul ignore next */ + var $ = toString.call([]); + /* istanbul ignore next */ + + return function isArray(object) { + return toString.call(object) === $; + }; + }({}.toString); + + /*! (c) Andrea Giammarchi - ISC */ + var createContent = function (document) { + + var FRAGMENT = 'fragment'; + var TEMPLATE = 'template'; + var HAS_CONTENT = ('content' in create(TEMPLATE)); + var createHTML = HAS_CONTENT ? function (html) { + var template = create(TEMPLATE); + template.innerHTML = html; + return template.content; + } : function (html) { + var content = create(FRAGMENT); + var template = create(TEMPLATE); + var childNodes = null; + + if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { + var selector = RegExp.$1; + template.innerHTML = '