chore(refactor): format code with prettier

pull/948/head
Jan van Brügge 4 years ago
parent 8d0a666182
commit 37f58835fa
No known key found for this signature in database
GPG Key ID: 88E0BF7B7A546481

@ -1,47 +1,55 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint'
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint'
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint",
],
env: {
browser: true,
node: false,
},
overrides: [{
files: ['*.ts', '*.tsx'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', 'test/tsconfig.json']
overrides: [
{
files: ["*.ts", "*.tsx"],
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json", "test/tsconfig.json"],
},
extends: [
"plugin:@typescript-eslint/recommended-requiring-type-checking",
],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
},
},
{
files: ["test/**/*.ts"],
env: {
browser: true,
node: true,
},
},
extends: ['plugin:@typescript-eslint/recommended-requiring-type-checking'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-non-null-assertion': 'off'
}
}, {
files: ['test/**/*.ts'],
env: {
browser: true,
node: true
}
}, {
files: ['*.js'],
excludedFiles: ['examples/**'],
extends: ['plugin:node/recommended'],
env: {
node: true,
browser: false
}
}],
{
files: ["*.js"],
excludedFiles: ["examples/**"],
extends: ["plugin:node/recommended"],
env: {
node: true,
browser: false,
},
rules: {
"@typescript-eslint/no-var-requires": "off",
},
},
],
};

@ -5,7 +5,7 @@
"javascriptreact",
"markdown",
"typescript",
"typescriptreact",
"typescriptreact"
],
"eslint.options.ignorePath": ".gitignore"
}

@ -4,116 +4,109 @@ All notable changes to this project will be documented in this file. See [standa
## [2.1.0](https://github.com/snabbdom/snabbdom/compare/v2.0.0...v2.1.0) (2020-09-14)
### Features
* **eventlisteners:** add types for VNode in listener ([63b1b6c](https://github.com/snabbdom/snabbdom/commit/63b1b6c22e49d06b1fb509a14d321ec19f324bb5)), closes [#796](https://github.com/snabbdom/snabbdom/issues/796)
* **eventlisteners:** relax custom event listener type ([15ce059](https://github.com/snabbdom/snabbdom/commit/15ce059e2b5e80d1975168fff2d2a44f71bd5cbb)), closes [#850](https://github.com/snabbdom/snabbdom/issues/850)
- **eventlisteners:** add types for VNode in listener ([63b1b6c](https://github.com/snabbdom/snabbdom/commit/63b1b6c22e49d06b1fb509a14d321ec19f324bb5)), closes [#796](https://github.com/snabbdom/snabbdom/issues/796)
- **eventlisteners:** relax custom event listener type ([15ce059](https://github.com/snabbdom/snabbdom/commit/15ce059e2b5e80d1975168fff2d2a44f71bd5cbb)), closes [#850](https://github.com/snabbdom/snabbdom/issues/850)
## [2.0.0](https://github.com/snabbdom/snabbdom/compare/v1.0.1...v2.0.0) (2020-09-10)
### ⚠ BREAKING CHANGES
* **eventlisteners:** loaded/carrying event listeners are no longer supported.
- **eventlisteners:** loaded/carrying event listeners are no longer supported.
### Features
* **eventlisteners:** add missing mult. listeners type ([5a89efe](https://github.com/snabbdom/snabbdom/commit/5a89efe01580d50f15649c19a444745867c5c0d4)), closes [#794](https://github.com/snabbdom/snabbdom/issues/794)
* **eventlisteners:** remove loaded listeners feature ([6e0ff8e](https://github.com/snabbdom/snabbdom/commit/6e0ff8e8141c70891e55e41a3107d6d4de0bc754)), closes [#802](https://github.com/snabbdom/snabbdom/issues/802) [#802](https://github.com/snabbdom/snabbdom/issues/802)
- **eventlisteners:** add missing mult. listeners type ([5a89efe](https://github.com/snabbdom/snabbdom/commit/5a89efe01580d50f15649c19a444745867c5c0d4)), closes [#794](https://github.com/snabbdom/snabbdom/issues/794)
- **eventlisteners:** remove loaded listeners feature ([6e0ff8e](https://github.com/snabbdom/snabbdom/commit/6e0ff8e8141c70891e55e41a3107d6d4de0bc754)), closes [#802](https://github.com/snabbdom/snabbdom/issues/802) [#802](https://github.com/snabbdom/snabbdom/issues/802)
### Bug Fixes
* **deps:** add regenertor-runtime to devDeps ([2a2964c](https://github.com/snabbdom/snabbdom/commit/2a2964c3eb47cd2f5a7ae88f49b2afe9ea299d7e)), closes [#813](https://github.com/snabbdom/snabbdom/issues/813)
* **docs:** gitter badge url ([7e19849](https://github.com/snabbdom/snabbdom/commit/7e198493c11f6d4afa8b03d727083d661e85ec0e))
* **examples:** example import paths ([8111f62](https://github.com/snabbdom/snabbdom/commit/8111f6234a70840673412da6cd37a726a7c839f8)), closes [#761](https://github.com/snabbdom/snabbdom/issues/761)
* **examples:** totalHeight 0 on remove last element reorder animation ([afa77c0](https://github.com/snabbdom/snabbdom/commit/afa77c04d4ab959a5f2bb5853e5dd821c744843f))
* **package:** remove directories field ([c7a2a93](https://github.com/snabbdom/snabbdom/commit/c7a2a93f5a2ed63bd76130e5e3d3769a9f1c1c58))
* **package:** update urls paldepind -> snabbdom ([f94185a](https://github.com/snabbdom/snabbdom/commit/f94185a5bbb31018af48b77449e74f58339fe404)), closes [#775](https://github.com/snabbdom/snabbdom/issues/775)
- **deps:** add regenertor-runtime to devDeps ([2a2964c](https://github.com/snabbdom/snabbdom/commit/2a2964c3eb47cd2f5a7ae88f49b2afe9ea299d7e)), closes [#813](https://github.com/snabbdom/snabbdom/issues/813)
- **docs:** gitter badge url ([7e19849](https://github.com/snabbdom/snabbdom/commit/7e198493c11f6d4afa8b03d727083d661e85ec0e))
- **examples:** example import paths ([8111f62](https://github.com/snabbdom/snabbdom/commit/8111f6234a70840673412da6cd37a726a7c839f8)), closes [#761](https://github.com/snabbdom/snabbdom/issues/761)
- **examples:** totalHeight 0 on remove last element reorder animation ([afa77c0](https://github.com/snabbdom/snabbdom/commit/afa77c04d4ab959a5f2bb5853e5dd821c744843f))
- **package:** remove directories field ([c7a2a93](https://github.com/snabbdom/snabbdom/commit/c7a2a93f5a2ed63bd76130e5e3d3769a9f1c1c58))
- **package:** update urls paldepind -> snabbdom ([f94185a](https://github.com/snabbdom/snabbdom/commit/f94185a5bbb31018af48b77449e74f58339fe404)), closes [#775](https://github.com/snabbdom/snabbdom/issues/775)
### [1.0.1](https://github.com/paldepind/snabbdom/compare/v1.0.0...v1.0.1) (2020-06-18)
### User facing changes
* **package:** fix ./snabbdom related files and exports fields errors ([89b917b](https://github.com/paldepind/snabbdom/commit/89b917bb3f3f8986390e3e400327a9087533d928))
- **package:** fix ./snabbdom related files and exports fields errors ([89b917b](https://github.com/paldepind/snabbdom/commit/89b917bb3f3f8986390e3e400327a9087533d928))
## [1.0.0](https://github.com/paldepind/snabbdom/compare/v0.7.4...v1.0.0) (2020-06-18)
### ⚠ BREAKING CHANGES
* **exports:** The main export path, 'snabbdom' was replaced with
the export path 'snabbdom/init'. This new export path includes only
the named export `init`.
* **exports:** No default exports exist. All exports are named.
* **exports:** the import path `snabbdom/snabbdom.bundle` is removed.
* **typescript:** Types exported by this package have re-declared
the global `Element.setAttribute` and `Element.setAttributeNS` to
accept `number` and `boolean` for the `value` parameter. This
change removes that re-declaration and thus the only valid value is
`string`. If your code provides `number` and/or `boolean`, then it
may now fail to compile.
* **props:** props module does not attempt to delete node
properties. This may affect you if you are using the props module
to add non-native (custom) properties to DOM nodes. Instead, it is
recommended to use _data-* attributes_.
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
* CommonJS modules are no longer provided.
* import paths in ES modules include file name
extensions.
* Compiled to ES2015 (was ES5).
* UMD bundles are no longer provided.
- **exports:** The main export path, 'snabbdom' was replaced with
the export path 'snabbdom/init'. This new export path includes only
the named export `init`.
- **exports:** No default exports exist. All exports are named.
- **exports:** the import path `snabbdom/snabbdom.bundle` is removed.
- **typescript:** Types exported by this package have re-declared
the global `Element.setAttribute` and `Element.setAttributeNS` to
accept `number` and `boolean` for the `value` parameter. This
change removes that re-declaration and thus the only valid value is
`string`. If your code provides `number` and/or `boolean`, then it
may now fail to compile.
- **props:** props module does not attempt to delete node
properties. This may affect you if you are using the props module
to add non-native (custom) properties to DOM nodes. Instead, it is
recommended to use _data-\* attributes_.
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
- CommonJS modules are no longer provided.
- import paths in ES modules include file name
extensions.
- Compiled to ES2015 (was ES5).
- UMD bundles are no longer provided.
### Internal changes
* **commitlint:** add type auto and scope deps for renovate ([b56a0ac](https://github.com/paldepind/snabbdom/commit/b56a0ac796a3c27644f8332278a7cbb9d24a95c6))
* **commitlint:** fix and enable in CI ([f8cf5cc](https://github.com/paldepind/snabbdom/commit/f8cf5ccba402cbbf6982da681db8707fd12fc8d4)), closes [#662](https://github.com/paldepind/snabbdom/issues/662)
* **deps:** update dependency @typescript-eslint/eslint-plugin to v3.3.0 ([9448e42](https://github.com/paldepind/snabbdom/commit/9448e4267cf077890deb0deb32c3200e4d19a213))
* **deps:** update dependency tsconfigs to v5 ([eb1ec8c](https://github.com/paldepind/snabbdom/commit/eb1ec8c280544a322fc9844c255b8ec5c8e004c6))
* **deps:** update dependency typescript to v3.9.5 ([5e24b20](https://github.com/paldepind/snabbdom/commit/5e24b20a52a8c20ed82e3fc7d20977262016731a))
* **docs:** lint code examples ([41cb359](https://github.com/paldepind/snabbdom/commit/41cb3596e8898399545f02ff8205a1d45f62f391))
* **eslint:** lint cjs files ([d581217](https://github.com/paldepind/snabbdom/commit/d58121755f4e2da50ad82e52818b66333ae10a37))
* **format:** sort file lists ([e77615b](https://github.com/paldepind/snabbdom/commit/e77615b16bd60fbd1963528a2c46e7dfbbb77e0e)), closes [#673](https://github.com/paldepind/snabbdom/issues/673)
* **git:** ignore each test artifact specifically ([b34e9a9](https://github.com/paldepind/snabbdom/commit/b34e9a9d3a8096c2c5cd7eebeafba0e52ed08a75))
* **package:** consistent values in files field ([6fe56f8](https://github.com/paldepind/snabbdom/commit/6fe56f8538f6e0073d458d7a8e21c8d469c0a9df)), closes [#672](https://github.com/paldepind/snabbdom/issues/672)
* **relic:** remove @types/assert ([2846189](https://github.com/paldepind/snabbdom/commit/28461899bdce0c2134dca92298e40c1ecf7be363))
* **typescript:** package and tests are two projects ([8a71211](https://github.com/paldepind/snabbdom/commit/8a71211b4a38616c9d90bc9214e116d1b3e869b5))
* **vscode:** eslint.validate short forms ([ba3e85b](https://github.com/paldepind/snabbdom/commit/ba3e85bf90f77254fad08435d484e19f836c6783))
* **vscode:** use workspace typescript ([eabbd2f](https://github.com/paldepind/snabbdom/commit/eabbd2f056b40e5e9376ccf94c9a6f9177bd020a))
- **commitlint:** add type auto and scope deps for renovate ([b56a0ac](https://github.com/paldepind/snabbdom/commit/b56a0ac796a3c27644f8332278a7cbb9d24a95c6))
- **commitlint:** fix and enable in CI ([f8cf5cc](https://github.com/paldepind/snabbdom/commit/f8cf5ccba402cbbf6982da681db8707fd12fc8d4)), closes [#662](https://github.com/paldepind/snabbdom/issues/662)
- **deps:** update dependency @typescript-eslint/eslint-plugin to v3.3.0 ([9448e42](https://github.com/paldepind/snabbdom/commit/9448e4267cf077890deb0deb32c3200e4d19a213))
- **deps:** update dependency tsconfigs to v5 ([eb1ec8c](https://github.com/paldepind/snabbdom/commit/eb1ec8c280544a322fc9844c255b8ec5c8e004c6))
- **deps:** update dependency typescript to v3.9.5 ([5e24b20](https://github.com/paldepind/snabbdom/commit/5e24b20a52a8c20ed82e3fc7d20977262016731a))
- **docs:** lint code examples ([41cb359](https://github.com/paldepind/snabbdom/commit/41cb3596e8898399545f02ff8205a1d45f62f391))
- **eslint:** lint cjs files ([d581217](https://github.com/paldepind/snabbdom/commit/d58121755f4e2da50ad82e52818b66333ae10a37))
- **format:** sort file lists ([e77615b](https://github.com/paldepind/snabbdom/commit/e77615b16bd60fbd1963528a2c46e7dfbbb77e0e)), closes [#673](https://github.com/paldepind/snabbdom/issues/673)
- **git:** ignore each test artifact specifically ([b34e9a9](https://github.com/paldepind/snabbdom/commit/b34e9a9d3a8096c2c5cd7eebeafba0e52ed08a75))
- **package:** consistent values in files field ([6fe56f8](https://github.com/paldepind/snabbdom/commit/6fe56f8538f6e0073d458d7a8e21c8d469c0a9df)), closes [#672](https://github.com/paldepind/snabbdom/issues/672)
- **relic:** remove @types/assert ([2846189](https://github.com/paldepind/snabbdom/commit/28461899bdce0c2134dca92298e40c1ecf7be363))
- **typescript:** package and tests are two projects ([8a71211](https://github.com/paldepind/snabbdom/commit/8a71211b4a38616c9d90bc9214e116d1b3e869b5))
- **vscode:** eslint.validate short forms ([ba3e85b](https://github.com/paldepind/snabbdom/commit/ba3e85bf90f77254fad08435d484e19f836c6783))
- **vscode:** use workspace typescript ([eabbd2f](https://github.com/paldepind/snabbdom/commit/eabbd2f056b40e5e9376ccf94c9a6f9177bd020a))
### User facing changes
* **docs:** enable eslint rule array-bracket-spacing ([77e54e9](https://github.com/paldepind/snabbdom/commit/77e54e9105394d4e6d21647a38adfb80ea567ee2))
* **docs:** enable eslint rule import/first ([17cf7ae](https://github.com/paldepind/snabbdom/commit/17cf7ae931185d8ab1ac1e4f8b7042677e03db8d))
* **docs:** enable eslint rule import/newline-after-import ([cd3a5cf](https://github.com/paldepind/snabbdom/commit/cd3a5cf17ee33c738d17adcb78a8c254e96653b9))
* **docs:** enable eslint rule indent ([e2861bb](https://github.com/paldepind/snabbdom/commit/e2861bb1bd68c63c99c30907bb331c21f27cb248))
* **docs:** enable eslint rule key-spacing ([349b686](https://github.com/paldepind/snabbdom/commit/349b686bd8cfc24a2845fbab62081a15e5b42d0f))
* **docs:** enable eslint rule max-statements-per-line ([a128a23](https://github.com/paldepind/snabbdom/commit/a128a23ac3182677d10d1979213c92dcf04294ea))
* **docs:** enable eslint rule no-multi-spaces ([8179381](https://github.com/paldepind/snabbdom/commit/8179381a775acea73d0064c77d643944cf684947)), closes [#692](https://github.com/paldepind/snabbdom/issues/692)
* **docs:** enable eslint rule object-curly-spacing ([8b8fbd5](https://github.com/paldepind/snabbdom/commit/8b8fbd5e34fdc3a99ce67635cc44f5ef9e3cf30c))
* **docs:** enable eslint rule quote-props ([37512fe](https://github.com/paldepind/snabbdom/commit/37512fe8ee02cf374cf3d9a62c0f6e3203be2f28))
* **docs:** enable eslint rule quotes ([2d455b5](https://github.com/paldepind/snabbdom/commit/2d455b52dcabc2f8e52082536e635a66e98eb650))
* **docs:** enable eslint rule semi ([f4e7885](https://github.com/paldepind/snabbdom/commit/f4e7885663e645aeb728470d5e1f159365f94f1b))
* **docs:** enable eslint rule space-before-blocks ([9f2d2d7](https://github.com/paldepind/snabbdom/commit/9f2d2d7a1687c7169dff775724a811e1dd7ccd8b))
* **docs:** enable eslint rule space-before-function-paren ([23e7b87](https://github.com/paldepind/snabbdom/commit/23e7b87c64b5d587acae5ce7bd899ec0d071958e))
* **docs:** enable eslint rules object-*-newline ([9a45b5b](https://github.com/paldepind/snabbdom/commit/9a45b5b22aba0fbed97431e99268a173995de4a1))
* **docs:** fix wrong module import paths ([3b6baee](https://github.com/paldepind/snabbdom/commit/3b6baee049f44cbc55cc2b0131c2c551e9c1b452)), closes [#691](https://github.com/paldepind/snabbdom/issues/691)
* **docs:** provide a release changelog ([616df35](https://github.com/paldepind/snabbdom/commit/616df35909f1d639d562418ea32122625104b00c)), closes [#670](https://github.com/paldepind/snabbdom/issues/670)
* **exports:** main export provided ([3becd84](https://github.com/paldepind/snabbdom/commit/3becd84cc1dcfb84e2ee292eab18aae36e415040)), closes [#682](https://github.com/paldepind/snabbdom/issues/682)
* **exports:** only named exports ([fefd141](https://github.com/paldepind/snabbdom/commit/fefd141f5f3567bb6dccbd6c43ce81f285f985bd)), closes [#522](https://github.com/paldepind/snabbdom/issues/522) [#523](https://github.com/paldepind/snabbdom/issues/523)
* **exports:** relative values in exports field ([187088e](https://github.com/paldepind/snabbdom/commit/187088ee0ebfaed2e84a992bdde50d207131ec29)), closes [#674](https://github.com/paldepind/snabbdom/issues/674)
* **exports:** remove package.json main field ([3122eec](https://github.com/paldepind/snabbdom/commit/3122eec9b98ffdf52fd31ceb2ced17c219e25042)), closes [#680](https://github.com/paldepind/snabbdom/issues/680)
* **exports:** remove the /snabbdom.bundle path ([c862993](https://github.com/paldepind/snabbdom/commit/c8629933599b3fdf3ea774f3ce67517908b79d8d))
* **exports:** replaced main export path with init ([09f2d1c](https://github.com/paldepind/snabbdom/commit/09f2d1ca5a16fd0b402209d90e58b97998efacef)), closes [#522](https://github.com/paldepind/snabbdom/issues/522)
* **package:** no module field ([2b30e25](https://github.com/paldepind/snabbdom/commit/2b30e25f0d261d2d7f37127bda0c235b0a5acf57)), closes [#681](https://github.com/paldepind/snabbdom/issues/681)
* **props:** do not attempt to delete node properties ([6f316c1](https://github.com/paldepind/snabbdom/commit/6f316c141b43ccb1c2c355ab8d0c499984154ef1)), closes [#623](https://github.com/paldepind/snabbdom/issues/623) [#283](https://github.com/paldepind/snabbdom/issues/283) [#415](https://github.com/paldepind/snabbdom/issues/415) [#307](https://github.com/paldepind/snabbdom/issues/307) [#151](https://github.com/paldepind/snabbdom/issues/151) [#416](https://github.com/paldepind/snabbdom/issues/416)
* **typescript:** do not redeclare Element.setAttribute(NS) ([0620b5e](https://github.com/paldepind/snabbdom/commit/0620b5eda03cd124d4bd743660cb376b0d75a0a3)), closes [#615](https://github.com/paldepind/snabbdom/issues/615)
* do not provide UMD bundles ([8e24bbf](https://github.com/paldepind/snabbdom/commit/8e24bbf016ff5cc0afb2759ec2e4b745921ee453)), closes [#498](https://github.com/paldepind/snabbdom/issues/498) [#514](https://github.com/paldepind/snabbdom/issues/514) [#481](https://github.com/paldepind/snabbdom/issues/481)
* only esm and correct import paths ([dad44f0](https://github.com/paldepind/snabbdom/commit/dad44f0d632d344ca13ee8430d941c26a53d5c2a)), closes [#516](https://github.com/paldepind/snabbdom/issues/516) [#437](https://github.com/paldepind/snabbdom/issues/437) [#263](https://github.com/paldepind/snabbdom/issues/263)
- **docs:** enable eslint rule array-bracket-spacing ([77e54e9](https://github.com/paldepind/snabbdom/commit/77e54e9105394d4e6d21647a38adfb80ea567ee2))
- **docs:** enable eslint rule import/first ([17cf7ae](https://github.com/paldepind/snabbdom/commit/17cf7ae931185d8ab1ac1e4f8b7042677e03db8d))
- **docs:** enable eslint rule import/newline-after-import ([cd3a5cf](https://github.com/paldepind/snabbdom/commit/cd3a5cf17ee33c738d17adcb78a8c254e96653b9))
- **docs:** enable eslint rule indent ([e2861bb](https://github.com/paldepind/snabbdom/commit/e2861bb1bd68c63c99c30907bb331c21f27cb248))
- **docs:** enable eslint rule key-spacing ([349b686](https://github.com/paldepind/snabbdom/commit/349b686bd8cfc24a2845fbab62081a15e5b42d0f))
- **docs:** enable eslint rule max-statements-per-line ([a128a23](https://github.com/paldepind/snabbdom/commit/a128a23ac3182677d10d1979213c92dcf04294ea))
- **docs:** enable eslint rule no-multi-spaces ([8179381](https://github.com/paldepind/snabbdom/commit/8179381a775acea73d0064c77d643944cf684947)), closes [#692](https://github.com/paldepind/snabbdom/issues/692)
- **docs:** enable eslint rule object-curly-spacing ([8b8fbd5](https://github.com/paldepind/snabbdom/commit/8b8fbd5e34fdc3a99ce67635cc44f5ef9e3cf30c))
- **docs:** enable eslint rule quote-props ([37512fe](https://github.com/paldepind/snabbdom/commit/37512fe8ee02cf374cf3d9a62c0f6e3203be2f28))
- **docs:** enable eslint rule quotes ([2d455b5](https://github.com/paldepind/snabbdom/commit/2d455b52dcabc2f8e52082536e635a66e98eb650))
- **docs:** enable eslint rule semi ([f4e7885](https://github.com/paldepind/snabbdom/commit/f4e7885663e645aeb728470d5e1f159365f94f1b))
- **docs:** enable eslint rule space-before-blocks ([9f2d2d7](https://github.com/paldepind/snabbdom/commit/9f2d2d7a1687c7169dff775724a811e1dd7ccd8b))
- **docs:** enable eslint rule space-before-function-paren ([23e7b87](https://github.com/paldepind/snabbdom/commit/23e7b87c64b5d587acae5ce7bd899ec0d071958e))
- **docs:** enable eslint rules object-\*-newline ([9a45b5b](https://github.com/paldepind/snabbdom/commit/9a45b5b22aba0fbed97431e99268a173995de4a1))
- **docs:** fix wrong module import paths ([3b6baee](https://github.com/paldepind/snabbdom/commit/3b6baee049f44cbc55cc2b0131c2c551e9c1b452)), closes [#691](https://github.com/paldepind/snabbdom/issues/691)
- **docs:** provide a release changelog ([616df35](https://github.com/paldepind/snabbdom/commit/616df35909f1d639d562418ea32122625104b00c)), closes [#670](https://github.com/paldepind/snabbdom/issues/670)
- **exports:** main export provided ([3becd84](https://github.com/paldepind/snabbdom/commit/3becd84cc1dcfb84e2ee292eab18aae36e415040)), closes [#682](https://github.com/paldepind/snabbdom/issues/682)
- **exports:** only named exports ([fefd141](https://github.com/paldepind/snabbdom/commit/fefd141f5f3567bb6dccbd6c43ce81f285f985bd)), closes [#522](https://github.com/paldepind/snabbdom/issues/522) [#523](https://github.com/paldepind/snabbdom/issues/523)
- **exports:** relative values in exports field ([187088e](https://github.com/paldepind/snabbdom/commit/187088ee0ebfaed2e84a992bdde50d207131ec29)), closes [#674](https://github.com/paldepind/snabbdom/issues/674)
- **exports:** remove package.json main field ([3122eec](https://github.com/paldepind/snabbdom/commit/3122eec9b98ffdf52fd31ceb2ced17c219e25042)), closes [#680](https://github.com/paldepind/snabbdom/issues/680)
- **exports:** remove the /snabbdom.bundle path ([c862993](https://github.com/paldepind/snabbdom/commit/c8629933599b3fdf3ea774f3ce67517908b79d8d))
- **exports:** replaced main export path with init ([09f2d1c](https://github.com/paldepind/snabbdom/commit/09f2d1ca5a16fd0b402209d90e58b97998efacef)), closes [#522](https://github.com/paldepind/snabbdom/issues/522)
- **package:** no module field ([2b30e25](https://github.com/paldepind/snabbdom/commit/2b30e25f0d261d2d7f37127bda0c235b0a5acf57)), closes [#681](https://github.com/paldepind/snabbdom/issues/681)
- **props:** do not attempt to delete node properties ([6f316c1](https://github.com/paldepind/snabbdom/commit/6f316c141b43ccb1c2c355ab8d0c499984154ef1)), closes [#623](https://github.com/paldepind/snabbdom/issues/623) [#283](https://github.com/paldepind/snabbdom/issues/283) [#415](https://github.com/paldepind/snabbdom/issues/415) [#307](https://github.com/paldepind/snabbdom/issues/307) [#151](https://github.com/paldepind/snabbdom/issues/151) [#416](https://github.com/paldepind/snabbdom/issues/416)
- **typescript:** do not redeclare Element.setAttribute(NS) ([0620b5e](https://github.com/paldepind/snabbdom/commit/0620b5eda03cd124d4bd743660cb376b0d75a0a3)), closes [#615](https://github.com/paldepind/snabbdom/issues/615)
- do not provide UMD bundles ([8e24bbf](https://github.com/paldepind/snabbdom/commit/8e24bbf016ff5cc0afb2759ec2e4b745921ee453)), closes [#498](https://github.com/paldepind/snabbdom/issues/498) [#514](https://github.com/paldepind/snabbdom/issues/514) [#481](https://github.com/paldepind/snabbdom/issues/481)
- only esm and correct import paths ([dad44f0](https://github.com/paldepind/snabbdom/commit/dad44f0d632d344ca13ee8430d941c26a53d5c2a)), closes [#516](https://github.com/paldepind/snabbdom/issues/516) [#437](https://github.com/paldepind/snabbdom/issues/437) [#263](https://github.com/paldepind/snabbdom/issues/263)
## [v0.7.2] - 2018-09-02
@ -121,7 +114,7 @@ extensions.
- Improvements to TypeScript types #364. Thanks to @gfmio.
- In some cases and browsers the style module would cause elements to not be removed correctly #367. Thanks to @jvanbruegge for fixing this tricky bug.
## [v0.7.0] - 2017-07-27
## Breaking change
@ -144,50 +137,52 @@ The example above will result in the HTML: `<div foo bar="baz" />`. Even if `bar
Previously `h("input", { attrs: { required: 0 } })` would result in the HTML `<input>` since `required` was a know boolean attribute and `0` is falsey. Per the new behavior the HTML will be `<input required="0">`. To accomidate for the change always give boolean values for boolean attributes.
## Bugfixes
- `toVNode` now handles `DocumentFragment` which makes it possible to patch into a fragment. #320. Thanks to @staltz.
- Custom boolean attributes are handled correctly. #314. Thanks to @caridy.
- Type improvement. `VNode` key property can be `undefined` #290. Thanks to @yarom82.
- Type improvement. `VNode` key property can be `undefined` #290. Thanks to @yarom82.
- Data attributes are checked for existence before deleting. Old behavior caused error in Safari. #313. Thanks to @FeliciousX.
## Performance improvements
- New handling of boolean attributes. #314. Thanks to @caridy.
## [v0.6.9] - 2017-05-19
## Bug fixes
- Fix style delayed and remove to be optional in TypeScript, https://github.com/snabbdom/snabbdom/issues/295
## [v0.6.8] - 2017-05-16
## Bug fixes
- Fix error when class is set by vdom selector in SVG, https://github.com/snabbdom/snabbdom/issues/217. Thanks to @caesarsol
- Fix hyperscript to support undefined or null children in TypeScript, https://github.com/snabbdom/snabbdom/issues/226. Thanks to @ornicar
- Fix thunk function so it is not called redundantly, https://github.com/snabbdom/snabbdom/pull/273. Thanks to @caesarsol
- Improve TypeScript types of VNode props, https://github.com/snabbdom/snabbdom/issues/264 and https://github.com/snabbdom/snabbdom/issues/264. Thanks to @mightyiam
- Fix error when class is set by vdom selector in SVG, https://github.com/snabbdom/snabbdom/issues/217. Thanks to @caesarsol
- Fix hyperscript to support undefined or null children in TypeScript, https://github.com/snabbdom/snabbdom/issues/226. Thanks to @ornicar
- Fix thunk function so it is not called redundantly, https://github.com/snabbdom/snabbdom/pull/273. Thanks to @caesarsol
- Improve TypeScript types of VNode props, https://github.com/snabbdom/snabbdom/issues/264 and https://github.com/snabbdom/snabbdom/issues/264. Thanks to @mightyiam
- Fix toVNode() for comment nodes, lacking some fields, https://github.com/snabbdom/snabbdom/pull/266. Thanks to @staltz
## Performance improvements
- Improvement for attribute patching, https://github.com/snabbdom/snabbdom/issues/257. Thanks to @diervo
- Improvement for attribute patching, https://github.com/snabbdom/snabbdom/issues/257. Thanks to @diervo
## [v0.6.6] - 2017-03-07
## Bug fixes
- The attributes module sets boolean attributes correctly according to the specificaiton. https://github.com/snabbdom/snabbdom/issues/254. Thanks to @PerWiklander for reporting the bug.
## [v0.6.5] - 2017-02-25
This is a patch version with a few bug fixes.
## Bug fixes
- Fix `toVNode()` to handle text nodes correctly, https://github.com/snabbdom/snabbdom/issues/252. Thanks to @Steelfish
- Fix `toVNode()` to handle text nodes correctly, https://github.com/snabbdom/snabbdom/issues/252. Thanks to @Steelfish
- Fix dataset module to support old browsers, such as IE10. Thanks @staltz
- Fix "create element" workflow to align with "update element" workflow, https://github.com/snabbdom/snabbdom/pull/234. Thanks @caridy
- Fix "create element" workflow to align with "update element" workflow, https://github.com/snabbdom/snabbdom/pull/234. Thanks @caridy
## [v0.6.4] - 2017-02-09
This version adds some features such as support for comment nodes and better server-side/client-side rendering collaboration, besides some bug fixes.
@ -198,13 +193,13 @@ This version adds some features such as support for comment nodes and better ser
Example:
``` js
h('!', 'Will show as a comment')
```js
h("!", "Will show as a comment");
```
will be rendered on the DOM as
``` html
```html
<!-- Will show as a comment -->
```
@ -214,31 +209,31 @@ Useful for client-side rendering over existing HTML that was rendered server-sid
Example:
``` js
import {toVNode} from 'snabbdom/tovnode'
```js
import { toVNode } from "snabbdom/tovnode";
// ...
patch(toVNode(element), vnode)
patch(toVNode(element), vnode);
```
Will deep-convert the `element` to a VNode, this way allowing existing HTML to not be ignored by the patch process.
## Bug fixes
- Fix compatibility issue of String.prototype.startsWith in the Style Module. https://github.com/snabbdom/snabbdom/pull/228 Thanks to @zhulongzheng
- Support for `null`/`undefined` children without crashing. https://github.com/snabbdom/snabbdom/issues/226 Thanks to @nunocastromartins
- Fix compatibility issue of String.prototype.startsWith in the Style Module. https://github.com/snabbdom/snabbdom/pull/228 Thanks to @zhulongzheng
- Support for `null`/`undefined` children without crashing. https://github.com/snabbdom/snabbdom/issues/226 Thanks to @nunocastromartins
## [v0.6.3] - 2017-01-16
## Bugfixes
- Fix the export of the `Module` interface for TypeScript projects depending on snabbdom.
- Fix the export of the `Module` interface for TypeScript projects depending on snabbdom.
## [v0.6.2] - 2017-01-16
## Bugfixes
- Fix the export of the `Hooks` interface for TypeScript projects depending on snabbdom.
- Fix the export of the `Hooks` interface for TypeScript projects depending on snabbdom.
## [v0.6.1] - 2017-01-05
The biggest change in this release is that the Snabbdom source code has been ported to TypeScript. The work has been primarily done by @staltz. This brings much improved support for using Snabbdom in TypeScript projects.
@ -246,11 +241,13 @@ The biggest change in this release is that the Snabbdom source code has been por
**Note**: This release contains breaking changes. See below.
## New features
- Complete TypeScript support. Thanks to @staltz.
- Support for CSS variables. #195. Thanks to @jlesquembre.
- Allow `h(sel, data, node)` and `h(sel, node)` shortcut notations in the `h` function. #196. That is, instead of `h('div', [child])` one can now do `h('div', child)`. Thanks to @AlexGalays.
## Bugfixes
- Fix custom element creation when tag name begins with 'svg'. #213. Thanks to @tdumitrescu.
- Fix bug related to updating one child with same key but different selector. #188. Thanks to @zhulongzheng.
- Strings can be used as children inside SVG elements. #208. Thanks to @jbucaran and @jbucaran.
@ -260,18 +257,16 @@ The biggest change in this release is that the Snabbdom source code has been por
The TypeScript rewrite uses the `import` and `export` features introduced in ECMAScript 2015. Unfortunately the ES imports have no analogy to the CommonJS pattern of setting `module.exports`. This means that the Snabbdom modules that previously used this feature now have to be imported in a slightly different way.
``` js
```js
var h = require("snabbdom/h"); // The old way
var h = require("snabbdom/h").h; // The new way
var h = require("snabbdom/h").default; // Alternative new way
var {h} = require("snabbdom/h"); // Using destructuring
var { h } = require("snabbdom/h"); // Using destructuring
```
## [v0.6.0] - 2017-01-05
Deprecated. Use [version 0.6.1](https://github.com/snabbdom/snabbdom/releases/tag/v0.6.1) instead.
Deprecated. Use [version 0.6.1](https://github.com/snabbdom/snabbdom/releases/tag/v0.6.1) instead.
## v0.5.0 - 2016-05-16
@ -279,8 +274,7 @@ Deprecated. Use [version 0.6.1](https://github.com/snabbdom/snabbdom/releases/ta
This release contains a new thunk implementation that solves many issues with the old thunk implementation. The thunk API has changed slightly. Please see the [thunks](https://github.com/paldepind/snabbdom#thunks) section in the readme.
[Unreleased]: https://github.com/snabbdom/snabbdom/compare/v0.7.2...HEAD
[unreleased]: https://github.com/snabbdom/snabbdom/compare/v0.7.2...HEAD
[v0.7.2]: https://github.com/snabbdom/snabbdom/compare/v0.7.0...v0.7.2
[v0.7.0]: https://github.com/snabbdom/snabbdom/compare/v0.6.9...v0.7.0
[v0.6.9]: https://github.com/snabbdom/snabbdom/compare/v0.6.8...v0.6.9

@ -1,32 +1,32 @@
module.exports = {
// Latest mainstream
BS_Chrome_Current: {
base: 'BrowserStack',
browser: 'chrome',
browser_version: 'latest',
os: 'Windows',
os_version: '10',
base: "BrowserStack",
browser: "chrome",
browser_version: "latest",
os: "Windows",
os_version: "10",
},
BS_Firefox_Current: {
base: 'BrowserStack',
browser: 'firefox',
browser_version: 'latest',
os: 'Windows',
os_version: '10',
base: "BrowserStack",
browser: "firefox",
browser_version: "latest",
os: "Windows",
os_version: "10",
},
BS_Safari_Current: {
base: 'BrowserStack',
browser: 'safari',
browser_version: 'latest',
os: 'OS X',
os_version: 'High Sierra',
base: "BrowserStack",
browser: "safari",
browser_version: "latest",
os: "OS X",
os_version: "High Sierra",
},
BS_Android_8: {
base: 'BrowserStack',
browser: 'Android',
device: 'Google Pixel 2',
os: 'Android',
os_version: '8.0',
base: "BrowserStack",
browser: "Android",
device: "Google Pixel 2",
os: "Android",
os_version: "8.0",
real_mobile: true,
},
@ -41,11 +41,11 @@ module.exports = {
},
*/
BS_Firefox_52: {
base: 'BrowserStack',
browser: 'firefox',
browser_version: '52',
os: 'Windows',
os_version: '10',
base: "BrowserStack",
browser: "firefox",
browser_version: "52",
os: "Windows",
os_version: "10",
},
/* https://github.com/snabbdom/snabbdom/issues/469
BS_Safari_9: {
@ -59,11 +59,11 @@ module.exports = {
// Misc
BS_Android_4_4: {
base: 'BrowserStack',
device_browser: 'ucbrowser',
device: 'Google Nexus 5',
os: 'Android',
os_version: '4.4',
base: "BrowserStack",
device_browser: "ucbrowser",
device: "Google Nexus 5",
os: "Android",
os_version: "4.4",
real_mobile: true,
},
/* https://github.com/snabbdom/snabbdom/issues/470
@ -78,24 +78,24 @@ module.exports = {
},
*/
BS_MS_Edge: {
base: 'BrowserStack',
browser: 'edge',
browser_version: 'latest',
os: 'Windows',
os_version: '10',
base: "BrowserStack",
browser: "edge",
browser_version: "latest",
os: "Windows",
os_version: "10",
},
BS_IE_11: {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '7',
base: "BrowserStack",
browser: "ie",
browser_version: "11.0",
os: "Windows",
os_version: "7",
},
BS_IE_10: {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '7',
base: "BrowserStack",
browser: "ie",
browser_version: "10.0",
os: "Windows",
os_version: "7",
},
}
};

@ -1,29 +1,31 @@
{
"extends": [
"@commitlint/config-conventional"
],
"extends": ["@commitlint/config-conventional"],
"rules": {
"scope-empty": [2, "never"],
"scope-enum": [2, "always", [
"ci",
"commitlint",
"deps",
"docs",
"eslint",
"eventlisteners",
"examples",
"exports",
"format",
"git",
"github",
"karma",
"npm",
"package",
"relic",
"style",
"ttypescript",
"typescript",
"vscode"
]]
"scope-enum": [
2,
"always",
[
"ci",
"commitlint",
"deps",
"docs",
"eslint",
"eventlisteners",
"examples",
"exports",
"format",
"git",
"github",
"karma",
"npm",
"package",
"relic",
"style",
"ttypescript",
"typescript",
"vscode"
]
]
}
}

@ -1,74 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta charset="utf-8">
<title>Carousel</title>
<script type="module" src="./index.js"></script>
<style type="text/css">
div.view {
margin: 10px;
}
h1 {
font-size: 24px;
color: #505000;
}
svg {
display: block;
margin-bottom: 10px;
border: 1px solid gray;
}
g#carousel {
-webkit-transition: -webkit-transform 1s ease;
transition: transform 1s ease;
}
polygon {
stroke: #808000;
transition: fill 0.5s linear;
}
polygon#yellow {
fill: rgba(255,255,0,0.4);
}
polygon#yellow:hover, polygon#yellow:active {
fill: yellow;
}
polygon#green {
fill: rgba(0,128,0,0.4);
}
polygon#green:hover, polygon#green:active {
fill: green;
}
polygon#magenta {
fill: rgba(255,0,255,0.4);
}
polygon#magenta:hover, polygon#magenta:active {
fill: magenta;
}
polygon#red {
fill: rgba(255,0,0,0.4);
}
polygon#red:hover, polygon#red:active {
fill: red;
}
polygon#cyan {
fill: rgba(0,255,255,0.4);
}
polygon#cyan:hover, polygon#cyan:active {
fill: cyan;
}
polygon#blue {
fill: rgba(0,0,255,0.4);
}
polygon#blue:hover, polygon#blue:active {
fill: blue;
}
button {
font-size: 15px;
margin: 0 0.7em 0.7em 0;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta charset="utf-8" />
<title>Carousel</title>
<script type="module" src="./index.js"></script>
<style type="text/css">
div.view {
margin: 10px;
}
h1 {
font-size: 24px;
color: #505000;
}
svg {
display: block;
margin-bottom: 10px;
border: 1px solid gray;
}
g#carousel {
-webkit-transition: -webkit-transform 1s ease;
transition: transform 1s ease;
}
polygon {
stroke: #808000;
transition: fill 0.5s linear;
}
polygon#yellow {
fill: rgba(255, 255, 0, 0.4);
}
polygon#yellow:hover,
polygon#yellow:active {
fill: yellow;
}
polygon#green {
fill: rgba(0, 128, 0, 0.4);
}
polygon#green:hover,
polygon#green:active {
fill: green;
}
polygon#magenta {
fill: rgba(255, 0, 255, 0.4);
}
polygon#magenta:hover,
polygon#magenta:active {
fill: magenta;
}
polygon#red {
fill: rgba(255, 0, 0, 0.4);
}
polygon#red:hover,
polygon#red:active {
fill: red;
}
polygon#cyan {
fill: rgba(0, 255, 255, 0.4);
}
polygon#cyan:hover,
polygon#cyan:active {
fill: cyan;
}
polygon#blue {
fill: rgba(0, 0, 255, 0.4);
}
polygon#blue:hover,
polygon#blue:active {
fill: blue;
}
button {
font-size: 15px;
margin: 0 0.7em 0.7em 0;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
</html>

@ -1,74 +1,117 @@
import { init } from '../../build/package/init.js'
import { attributesModule } from '../../build/package/modules/attributes.js'
import { styleModule } from '../../build/package/modules/style.js'
import { eventListenersModule } from '../../build/package/modules/eventlisteners.js'
import { h } from '../../build/package/h.js'
import { init } from "../../build/package/init.js";
import { attributesModule } from "../../build/package/modules/attributes.js";
import { styleModule } from "../../build/package/modules/style.js";
import { eventListenersModule } from "../../build/package/modules/eventlisteners.js";
import { h } from "../../build/package/h.js";
var patch = init([attributesModule, styleModule, eventListenersModule])
var patch = init([attributesModule, styleModule, eventListenersModule]);
var vnode
var vnode;
var data = {
degRotation: 0
}
degRotation: 0,
};
function gRotation () {
function gRotation() {
// console.log("gRotation: %s", data.degRotation);
return 'rotate(' + data.degRotation + 'deg)'
return "rotate(" + data.degRotation + "deg)";
}
function triangleClick (id) {
console.log('triangleClick: %s', id)
render()
function triangleClick(id) {
console.log("triangleClick: %s", id);
render();
}
function handleRotate (degs) {
data.degRotation += degs
console.log('handleRotate: %s, %s', degs, data.degRotation)
render()
function handleRotate(degs) {
data.degRotation += degs;
console.log("handleRotate: %s, %s", degs, data.degRotation);
render();
}
function handleReset (degs) {
data.degRotation = degs
console.log('handleReset: %s', degs)
render()
function handleReset(degs) {
data.degRotation = degs;
console.log("handleReset: %s", degs);
render();
}
function render () {
vnode = patch(vnode, view(data))
function render() {
vnode = patch(vnode, view(data));
}
const hTriangle = (id, degRotation) =>
h('polygon#' + id, {
h("polygon#" + id, {
attrs: {
points: '-50,-88 0,-175 50,-88',
transform: 'rotate(' + degRotation + ')',
'stroke-width': 3
points: "-50,-88 0,-175 50,-88",
transform: "rotate(" + degRotation + ")",
"stroke-width": 3,
},
on: {
click: () => {
triangleClick(id);
},
},
on: { click: () => { triangleClick(id) } }
})
});
const view = (data) =>
h('div.view', [
h('h1', 'Snabbdom SVG Carousel'),
h('svg', { attrs: { width: 380, height: 380, viewBox: [-190, -190, 380, 380] } }, [
h('g#carousel',
{ style: { '-webkit-transform': gRotation(), transform: gRotation() } }, [
hTriangle('yellow', 0),
hTriangle('green', 60),
hTriangle('magenta', 120),
hTriangle('red', 180),
hTriangle('cyan', 240),
hTriangle('blue', 300)
])
]),
h('button', { on: { click: () => { handleRotate(60) } } }, 'Rotate Clockwise'),
h('button', { on: { click: () => { handleRotate(-60) } } }, 'Rotate Anticlockwise'),
h('button', { on: { click: () => { handleReset(0) } } }, 'Reset')
])
const view = () =>
h("div.view", [
h("h1", "Snabbdom SVG Carousel"),
h(
"svg",
{ attrs: { width: 380, height: 380, viewBox: [-190, -190, 380, 380] } },
[
h(
"g#carousel",
{
style: { "-webkit-transform": gRotation(), transform: gRotation() },
},
[
hTriangle("yellow", 0),
hTriangle("green", 60),
hTriangle("magenta", 120),
hTriangle("red", 180),
hTriangle("cyan", 240),
hTriangle("blue", 300),
]
),
]
),
h(
"button",
{
on: {
click: () => {
handleRotate(60);
},
},
},
"Rotate Clockwise"
),
h(
"button",
{
on: {
click: () => {
handleRotate(-60);
},
},
},
"Rotate Anticlockwise"
),
h(
"button",
{
on: {
click: () => {
handleReset(0);
},
},
},
"Reset"
),
]);
window.addEventListener('DOMContentLoaded', () => {
var container = document.getElementById('container')
vnode = patch(container, view(data))
render()
})
window.addEventListener("DOMContentLoaded", () => {
var container = document.getElementById("container");
vnode = patch(container, view(data));
render();
});

@ -2,14 +2,18 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<title>Hero animation</title>
<script type="module" src="./index.js"></script>
<style>
{
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
{
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html, body {
html,
body {
height: 100%;
margin: 0;
}
@ -22,8 +26,7 @@
position: relative;
background: #fff;
}
@media (min-width: 28em),
@media (min-height: 38em) {
@media (min-width: 28em), @media (min-height: 38em) {
body {
display: flex;
flex-direction: column;
@ -33,7 +36,7 @@
position: relative;
}
.page-container {
box-shadow: 0 0 1em rgba(0, 0, 0, .5);
box-shadow: 0 0 1em rgba(0, 0, 0, 0.5);
width: 28em;
min-height: 38em;
height: 38em;
@ -41,14 +44,13 @@
}
.page {
background: #fff;
transition: opacity 0.4s ease-in-out,
transform 0.4s ease-in-out;
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
width: 100%;
height: 100%;
}
h2 {
font-size: 1.1em;
margin: .2em 0;
margin: 0.2em 0;
}
.header {
height: 3.5em;
@ -58,13 +60,12 @@
.header-content {
width: 100%;
box-sizing: border-box;
padding: .4em .8em;
padding: 0.4em 0.8em;
color: #fff;
display: flex;
align-items: center;
position: absolute;
transition: opacity 0.4s ease-in-out,
transform 0.4s ease-in-out;
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
}
.header h1 {
font-weight: normal;
@ -88,7 +89,7 @@
justify-content: center;
width: 2.7em;
height: 2.7em;
margin-right: .5em;
margin-right: 0.5em;
position: relative;
}
.header .rank-circle {
@ -97,7 +98,7 @@
width: 2.7em;
height: 2.7em;
border-radius: 1.35em;
margin-right: .5em;
margin-right: 0.5em;
transition: transform 0.4s ease-in-out;
top: 0;
left: 0;
@ -113,13 +114,12 @@
text-align: center;
width: 1.8em;
height: 1.8em;
border-radius: .9em;
background: rgba(0, 0, 0, .5);
border-radius: 0.9em;
background: rgba(0, 0, 0, 0.5);
transition: transform 0.4s ease-in-out;
}
.hero {
transition: transform 0.4s ease-in-out,
opacity 0.4s ease-in-out;
transition: transform 0.4s ease-in-out, opacity 0.4s ease-in-out;
}
.page-content {
position: relative;
@ -130,13 +130,11 @@
.list {
position: absolute;
width: 100%;
transition: transform 0.4s ease-in-out,
opacity 0.4s ease-in-out;
transition: transform 0.4s ease-in-out, opacity 0.4s ease-in-out;
}
.desc {
position: absolute;
transition: transform 0.4s ease-in-out,
opacity 0.4s ease-in-out;
transition: transform 0.4s ease-in-out, opacity 0.4s ease-in-out;
padding: 1em;
}
.spacer {

@ -1,136 +1,233 @@
/* jshint esnext: true */
import { init } from '../../build/package/init.js'
import { classModule } from '../../build/package/modules/class.js'
import { heroModule } from '../../build/package/modules/hero.js'
import { styleModule } from '../../build/package/modules/style.js'
import { eventListenersModule } from '../../build/package/modules/eventlisteners.js'
import { h } from '../../build/package/h.js'
import { init } from "../../build/package/init.js";
import { classModule } from "../../build/package/modules/class.js";
import { heroModule } from "../../build/package/modules/hero.js";
import { styleModule } from "../../build/package/modules/style.js";
import { eventListenersModule } from "../../build/package/modules/eventlisteners.js";
import { h } from "../../build/package/h.js";
var patch = init([classModule, heroModule, styleModule, eventListenersModule])
var patch = init([classModule, heroModule, styleModule, eventListenersModule]);
var vnode
var vnode;
var data = {
selected: undefined,
movies: [
{ rank: 1, title: 'This is an', desc: 'Lorem ipsum dolor sit amet, sed pede integer vitae bibendum, accumsan sit, vulputate aenean tempora ipsum. Lorem sed id et metus, eros posuere suspendisse nec nunc justo, fusce augue placerat nibh purus suspendisse. Aliquam aliquam, ut eget. Mollis a eget sed nibh tincidunt nec, mi integer, proin magna lacus iaculis tortor. Aliquam vel arcu arcu, vivamus a urna fames felis vel wisi, cursus tortor nec erat dignissim cras sem, mauris ac venenatis tellus elit.' },
{ rank: 2, title: 'example of', desc: 'Consequuntur ipsum nulla, consequat curabitur in magnis risus. Taciti mattis bibendum tellus nibh, at dui neque eget, odio pede ut, sapien pede, ipsum ut. Sagittis dui, sodales sem, praesent ipsum conubia eget lorem lobortis wisi.' },
{ rank: 3, title: 'Snabbdom', desc: 'Quam lorem aliquam fusce wisi, urna purus ipsum pharetra sed, at cras sodales enim vestibulum odio cras, luctus integer phasellus.' },
{ rank: 4, title: 'doing hero transitions', desc: 'Et orci hac ultrices id in. Diam ultrices luctus egestas, sem aliquam auctor molestie odio laoreet. Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris. Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst. Sem nec tristique vel vehicula fringilla, nibh eu et posuere mi rhoncus.' },
{ rank: 5, title: 'using the', desc: 'Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris. Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst. Sem nec tristique vel vehicula fringilla, nibh eu et posuere mi rhoncus.' },
{ rank: 6, title: 'module for hero transitions', desc: 'Sapien laoreet, ligula elit tortor nulla pellentesque, maecenas enim turpis, quae duis venenatis vivamus ultricies, nunc imperdiet sollicitudin ipsum malesuada. Ut sem. Wisi fusce nullam nibh enim. Nisl hymenaeos id sed sed in. Proin leo et, pulvinar nunc pede laoreet.' },
{ rank: 7, title: 'click on ar element in', desc: 'Accumsan quia, id nascetur dui et congue erat, id excepteur, primis ratione nec. At nulla et. Suspendisse lobortis, lobortis in tortor fringilla, duis adipiscing vestibulum voluptates sociosqu auctor.' },
{ rank: 8, title: 'the list', desc: 'Ante tellus egestas vel hymenaeos, ut viverra nibh ut, ipsum nibh donec donec dolor. Eros ridiculus vel egestas convallis ipsum, commodo ut venenatis nullam porta iaculis, suspendisse ante proin leo, felis risus etiam.' },
{ rank: 9, title: 'to witness', desc: 'Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst.' },
{ rank: 10, title: 'the effect', desc: 'Et orci hac ultrices id in. Diam ultrices luctus egestas, sem aliquam auctor molestie odio laoreet. Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris' },
]
}
{
rank: 1,
title: "This is an",
desc:
"Lorem ipsum dolor sit amet, sed pede integer vitae bibendum, accumsan sit, vulputate aenean tempora ipsum. Lorem sed id et metus, eros posuere suspendisse nec nunc justo, fusce augue placerat nibh purus suspendisse. Aliquam aliquam, ut eget. Mollis a eget sed nibh tincidunt nec, mi integer, proin magna lacus iaculis tortor. Aliquam vel arcu arcu, vivamus a urna fames felis vel wisi, cursus tortor nec erat dignissim cras sem, mauris ac venenatis tellus elit.",
},
{
rank: 2,
title: "example of",
desc:
"Consequuntur ipsum nulla, consequat curabitur in magnis risus. Taciti mattis bibendum tellus nibh, at dui neque eget, odio pede ut, sapien pede, ipsum ut. Sagittis dui, sodales sem, praesent ipsum conubia eget lorem lobortis wisi.",
},
{
rank: 3,
title: "Snabbdom",
desc:
"Quam lorem aliquam fusce wisi, urna purus ipsum pharetra sed, at cras sodales enim vestibulum odio cras, luctus integer phasellus.",
},
{
rank: 4,
title: "doing hero transitions",
desc:
"Et orci hac ultrices id in. Diam ultrices luctus egestas, sem aliquam auctor molestie odio laoreet. Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris. Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst. Sem nec tristique vel vehicula fringilla, nibh eu et posuere mi rhoncus.",
},
{
rank: 5,
title: "using the",
desc:
"Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris. Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst. Sem nec tristique vel vehicula fringilla, nibh eu et posuere mi rhoncus.",
},
{
rank: 6,
title: "module for hero transitions",
desc:
"Sapien laoreet, ligula elit tortor nulla pellentesque, maecenas enim turpis, quae duis venenatis vivamus ultricies, nunc imperdiet sollicitudin ipsum malesuada. Ut sem. Wisi fusce nullam nibh enim. Nisl hymenaeos id sed sed in. Proin leo et, pulvinar nunc pede laoreet.",
},
{
rank: 7,
title: "click on ar element in",
desc:
"Accumsan quia, id nascetur dui et congue erat, id excepteur, primis ratione nec. At nulla et. Suspendisse lobortis, lobortis in tortor fringilla, duis adipiscing vestibulum voluptates sociosqu auctor.",
},
{
rank: 8,
title: "the list",
desc:
"Ante tellus egestas vel hymenaeos, ut viverra nibh ut, ipsum nibh donec donec dolor. Eros ridiculus vel egestas convallis ipsum, commodo ut venenatis nullam porta iaculis, suspendisse ante proin leo, felis risus etiam.",
},
{
rank: 9,
title: "to witness",
desc:
"Metus amet ad, elit porttitor a aliquet commodo lacus, integer neque imperdiet augue laoreet, nonummy turpis lacus sed pulvinar condimentum platea. Wisi eleifend quis, tristique dictum, ac dictumst.",
},
{
rank: 10,
title: "the effect",
desc:
"Et orci hac ultrices id in. Diam ultrices luctus egestas, sem aliquam auctor molestie odio laoreet. Pede nam cubilia, diam vestibulum ornare natoque, aenean etiam fusce id, eget dictum blandit et mauris mauris",
},
],
};
function select (m) {
data.selected = m
render()
function select(m) {
data.selected = m;
render();
}
function render () {
vnode = patch(vnode, view(data))
function render() {
vnode = patch(vnode, view(data));
}
const fadeInOutStyle = {
opacity: '0', delayed: { opacity: '1' }, remove: { opacity: '0' }
}
opacity: "0",
delayed: { opacity: "1" },
remove: { opacity: "0" },
};
const detailView = (movie) =>
h('div.page', { style: fadeInOutStyle }, [
h('div.header', [
h('div.header-content.detail', {
style: { opacity: '1', remove: { opacity: '0' } },
}, [
h('div.rank', [
h('span.header-rank.hero', { hero: { id: 'rank' + movie.rank } }, movie.rank),
h('div.rank-circle', {
style: {
transform: 'scale(0)',
delayed: { transform: 'scale(1)' },
destroy: { transform: 'scale(0)' }
h("div.page", { style: fadeInOutStyle }, [
h("div.header", [
h(
"div.header-content.detail",
{
style: { opacity: "1", remove: { opacity: "0" } },
},
[
h("div.rank", [
h(
"span.header-rank.hero",
{ hero: { id: "rank" + movie.rank } },
movie.rank
),
h("div.rank-circle", {
style: {
transform: "scale(0)",
delayed: { transform: "scale(1)" },
destroy: { transform: "scale(0)" },
},
}),
]),
h(
"div.hero.header-title",
{ hero: { id: movie.title } },
movie.title
),
h("div.spacer"),
h(
"div.close",
{
on: {
click: () => {
select(undefined);
},
},
style: {
transform: "scale(0)",
delayed: { transform: "scale(1)" },
destroy: { transform: "scale(0)" },
},
},
}),
]),
h('div.hero.header-title', { hero: { id: movie.title } }, movie.title),
h('div.spacer'),
h('div.close', {
on: { click: () => { select(undefined) } },
"x"
),
]
),
]),
h("div.page-content", [
h(
"div.desc",
{
style: {
transform: 'scale(0)',
delayed: { transform: 'scale(1)' },
destroy: { transform: 'scale(0)' }
opacity: "0",
transform: "translateX(3em)",
delayed: { opacity: "1", transform: "translate(0)" },
remove: {
opacity: "0",
position: "absolute",
top: "0",
left: "0",
transform: "translateX(3em)",
},
},
}, 'x'),
]),
]),
h('div.page-content', [
h('div.desc', {
style: {
opacity: '0',
transform: 'translateX(3em)',
delayed: { opacity: '1', transform: 'translate(0)' },
remove: {
opacity: '0',
position: 'absolute',
top: '0',
left: '0',
transform: 'translateX(3em)'
}
}
}, [
h('h2', 'Description:'),
h('span', movie.desc),
]),
},
[h("h2", "Description:"), h("span", movie.desc)]
),
]),
])
]);
const overviewView = (movies) =>
h('div.page', { style: fadeInOutStyle }, [
h('div.header', [
h('div.header-content.overview', {
style: fadeInOutStyle,
}, [
h('div.header-title', {
style: {
transform: 'translateY(-2em)',
delayed: { transform: 'translate(0)' },
destroy: { transform: 'translateY(-2em)' }
}
}, 'Top 10 movies'),
h('div.spacer'),
]),
h("div.page", { style: fadeInOutStyle }, [
h("div.header", [
h(
"div.header-content.overview",
{
style: fadeInOutStyle,
},
[
h(
"div.header-title",
{
style: {
transform: "translateY(-2em)",
delayed: { transform: "translate(0)" },
destroy: { transform: "translateY(-2em)" },
},
},
"Top 10 movies"
),
h("div.spacer"),
]
),
]),
h('div.page-content', [
h('div.list', {
style: {
opacity: '0',
delayed: { opacity: '1' },
remove: { opacity: '0', position: 'absolute', top: '0', left: '0' }
}
}, movies.map((movie) =>
h('div.row', {
on: { click: () => { select(movie) } },
}, [
h('div.hero.rank', [
h('span.hero', { hero: { id: 'rank' + movie.rank } }, movie.rank)
]),
h('div.hero', { hero: { id: movie.title } }, movie.title)
])
)),
h("div.page-content", [
h(
"div.list",
{
style: {
opacity: "0",
delayed: { opacity: "1" },
remove: { opacity: "0", position: "absolute", top: "0", left: "0" },
},
},
movies.map((movie) =>
h(
"div.row",
{
on: {
click: () => {
select(movie);
},
},
},
[
h("div.hero.rank", [
h(
"span.hero",
{ hero: { id: "rank" + movie.rank } },
movie.rank
),
]),
h("div.hero", { hero: { id: movie.title } }, movie.title),
]
)
)
),
]),
])
]);
const view = (data) =>
h('div.page-container', [
h("div.page-container", [
data.selected ? detailView(data.selected) : overviewView(data.movies),
])
]);
window.addEventListener('DOMContentLoaded', () => {
var container = document.getElementById('container')
vnode = patch(container, view(data))
render()
})
window.addEventListener("DOMContentLoaded", () => {
var container = document.getElementById("container");
vnode = patch(container, view(data));
render();
});

@ -16,17 +16,18 @@
display: inline-block;
cursor: pointer;
background: #fff;
box-shadow: 0 0 1px rgba(0, 0, 0, .2);
padding: .5em .8em;
transition: box-shadow .05s ease-in-out;
-webkit-transition: box-shadow .05s ease-in-out;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
padding: 0.5em 0.8em;
transition: box-shadow 0.05s ease-in-out;
-webkit-transition: box-shadow 0.05s ease-in-out;
}
.btn:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, .2);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.btn:active, .active, .active:hover {
box-shadow: 0 0 1px rgba(0, 0, 0, .2),
inset 0 0 4px rgba(0, 0, 0, .1);
.btn:active,
.active,
.active:hover {
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2), inset 0 0 4px rgba(0, 0, 0, 0.1);
}
.add {
float: right;
@ -44,12 +45,14 @@
box-sizing: border-box;
width: 100%;
left: 0px;
margin: .5em 0;
margin: 0.5em 0;
padding: 1em;
background: #fff;
box-shadow: 0 0 1px rgba(0, 0, 0, .2);
transition: transform .5s ease-in-out, opacity .5s ease-out, left .5s ease-in-out;
-webkit-transition: transform .5s ease-in-out, opacity .5s ease-out, left .5s ease-in-out;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
transition: transform 0.5s ease-in-out, opacity 0.5s ease-out,
left 0.5s ease-in-out;
-webkit-transition: transform 0.5s ease-in-out, opacity 0.5s ease-out,
left 0.5s ease-in-out;
}
.row div {
display: inline-block;
@ -69,7 +72,7 @@
position: absolute;
top: 0;
right: 0;
color: #C25151;
color: #c25151;
width: 1.4em;
height: 1.4em;
text-align: center;

@ -1,30 +1,90 @@
import { init } from '../../build/package/init.js'
import { classModule } from '../../build/package/modules/class.js'
import { propsModule } from '../../build/package/modules/props.js'
import { styleModule } from '../../build/package/modules/style.js'
import { eventListenersModule } from '../../build/package/modules/eventlisteners.js'
import { h } from '../../build/package/h.js'
import { init } from "../../build/package/init.js";
import { classModule } from "../../build/package/modules/class.js";
import { propsModule } from "../../build/package/modules/props.js";
import { styleModule } from "../../build/package/modules/style.js";
import { eventListenersModule } from "../../build/package/modules/eventlisteners.js";
import { h } from "../../build/package/h.js";
var patch = init([classModule, propsModule, styleModule, eventListenersModule])
var patch = init([classModule, propsModule, styleModule, eventListenersModule]);
var vnode
var vnode;
var nextKey = 11
var margin = 8
var sortBy = 'rank'
var totalHeight = 0
var nextKey = 11;
var margin = 8;
var sortBy = "rank";
var totalHeight = 0;
var originalData = [
{ rank: 1, title: 'The Shawshank Redemption', desc: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.', elmHeight: 0 },
{ rank: 2, title: 'The Godfather', desc: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.', elmHeight: 0 },
{ rank: 3, title: 'The Godfather: Part II', desc: 'The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.', elmHeight: 0 },
{ rank: 4, title: 'The Dark Knight', desc: 'When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.', elmHeight: 0 },
{ rank: 5, title: 'Pulp Fiction', desc: 'The lives of two mob hit men, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine in four tales of violence and redemption.', elmHeight: 0 },
{ rank: 6, title: 'Schindler\'s List', desc: 'In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis.', elmHeight: 0 },
{ rank: 7, title: '12 Angry Men', desc: 'A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.', elmHeight: 0 },
{ rank: 8, title: 'The Good, the Bad and the Ugly', desc: 'A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried in a remote cemetery.', elmHeight: 0 },
{ rank: 9, title: 'The Lord of the Rings: The Return of the King', desc: 'Gandalf and Aragorn lead the World of Men against Sauron\'s army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.', elmHeight: 0 },
{ rank: 10, title: 'Fight Club', desc: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and they form an underground fight club that evolves into something much, much more...', elmHeight: 0 },
]
{
rank: 1,
title: "The Shawshank Redemption",
desc:
"Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.",
elmHeight: 0,
},
{
rank: 2,
title: "The Godfather",
desc:
"The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.",
elmHeight: 0,
},
{
rank: 3,
title: "The Godfather: Part II",
desc:
"The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.",
elmHeight: 0,
},
{
rank: 4,
title: "The Dark Knight",
desc:
"When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.",
elmHeight: 0,
},
{
rank: 5,
title: "Pulp Fiction",
desc:
"The lives of two mob hit men, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.",
elmHeight: 0,
},
{
rank: 6,
title: "Schindler's List",
desc:
"In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis.",
elmHeight: 0,
},
{
rank: 7,
title: "12 Angry Men",
desc:
"A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.",
elmHeight: 0,
},
{
rank: 8,
title: "The Good, the Bad and the Ugly",
desc:
"A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried in a remote cemetery.",
elmHeight: 0,
},
{
rank: 9,
title: "The Lord of the Rings: The Return of the King",
desc:
"Gandalf and Aragorn lead the World of Men against Sauron's army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.",
elmHeight: 0,
},
{
rank: 10,
title: "Fight Club",
desc:
"An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and they form an underground fight club that evolves into something much, much more...",
elmHeight: 0,
},
];
var data = [
originalData[0],
originalData[1],
@ -36,84 +96,145 @@ var data = [
originalData[7],
originalData[8],
originalData[9],
]
];
function changeSort (prop) {
sortBy = prop
function changeSort(prop) {
sortBy = prop;
data.sort((a, b) => {
if (a[prop] > b[prop]) {
return 1
return 1;
}
if (a[prop] < b[prop]) {
return -1
return -1;
}
return 0
})
render()
return 0;
});
render();
}
function add () {
var n = originalData[Math.floor(Math.random() * 10)]
data = [{ rank: nextKey++, title: n.title, desc: n.desc, elmHeight: 0 }].concat(data)
render()
render()
function add() {
var n = originalData[Math.floor(Math.random() * 10)];
data = [
{ rank: nextKey++, title: n.title, desc: n.desc, elmHeight: 0 },
].concat(data);
render();
render();
}
function remove (movie) {
function remove(movie) {
data = data.filter((m) => {
return m !== movie
})
render()
return m !== movie;
});
render();
}
function movieView (movie) {
return h('div.row', {
key: movie.rank,
style: {
opacity: '0',
transform: 'translate(-200px)',
delayed: { transform: `translateY(${movie.offset}px)`, opacity: '1' },
remove: { opacity: '0', transform: `translateY(${movie.offset}px) translateX(200px)` }
function movieView(movie) {
return h(
"div.row",
{
key: movie.rank,
style: {
opacity: "0",
transform: "translate(-200px)",
delayed: { transform: `translateY(${movie.offset}px)`, opacity: "1" },
remove: {
opacity: "0",
transform: `translateY(${movie.offset}px) translateX(200px)`,
},
},
hook: {
insert: (vnode) => {
movie.elmHeight = vnode.elm.offsetHeight;
},
},
},
hook: { insert: (vnode) => { movie.elmHeight = vnode.elm.offsetHeight } },
}, [
h('div', { style: { fontWeight: 'bold' } }, movie.rank),
h('div', movie.title),
h('div', movie.desc),
h('div.btn.rm-btn', { on: { click: () => { remove(movie) } } }, 'x'),
])
[
h("div", { style: { fontWeight: "bold" } }, movie.rank),
h("div", movie.title),
h("div", movie.desc),
h(
"div.btn.rm-btn",
{
on: {
click: () => {
remove(movie);
},
},
},
"x"
),
]
);
}
function render () {
function render() {
data = data.reduce((acc, m) => {
var last = acc[acc.length - 1]
m.offset = last ? last.offset + last.elmHeight + margin : margin
return acc.concat(m)
}, [])
totalHeight = data.length === 0
? 0
: data[data.length - 1].offset + data[data.length - 1].elmHeight
vnode = patch(vnode, view(data))
var last = acc[acc.length - 1];
m.offset = last ? last.offset + last.elmHeight + margin : margin;
return acc.concat(m);
}, []);
totalHeight =
data.length === 0
? 0
: data[data.length - 1].offset + data[data.length - 1].elmHeight;
vnode = patch(vnode, view(data));
}
function view (data) {
return h('div', [
h('h1', 'Top 10 movies'),
h('div', [
h('a.btn.add', { on: { click: add } }, 'Add'),
'Sort by: ',
h('span.btn-group', [
h('a.btn.rank', { class: { active: sortBy === 'rank' }, on: { click: () => { changeSort('rank') } } }, 'Rank'),
h('a.btn.title', { class: { active: sortBy === 'title' }, on: { click: () => { changeSort('title') } } }, 'Title'),
h('a.btn.desc', { class: { active: sortBy === 'desc' }, on: { click: () => { changeSort('desc') } } }, 'Description'),
function view(data) {
return h("div", [
h("h1", "Top 10 movies"),
h("div", [
h("a.btn.add", { on: { click: add } }, "Add"),
"Sort by: ",
h("span.btn-group", [
h(
"a.btn.rank",
{
class: { active: sortBy === "rank" },
on: {
click: () => {
changeSort("rank");
},
},
},
"Rank"
),
h(
"a.btn.title",
{
class: { active: sortBy === "title" },
on: {
click: () => {
changeSort("title");
},
},
},
"Title"
),
h(
"a.btn.desc",
{
class: { active: sortBy === "desc" },
on: {
click: () => {
changeSort("desc");
},
},
},
"Description"
),
]),
]),
h('div.list', { style: { height: totalHeight + 'px' } }, data.map(movieView)),
])
h(
"div.list",
{ style: { height: totalHeight + "px" } },
data.map(movieView)
),
]);
}
window.addEventListener('DOMContentLoaded', () => {
var container = document.getElementById('container')
vnode = patch(container, view(data))
render()
})
window.addEventListener("DOMContentLoaded", () => {
var container = document.getElementById("container");
vnode = patch(container, view(data));
render();
});

@ -16,17 +16,18 @@
display: inline-block;
cursor: pointer;
background: #fff;
box-shadow: 0 0 1px rgba(0, 0, 0, .2);
padding: .5em .8em;
transition: box-shadow .05s ease-in-out;
-webkit-transition: box-shadow .05s ease-in-out;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
padding: 0.5em 0.8em;
transition: box-shadow 0.05s ease-in-out;
-webkit-transition: box-shadow 0.05s ease-in-out;
}
.btn:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, .2);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.btn:active, .active, .active:hover {
box-shadow: 0 0 1px rgba(0, 0, 0, .2),
inset 0 0 4px rgba(0, 0, 0, .1);
.btn:active,
.active,
.active:hover {
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2), inset 0 0 4px rgba(0, 0, 0, 0.1);
}
.add {
float: right;
@ -44,12 +45,14 @@
box-sizing: border-box;
width: 100%;
left: 0px;
margin: .5em 0;
margin: 0.5em 0;
padding: 1em;
background: #fff;
box-shadow: 0 0 1px rgba(0, 0, 0, .2);
transition: transform .5s ease-in-out, opacity .5s ease-out, left .5s ease-in-out;
-webkit-transition: transform .5s ease-in-out, opacity .5s ease-out, left .5s ease-in-out;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
transition: transform 0.5s ease-in-out, opacity 0.5s ease-out,
left 0.5s ease-in-out;
-webkit-transition: transform 0.5s ease-in-out, opacity 0.5s ease-out,
left 0.5s ease-in-out;
}
.row div {
display: inline-block;
@ -69,7 +72,7 @@
position: absolute;
top: 0;
right: 0;
color: #C25151;
color: #c25151;
width: 1.4em;
height: 1.4em;
text-align: center;

@ -1,15 +1,24 @@
import { init } from '../../build/package/init.js'
import { attributesModule } from '../../build/package/modules/attributes.js'
import { h } from '../../build/package/h.js'
import { init } from "../../build/package/init.js";
import { attributesModule } from "../../build/package/modules/attributes.js";
import { h } from "../../build/package/h.js";
var patch = init([attributesModule])
var patch = init([attributesModule]);
window.addEventListener('DOMContentLoaded', () => {
var container = document.getElementById('container')
var vnode = h('div', [
h('svg', { attrs: { width: 100, height: 100 } }, [
h('circle', { attrs: { cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow' } })
])
])
patch(container, vnode)
})
window.addEventListener("DOMContentLoaded", () => {
var container = document.getElementById("container");
var vnode = h("div", [
h("svg", { attrs: { width: 100, height: 100 } }, [
h("circle", {
attrs: {
cx: 50,
cy: 50,
r: 40,
stroke: "green",
"stroke-width": 4,
fill: "yellow",
},
}),
]),
]);
patch(container, vnode);
});

@ -1,48 +1,50 @@
const chalk = require('chalk')
const Table = require('tty-table')
const chalk = require("chalk");
const Table = require("tty-table");
exports['reporter:benchmark'] = ['type', BenchmarkReporter]
exports["reporter:benchmark"] = ["type", BenchmarkReporter];
function BenchmarkReporter (baseReporterDecorator) {
baseReporterDecorator(this)
const resultsPerBrowser = new Map()
function BenchmarkReporter(baseReporterDecorator) {
baseReporterDecorator(this);
const resultsPerBrowser = new Map();
this.onBrowserInfo = function (browser, info) {
if (!info.benchmark) return
if (!info.benchmark) return;
if (!resultsPerBrowser.has(browser.name)) {
resultsPerBrowser.set(browser.name, info.benchmark)
resultsPerBrowser.set(browser.name, info.benchmark);
}
}
};
this.onRunComplete = function () {
if (resultsPerBrowser.size === 0) return
this.writeCommonMsg(chalk.underline.bold('\nBENCHMARK (times in seconds):\n'))
if (resultsPerBrowser.size === 0) return;
this.writeCommonMsg(
chalk.underline.bold("\nBENCHMARK (times in seconds):\n")
);
resultsPerBrowser.forEach((results, browserName) => {
this.writeCommonMsg(` ${chalk.bold(browserName)}:\n`)
this.writeCommonMsg(` ${chalk.bold(browserName)}:\n`);
const rows = results.map(({ cur, ref }, i) => ({
i: String(i),
cur: cur.toFixed(0),
ref: ref.toFixed(0),
diff: `${(cur / ref * 100).toFixed(2)}%`,
}))
diff: `${((cur / ref) * 100).toFixed(2)}%`,
}));
const header = [
{
value: 'i',
align: 'right'
value: "i",
align: "right",
},
{
value: 'ref',
align: 'right'
value: "ref",
align: "right",
},
{
value: 'cur',
align: 'right'
value: "cur",
align: "right",
},
{
value: 'diff',
align: 'right'
value: "diff",
align: "right",
},
]
console.log(Table(header, rows).render())
})
resultsPerBrowser.clear()
}
];
console.log(Table(header, rows).render());
});
resultsPerBrowser.clear();
};
}

@ -1,61 +1,58 @@
const ci = !!process.env.CI
const watch = !!process.env.WATCH
const live = !!process.env.LIVE
const ci = !!process.env.CI;
const watch = !!process.env.WATCH;
const live = !!process.env.LIVE;
const ip = 'bs-local.com'
const ip = "bs-local.com";
const browserstack = require('./browserstack-karma.cjs')
const browserstack = require("./browserstack-karma.cjs");
// https://www.browserstack.com/open-source (text search "parallels")
const BROWSERSTACK_OPEN_SOURCE_CONCURRENCY = 5
const BROWSERSTACK_OPEN_SOURCE_CONCURRENCY = 5;
const browsers = ci
? Object.keys(browserstack)
: live
? undefined
: watch
? ['Chrome']
: ['ChromeHeadless', 'FirefoxHeadless']
? undefined
: watch
? ["Chrome"]
: ["ChromeHeadless", "FirefoxHeadless"];
module.exports = function (config) {
config.set({
basePath: '.',
frameworks: ['mocha', 'karma-typescript'],
basePath: ".",
frameworks: ["mocha", "karma-typescript"],
// list of files / patterns to load in the browser
files: [
{ pattern: 'src/**/*.ts' },
{ pattern: process.env.FILES_PATTERN },
],
files: [{ pattern: "src/**/*.ts" }, { pattern: process.env.FILES_PATTERN }],
preprocessors: {
'**/*.ts': 'karma-typescript'
"**/*.ts": "karma-typescript",
},
plugins: [
'karma-mocha',
'karma-typescript',
require('karma-mocha-reporter'),
require('./karma-benchmark-reporter.cjs'),
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-browserstack-launcher',
"karma-mocha",
"karma-typescript",
require("karma-mocha-reporter"),
require("./karma-benchmark-reporter.cjs"),
"karma-chrome-launcher",
"karma-firefox-launcher",
"karma-browserstack-launcher",
],
hostname: ci ? ip : 'localhost',
hostname: ci ? ip : "localhost",
karmaTypescriptConfig: {
compilerOptions: {
esModuleInterop: true
esModuleInterop: true,
},
include: [process.env.FILES_PATTERN, 'src/**/*.ts']
include: [process.env.FILES_PATTERN, "src/**/*.ts"],
},
browserStack: {
name: 'Snabbdom',
name: "Snabbdom",
retryLimit: 1,
},
client: {
captureConsole: true,
},
customLaunchers: browserstack,
reporters: ['karma-typescript', 'mocha', 'benchmark', 'BrowserStack'],
reporters: ["karma-typescript", "mocha", "benchmark", "BrowserStack"],
mochaReporter: {
showDiff: true
showDiff: true,
},
port: 9876,
colors: true,
@ -63,5 +60,5 @@ module.exports = function (config) {
browsers: browsers,
singleRun: !watch && !live,
concurrency: ci ? BROWSERSTACK_OPEN_SOURCE_CONCURRENCY : Infinity,
})
}
});
};

@ -1,45 +1,48 @@
// Has not been maintained for a while.
/* eslint-disable */
var Benchmark = require('benchmark');
var a = require('../snabbdom.js');
var b = require('../oldsnabbdom.js');
var Benchmark = require("benchmark");
var a = require("../snabbdom.js");
var b = require("../oldsnabbdom.js");
global.a = a;
global.b = b;
var suite = new Benchmark.Suite();
a.spanNum = function spanNum (n) {
return a.h('span', { key: n }, n.toString());
a.spanNum = function spanNum(n) {
return a.h("span", { key: n }, n.toString());
};
b.spanNum = function spanNum (n) {
return b.h('span', { key: n }, n.toString());
b.spanNum = function spanNum(n) {
return b.h("span", { key: n }, n.toString());
};
var elms = global.elms = 10;
var arr = global.arr = [];
for (var n = 0; n < elms; ++n) { arr[n] = n; }
var elms = (global.elms = 10);
var arr = (global.arr = []);
for (var n = 0; n < elms; ++n) {
arr[n] = n;
}
document.addEventListener('DOMContentLoaded', function () {
var elm = global.elm = document.getElementById('container');
document.addEventListener("DOMContentLoaded", function () {
var elm = (global.elm = document.getElementById("container"));
// add tests
suite.add('a/ insert first', {
setup: function () {
var vnode1 = a.h('div', arr.map(a.spanNum));
var vnode2 = a.h('div', ['new'].concat(arr).map(a.spanNum));
},
fn: function () {
var emptyNode = a.emptyNodeAt(elm);
a.patch(emptyNode, vnode1);
a.patch(vnode1, vnode2);
a.patch(vnode2, a.emptyNode);
},
})
.add('b/ insert first', {
suite
.add("a/ insert first", {
setup: function () {
var vnode1 = b.h('div', arr.map(b.spanNum));
var vnode2 = b.h('div', ['new'].concat(arr).map(b.spanNum));
var vnode1 = a.h("div", arr.map(a.spanNum));
var vnode2 = a.h("div", ["new"].concat(arr).map(a.spanNum));
},
fn: function () {
var emptyNode = a.emptyNodeAt(elm);
a.patch(emptyNode, vnode1);
a.patch(vnode1, vnode2);
a.patch(vnode2, a.emptyNode);
},
})
.add("b/ insert first", {
setup: function () {
var vnode1 = b.h("div", arr.map(b.spanNum));
var vnode2 = b.h("div", ["new"].concat(arr).map(b.spanNum));
},
fn: function () {
var emptyNode = b.emptyNodeAt(elm);
@ -48,13 +51,13 @@ document.addEventListener('DOMContentLoaded', function () {
b.patch(vnode2, b.emptyNode);
},
})
// add listeners
.on('cycle', function (event) {
// add listeners
.on("cycle", function (event) {
console.log(String(event.target));
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
.on("complete", function () {
console.log("Fastest is " + this.filter("fastest").pluck("name"));
})
// run async
// run async
.run({ async: true });
});

@ -1,62 +1,85 @@
import { vnode, VNode, VNodeData } from './vnode'
import * as is from './is'
import { vnode, VNode, VNodeData } from "./vnode";
import * as is from "./is";
export type VNodes = VNode[]
export type VNodeChildElement = VNode | string | number | undefined | null
export type ArrayOrElement<T> = T | T[]
export type VNodeChildren = ArrayOrElement<VNodeChildElement>
export type VNodes = VNode[];
export type VNodeChildElement = VNode | string | number | undefined | null;
export type ArrayOrElement<T> = T | T[];
export type VNodeChildren = ArrayOrElement<VNodeChildElement>;
function addNS (data: any, children: VNodes | undefined, sel: string | undefined): void {
data.ns = 'http://www.w3.org/2000/svg'
if (sel !== 'foreignObject' && children !== undefined) {
function addNS(
data: any,
children: VNodes | undefined,
sel: string | undefined
): void {
data.ns = "http://www.w3.org/2000/svg";
if (sel !== "foreignObject" && children !== undefined) {
for (let i = 0; i < children.length; ++i) {
const childData = children[i].data
const childData = children[i].data;
if (childData !== undefined) {
addNS(childData, (children[i] as VNode).children as VNodes, children[i].sel)
addNS(
childData,
(children[i] as VNode).children as VNodes,
children[i].sel
);
}
}
}
}
export function h (sel: string): VNode
export function h (sel: string, data: VNodeData | null): VNode
export function h (sel: string, children: VNodeChildren): VNode
export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode
export function h (sel: any, b?: any, c?: any): VNode {
let data: VNodeData = {}
let children: any
let text: any
let i: number
export function h(sel: string): VNode;
export function h(sel: string, data: VNodeData | null): VNode;
export function h(sel: string, children: VNodeChildren): VNode;
export function h(
sel: string,
data: VNodeData | null,
children: VNodeChildren
): VNode;
export function h(sel: any, b?: any, c?: any): VNode {
let data: VNodeData = {};
let children: any;
let text: any;
let i: number;
if (c !== undefined) {
if (b !== null) {
data = b
data = b;
}
if (is.array(c)) {
children = c
children = c;
} else if (is.primitive(c)) {
text = c
text = c;
} else if (c && c.sel) {
children = [c]
children = [c];
}
} else if (b !== undefined && b !== null) {
if (is.array(b)) {
children = b
children = b;
} else if (is.primitive(b)) {
text = b
text = b;
} else if (b && b.sel) {
children = [b]
} else { data = b }
children = [b];
} else {
data = b;
}
}
if (children !== undefined) {
for (i = 0; i < children.length; ++i) {
if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined)
if (is.primitive(children[i]))
children[i] = vnode(
undefined,
undefined,
undefined,
children[i],
undefined
);
}
}
if (
sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')
sel[0] === "s" &&
sel[1] === "v" &&
sel[2] === "g" &&
(sel.length === 3 || sel[3] === "." || sel[3] === "#")
) {
addNS(data, children, sel)
addNS(data, children, sel);
}
return vnode(sel, data, children, text, undefined)
};
return vnode(sel, data, children, text, undefined);
}

@ -1,64 +1,64 @@
import { VNode, VNodeData } from '../vnode'
import { VNode, VNodeData } from "../vnode";
export interface AttachData {
[key: string]: any
[i: number]: any
placeholder?: any
real?: Node
[key: string]: any;
[i: number]: any;
placeholder?: any;
real?: Node;
}
interface VNodeDataWithAttach extends VNodeData {
attachData: AttachData
attachData: AttachData;
}
interface VNodeWithAttachData extends VNode {
data: VNodeDataWithAttach
data: VNodeDataWithAttach;
}
function pre (vnode: VNodeWithAttachData, newVnode: VNodeWithAttachData): void {
const attachData = vnode.data.attachData
function pre(vnode: VNodeWithAttachData, newVnode: VNodeWithAttachData): void {
const attachData = vnode.data.attachData;
// Copy created placeholder and real element from old vnode
newVnode.data.attachData.placeholder = attachData.placeholder
newVnode.data.attachData.real = attachData.real
newVnode.data.attachData.placeholder = attachData.placeholder;
newVnode.data.attachData.real = attachData.real;
// Mount real element in vnode so the patch process operates on it
vnode.elm = vnode.data.attachData.real
vnode.elm = vnode.data.attachData.real;
}
function post (_: any, vnode: VNodeWithAttachData): void {
function post(_: any, vnode: VNodeWithAttachData): void {
// Mount dummy placeholder in vnode so potential reorders use it
vnode.elm = vnode.data.attachData.placeholder
vnode.elm = vnode.data.attachData.placeholder;
}
function destroy (vnode: VNodeWithAttachData): void {
function destroy(vnode: VNodeWithAttachData): void {
// Remove placeholder
if (vnode.elm !== undefined) {
(vnode.elm.parentNode as HTMLElement).removeChild(vnode.elm)
(vnode.elm.parentNode as HTMLElement).removeChild(vnode.elm);
}
// Remove real element from where it was inserted
vnode.elm = vnode.data.attachData.real
vnode.elm = vnode.data.attachData.real;
}
function create (_: any, vnode: VNodeWithAttachData): void {
const real = vnode.elm
const attachData = vnode.data.attachData
const placeholder = document.createElement('span')
function create(_: any, vnode: VNodeWithAttachData): void {
const real = vnode.elm;
const attachData = vnode.data.attachData;
const placeholder = document.createElement("span");
// Replace actual element with dummy placeholder
// Snabbdom will then insert placeholder instead
vnode.elm = placeholder
attachData.target.appendChild(real)
attachData.real = real
attachData.placeholder = placeholder
vnode.elm = placeholder;
attachData.target.appendChild(real);
attachData.real = real;
attachData.placeholder = placeholder;
}
export function attachTo (target: Element, vnode: VNode): VNode {
if (vnode.data === undefined) vnode.data = {}
if (vnode.data.hook === undefined) vnode.data.hook = {}
const data = vnode.data
const hook = vnode.data.hook
data.attachData = { target: target, placeholder: undefined, real: undefined }
hook.create = create
hook.prepatch = pre
hook.postpatch = post
hook.destroy = destroy
return vnode
};
export function attachTo(target: Element, vnode: VNode): VNode {
if (vnode.data === undefined) vnode.data = {};
if (vnode.data.hook === undefined) vnode.data.hook = {};
const data = vnode.data;
const hook = vnode.data.hook;
data.attachData = { target: target, placeholder: undefined, real: undefined };
hook.create = create;
hook.prepatch = pre;
hook.postpatch = post;
hook.destroy = destroy;
return vnode;
}

@ -1,25 +1,25 @@
import { VNode } from './vnode'
import { VNode } from "./vnode";
export type PreHook = () => any
export type InitHook = (vNode: VNode) => any
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any
export type InsertHook = (vNode: VNode) => any
export type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any
export type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any
export type DestroyHook = (vNode: VNode) => any
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any
export type PostHook = () => any
export type PreHook = () => any;
export type InitHook = (vNode: VNode) => any;
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any;
export type InsertHook = (vNode: VNode) => any;
export type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any;
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any;
export type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any;
export type DestroyHook = (vNode: VNode) => any;
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any;
export type PostHook = () => any;
export interface Hooks {
pre?: PreHook
init?: InitHook
create?: CreateHook
insert?: InsertHook
prepatch?: PrePatchHook
update?: UpdateHook
postpatch?: PostPatchHook
destroy?: DestroyHook
remove?: RemoveHook
post?: PostHook
pre?: PreHook;
init?: InitHook;
create?: CreateHook;
insert?: InsertHook;
prepatch?: PrePatchHook;
update?: UpdateHook;
postpatch?: PostPatchHook;
destroy?: DestroyHook;
remove?: RemoveHook;
post?: PostHook;
}

@ -1,79 +1,101 @@
export interface DOMAPI {
createElement: (tagName: any, options?: ElementCreationOptions) => HTMLElement
createElementNS: (namespaceURI: string, qualifiedName: string, options?: ElementCreationOptions) => Element
createTextNode: (text: string) => Text
createComment: (text: string) => Comment
insertBefore: (parentNode: Node, newNode: Node, referenceNode: Node | null) => void
removeChild: (node: Node, child: Node) => void
appendChild: (node: Node, child: Node) => void
parentNode: (node: Node) => Node | null
nextSibling: (node: Node) => Node | null
tagName: (elm: Element) => string
setTextContent: (node: Node, text: string | null) => void
getTextContent: (node: Node) => string | null
isElement: (node: Node) => node is Element
isText: (node: Node) => node is Text
isComment: (node: Node) => node is Comment
createElement: (
tagName: any,
options?: ElementCreationOptions
) => HTMLElement;
createElementNS: (
namespaceURI: string,
qualifiedName: string,
options?: ElementCreationOptions
) => Element;
createTextNode: (text: string) => Text;
createComment: (text: string) => Comment;
insertBefore: (
parentNode: Node,
newNode: Node,
referenceNode: Node | null
) => void;
removeChild: (node: Node, child: Node) => void;
appendChild: (node: Node, child: Node) => void;
parentNode: (node: Node) => Node | null;
nextSibling: (node: Node) => Node | null;
tagName: (elm: Element) => string;
setTextContent: (node: Node, text: string | null) => void;
getTextContent: (node: Node) => string | null;
isElement: (node: Node) => node is Element;
isText: (node: Node) => node is Text;
isComment: (node: Node) => node is Comment;
}
function createElement (tagName: any, options?: ElementCreationOptions): HTMLElement {
return document.createElement(tagName, options)
function createElement(
tagName: any,
options?: ElementCreationOptions
): HTMLElement {
return document.createElement(tagName, options);
}
function createElementNS (namespaceURI: string, qualifiedName: string, options?: ElementCreationOptions): Element {
return document.createElementNS(namespaceURI, qualifiedName, options)
function createElementNS(
namespaceURI: string,
qualifiedName: string,
options?: ElementCreationOptions
): Element {
return document.createElementNS(namespaceURI, qualifiedName, options);
}
function createTextNode (text: string): Text {
return document.createTextNode(text)
function createTextNode(text: string): Text {
return document.createTextNode(text);
}
function createComment (text: string): Comment {
return document.createComment(text)
function createComment(text: string): Comment {
return document.createComment(text);
}
function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node | null): void {
parentNode.insertBefore(newNode, referenceNode)
function insertBefore(
parentNode: Node,
newNode: Node,
referenceNode: Node | null
): void {
parentNode.insertBefore(newNode, referenceNode);
}
function removeChild (node: Node, child: Node): void {
node.removeChild(child)
function removeChild(node: Node, child: Node): void {
node.removeChild(child);
}
function appendChild (node: Node, child: Node): void {
node.appendChild(child)
function appendChild(node: Node, child: Node): void {
node.appendChild(child);
}
function parentNode (node: Node): Node | null {
return node.parentNode
function parentNode(node: Node): Node | null {
return node.parentNode;
}
function nextSibling (node: Node): Node | null {
return node.nextSibling
function nextSibling(node: Node): Node | null {
return node.nextSibling;
}
function tagName (elm: Element): string {
return elm.tagName
function tagName(elm: Element): string {
return elm.tagName;
}
function setTextContent (node: Node, text: string | null): void {
node.textContent = text
function setTextContent(node: Node, text: string | null): void {
node.textContent = text;
}
function getTextContent (node: Node): string | null {
return node.textContent
function getTextContent(node: Node): string | null {
return node.textContent;
}
function isElement (node: Node): node is Element {
return node.nodeType === 1
function isElement(node: Node): node is Element {
return node.nodeType === 1;
}
function isText (node: Node): node is Text {
return node.nodeType === 3
function isText(node: Node): node is Text {
return node.nodeType === 3;
}
function isComment (node: Node): node is Comment {
return node.nodeType === 8
function isComment(node: Node): node is Comment {
return node.nodeType === 8;
}
export const htmlDomApi: DOMAPI = {
@ -92,4 +114,4 @@ export const htmlDomApi: DOMAPI = {
isElement,
isText,
isComment,
}
};

@ -1,30 +1,30 @@
// core
export { DOMAPI, htmlDomApi } from './htmldomapi';
export { init } from './init';
export { ThunkData, Thunk, ThunkFn, thunk } from './thunk';
export { Key, VNode, VNodeData, vnode } from './vnode';
export { DOMAPI, htmlDomApi } from "./htmldomapi";
export { init } from "./init";
export { ThunkData, Thunk, ThunkFn, thunk } from "./thunk";
export { Key, VNode, VNodeData, vnode } from "./vnode";
// helpers
export { AttachData, attachTo } from './helpers/attachto';
export { array, primitive } from './is';
export { toVNode } from './tovnode';
export { AttachData, attachTo } from "./helpers/attachto";
export { array, primitive } from "./is";
export { toVNode } from "./tovnode";
export {
VNodes,
VNodeChildElement,
ArrayOrElement,
VNodeChildren,
h,
} from './h';
} from "./h";
// types
export * from './hooks';
export { Module } from './modules/module';
export * from "./hooks";
export { Module } from "./modules/module";
// modules
export { Attrs, attributesModule } from './modules/attributes';
export { Classes, classModule } from './modules/class';
export { Dataset, datasetModule } from './modules/dataset';
export { On, eventListenersModule } from './modules/eventlisteners';
export { Hero, heroModule } from './modules/hero';
export { Props, propsModule } from './modules/props';
export { VNodeStyle, styleModule } from './modules/style';
export { Attrs, attributesModule } from "./modules/attributes";
export { Classes, classModule } from "./modules/class";
export { Dataset, datasetModule } from "./modules/dataset";
export { On, eventListenersModule } from "./modules/eventlisteners";
export { Hero, heroModule } from "./modules/hero";
export { Props, propsModule } from "./modules/props";
export { VNodeStyle, styleModule } from "./modules/style";

@ -1,147 +1,169 @@
import { Module } from './modules/module'
import { vnode, VNode } from './vnode'
import * as is from './is'
import { htmlDomApi, DOMAPI } from './htmldomapi'
import { Module } from "./modules/module";
import { vnode, VNode } from "./vnode";
import * as is from "./is";
import { htmlDomApi, DOMAPI } from "./htmldomapi";
type NonUndefined<T> = T extends undefined ? never : T
type NonUndefined<T> = T extends undefined ? never : T;
function isUndef (s: any): boolean {
return s === undefined
function isUndef(s: any): boolean {
return s === undefined;
}
function isDef<A> (s: A): s is NonUndefined<A> {
return s !== undefined
function isDef<A>(s: A): s is NonUndefined<A> {
return s !== undefined;
}
type VNodeQueue = VNode[]
type VNodeQueue = VNode[];
const emptyNode = vnode('', {}, [], undefined, undefined)
const emptyNode = vnode("", {}, [], undefined, undefined);
function sameVnode (vnode1: VNode, vnode2: VNode): boolean {
const isSameKey = vnode1.key === vnode2.key
const isSameIs = vnode1.data?.is === vnode2.data?.is
const isSameSel = vnode1.sel === vnode2.sel
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
const isSameKey = vnode1.key === vnode2.key;
const isSameIs = vnode1.data?.is === vnode2.data?.is;
const isSameSel = vnode1.sel === vnode2.sel;
return isSameSel && isSameKey && isSameIs
return isSameSel && isSameKey && isSameIs;
}
function isVnode (vnode: any): vnode is VNode {
return vnode.sel !== undefined
function isVnode(vnode: any): vnode is VNode {
return vnode.sel !== undefined;
}
type KeyToIndexMap = {[key: string]: number}
type KeyToIndexMap = { [key: string]: number };
type ArraysOf<T> = {
[K in keyof T]: Array<T[K]>;
}
};
type ModuleHooks = ArraysOf<Required<Module>>
type ModuleHooks = ArraysOf<Required<Module>>;
function createKeyToOldIdx (children: VNode[], beginIdx: number, endIdx: number): KeyToIndexMap {
const map: KeyToIndexMap = {}
function createKeyToOldIdx(
children: VNode[],
beginIdx: number,
endIdx: number
): KeyToIndexMap {
const map: KeyToIndexMap = {};
for (let i = beginIdx; i <= endIdx; ++i) {
const key = children[i]?.key
const key = children[i]?.key;
if (key !== undefined) {
map[key] = i
map[key] = i;
}
}
return map
return map;
}
const hooks: Array<keyof Module> = ['create', 'update', 'remove', 'destroy', 'pre', 'post']
const hooks: Array<keyof Module> = [
"create",
"update",
"remove",
"destroy",
"pre",
"post",
];
export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
let i: number
let j: number
export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
let i: number;
let j: number;
const cbs: ModuleHooks = {
create: [],
update: [],
remove: [],
destroy: [],
pre: [],
post: []
}
post: [],
};
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
cbs[hooks[i]] = [];
for (j = 0; j < modules.length; ++j) {
const hook = modules[j][hooks[i]]
const hook = modules[j][hooks[i]];
if (hook !== undefined) {
(cbs[hooks[i]] as any[]).push(hook)
(cbs[hooks[i]] as any[]).push(hook);
}
}
}
function emptyNodeAt (elm: Element) {
const id = elm.id ? '#' + elm.id : ''
const c = elm.className ? '.' + elm.className.split(' ').join('.') : ''
return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm)
function emptyNodeAt(elm: Element) {
const id = elm.id ? "#" + elm.id : "";
const c = elm.className ? "." + elm.className.split(" ").join(".") : "";
return vnode(
api.tagName(elm).toLowerCase() + id + c,
{},
[],
undefined,
elm
);
}
function createRmCb (childElm: Node, listeners: number) {
return function rmCb () {
function createRmCb(childElm: Node, listeners: number) {
return function rmCb() {
if (--listeners === 0) {
const parent = api.parentNode(childElm) as Node
api.removeChild(parent, childElm)
const parent = api.parentNode(childElm) as Node;
api.removeChild(parent, childElm);
}
}
};
}
function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any
let data = vnode.data
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any;
let data = vnode.data;
if (data !== undefined) {
const init = data.hook?.init
const init = data.hook?.init;
if (isDef(init)) {
init(vnode)
data = vnode.data
init(vnode);
data = vnode.data;
}
}
const children = vnode.children
const sel = vnode.sel
if (sel === '!') {
const children = vnode.children;
const sel = vnode.sel;
if (sel === "!") {
if (isUndef(vnode.text)) {
vnode.text = ''
vnode.text = "";
}
vnode.elm = api.createComment(vnode.text!)
vnode.elm = api.createComment(vnode.text!);
} else if (sel !== undefined) {
// Parse selector
const hashIdx = sel.indexOf('#')
const dotIdx = sel.indexOf('.', hashIdx)
const hash = hashIdx > 0 ? hashIdx : sel.length
const dot = dotIdx > 0 ? dotIdx : sel.length
const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel
const elm = vnode.elm = isDef(data) && isDef(i = data.ns)
? api.createElementNS(i, tag, data)
: api.createElement(tag, data)
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot))
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '))
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
const hashIdx = sel.indexOf("#");
const dotIdx = sel.indexOf(".", hashIdx);
const hash = hashIdx > 0 ? hashIdx : sel.length;
const dot = dotIdx > 0 ? dotIdx : sel.length;
const tag =
hashIdx !== -1 || dotIdx !== -1
? sel.slice(0, Math.min(hash, dot))
: sel;
const elm = (vnode.elm =
isDef(data) && isDef((i = data.ns))
? api.createElementNS(i, tag, data)
: api.createElement(tag, data));
if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot));
if (dotIdx > 0)
elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " "));
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i]
const ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue))
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
}
}
} else if (is.primitive(vnode.text)) {
api.appendChild(elm, api.createTextNode(vnode.text))
api.appendChild(elm, api.createTextNode(vnode.text));
}
const hook = vnode.data!.hook
const hook = vnode.data!.hook;
if (isDef(hook)) {
hook.create?.(emptyNode, vnode)
hook.create?.(emptyNode, vnode);
if (hook.insert) {
insertedVnodeQueue.push(vnode)
insertedVnodeQueue.push(vnode);
}
}
} else {
vnode.elm = api.createTextNode(vnode.text!)
vnode.elm = api.createTextNode(vnode.text!);
}
return vnode.elm
return vnode.elm;
}
function addVnodes (
function addVnodes(
parentElm: Node,
before: Node | null,
vnodes: VNode[],
@ -150,188 +172,220 @@ export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
insertedVnodeQueue: VNodeQueue
) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
const ch = vnodes[startIdx];
if (ch != null) {
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before)
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
}
}
}
function invokeDestroyHook (vnode: VNode) {
const data = vnode.data
function invokeDestroyHook(vnode: VNode) {
const data = vnode.data;
if (data !== undefined) {
data?.hook?.destroy?.(vnode)
for (let i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
data?.hook?.destroy?.(vnode);
for (let i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
if (vnode.children !== undefined) {
for (let j = 0; j < vnode.children.length; ++j) {
const child = vnode.children[j]
if (child != null && typeof child !== 'string') {
invokeDestroyHook(child)
const child = vnode.children[j];
if (child != null && typeof child !== "string") {
invokeDestroyHook(child);
}
}
}
}
}
function removeVnodes (parentElm: Node,
function removeVnodes(
parentElm: Node,
vnodes: VNode[],
startIdx: number,
endIdx: number): void {
endIdx: number
): void {
for (; startIdx <= endIdx; ++startIdx) {
let listeners: number
let rm: () => void
const ch = vnodes[startIdx]
let listeners: number;
let rm: () => void;
const ch = vnodes[startIdx];
if (ch != null) {
if (isDef(ch.sel)) {
invokeDestroyHook(ch)
listeners = cbs.remove.length + 1
rm = createRmCb(ch.elm!, listeners)
for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm)
const removeHook = ch?.data?.hook?.remove
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm!, listeners);
for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
const removeHook = ch?.data?.hook?.remove;
if (isDef(removeHook)) {
removeHook(ch, rm)
removeHook(ch, rm);
} else {
rm()
rm();
}
} else { // Text node
api.removeChild(parentElm, ch.elm!)
} else {
// Text node
api.removeChild(parentElm, ch.elm!);
}
}
}
}
function updateChildren (parentElm: Node,
function updateChildren(
parentElm: Node,
oldCh: VNode[],
newCh: VNode[],
insertedVnodeQueue: VNodeQueue) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx: KeyToIndexMap | undefined
let idxInOld: number
let elmToMove: VNode
let before: any
insertedVnodeQueue: VNodeQueue
) {
let oldStartIdx = 0;
let newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let oldKeyToIdx: KeyToIndexMap | undefined;
let idxInOld: number;
let elmToMove: VNode;
let before: any;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
} else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
oldEndVnode = oldCh[--oldEndIdx];
} else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
newStartVnode = newCh[++newStartIdx];
} else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
api.insertBefore(parentElm, oldStartVnode.elm!, api.nextSibling(oldEndVnode.elm!))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(
parentElm,
oldStartVnode.elm!,
api.nextSibling(oldEndVnode.elm!)
);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
idxInOld = oldKeyToIdx[newStartVnode.key as string]
if (isUndef(idxInOld)) { // New element
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
idxInOld = oldKeyToIdx[newStartVnode.key as string];
if (isUndef(idxInOld)) {
// New element
api.insertBefore(
parentElm,
createElm(newStartVnode, insertedVnodeQueue),
oldStartVnode.elm!
);
} else {
elmToMove = oldCh[idxInOld]
elmToMove = oldCh[idxInOld];
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
api.insertBefore(
parentElm,
createElm(newStartVnode, insertedVnodeQueue),
oldStartVnode.elm!
);
} else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined as any
api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!)
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined as any;
api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!);
}
}
newStartVnode = newCh[++newStartIdx]
newStartVnode = newCh[++newStartIdx];
}
}
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
addVnodes(
parentElm,
before,
newCh,
newStartIdx,
newEndIdx,
insertedVnodeQueue
);
} else {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
}
function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
const hook = vnode.data?.hook
hook?.prepatch?.(oldVnode, vnode)
const elm = vnode.elm = oldVnode.elm!
const oldCh = oldVnode.children as VNode[]
const ch = vnode.children as VNode[]
if (oldVnode === vnode) return
function patchVnode(
oldVnode: VNode,
vnode: VNode,
insertedVnodeQueue: VNodeQueue
) {
const hook = vnode.data?.hook;
hook?.prepatch?.(oldVnode, vnode);
const elm = (vnode.elm = oldVnode.elm!);
const oldCh = oldVnode.children as VNode[];
const ch = vnode.children as VNode[];
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
vnode.data.hook?.update?.(oldVnode, vnode)
for (let i = 0; i < cbs.update.length; ++i)
cbs.update[i](oldVnode, vnode);
vnode.data.hook?.update?.(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, '')
api.setTextContent(elm, "");
}
} else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
api.setTextContent(elm, vnode.text!)
api.setTextContent(elm, vnode.text!);
}
hook?.postpatch?.(oldVnode, vnode)
hook?.postpatch?.(oldVnode, vnode);
}
return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node
const insertedVnodeQueue: VNodeQueue = []
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node;
const insertedVnodeQueue: VNodeQueue = [];
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode)
oldVnode = emptyNodeAt(oldVnode);
}
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
patchVnode(oldVnode, vnode, insertedVnodeQueue);
} else {
elm = oldVnode.elm!
parent = api.parentNode(elm) as Node
elm = oldVnode.elm!;
parent = api.parentNode(elm) as Node;
createElm(vnode, insertedVnodeQueue)
createElm(vnode, insertedVnodeQueue);
if (parent !== null) {
api.insertBefore(parent, vnode.elm!, api.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
api.insertBefore(parent, vnode.elm!, api.nextSibling(elm));
removeVnodes(parent, [oldVnode], 0, 0);
}
}
for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i])
insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]);
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
return vnode;
};
}

@ -1,4 +1,4 @@
export const array = Array.isArray
export function primitive (s: any): s is (string | number) {
return typeof s === 'string' || typeof s === 'number'
export const array = Array.isArray;
export function primitive(s: any): s is string | number {
return typeof s === "string" || typeof s === "number";
}

@ -1,9 +1,9 @@
import { VNode as _VNode, VNodeData as _VNodeData } from './vnode'
import { VNode as _VNode, VNodeData as _VNodeData } from "./vnode";
// workaround
// https://github.com/typescript-eslint/typescript-eslint/issues/1596
type VNode = _VNode
type VNodeData = _VNodeData
type VNode = _VNode;
type VNodeData = _VNodeData;
/* eslint-disable @typescript-eslint/no-unused-vars */
declare global {
@ -13,9 +13,9 @@ declare global {
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
type Element = VNode
type Element = VNode;
interface IntrinsicElements {
[elemName: string]: VNodeData
[elemName: string]: VNodeData;
}
}
}

@ -1,43 +1,74 @@
import { vnode, VNode, VNodeData } from './vnode'
import { h, ArrayOrElement } from './h'
import { vnode, VNode, VNodeData } from "./vnode";
import { h, ArrayOrElement } from "./h";
// for conditional rendering we support boolean child element e.g cond && <tag />
export type JsxVNodeChild = VNode | string | number | boolean | undefined | null
export type JsxVNodeChildren = ArrayOrElement<JsxVNodeChild>
export type JsxVNodeChild =
| VNode
| string
| number
| boolean
| undefined
| null;
export type JsxVNodeChildren = ArrayOrElement<JsxVNodeChild>;
export type FunctionComponent = (props: {[prop: string]: any} | null, children?: VNode[]) => VNode
export type FunctionComponent = (
props: { [prop: string]: any } | null,
children?: VNode[]
) => VNode;
function flattenAndFilter (children: JsxVNodeChildren[], flattened: VNode[]): VNode[] {
function flattenAndFilter(
children: JsxVNodeChildren[],
flattened: VNode[]
): VNode[] {
for (const child of children) {
// filter out falsey children, except 0 since zero can be a valid value e.g inside a chart
if (child !== undefined && child !== null && child !== false && child !== '') {
if (
child !== undefined &&
child !== null &&
child !== false &&
child !== ""
) {
if (Array.isArray(child)) {
flattenAndFilter(child, flattened)
} else if (typeof child === 'string' || typeof child === 'number' || typeof child === 'boolean') {
flattened.push(vnode(undefined, undefined, undefined, String(child), undefined))
flattenAndFilter(child, flattened);
} else if (
typeof child === "string" ||
typeof child === "number" ||
typeof child === "boolean"
) {
flattened.push(
vnode(undefined, undefined, undefined, String(child), undefined)
);
} else {
flattened.push(child)
flattened.push(child);
}
}
}
return flattened
return flattened;
}
/**
* jsx/tsx compatible factory function
* see: https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions
*/
export function jsx (tag: string | FunctionComponent, data: VNodeData | null, ...children: JsxVNodeChildren[]): VNode {
const flatChildren = flattenAndFilter(children, [])
if (typeof tag === 'function') {
export function jsx(
tag: string | FunctionComponent,
data: VNodeData | null,
...children: JsxVNodeChildren[]
): VNode {
const flatChildren = flattenAndFilter(children, []);
if (typeof tag === "function") {
// tag is a function component
return tag(data, flatChildren)
return tag(data, flatChildren);
} else {
if (flatChildren.length === 1 && !flatChildren[0].sel && flatChildren[0].text) {
if (
flatChildren.length === 1 &&
!flatChildren[0].sel &&
flatChildren[0].text
) {
// only child is a simple text node, pass as text for a simpler vtree
return h(tag, data, flatChildren[0].text)
return h(tag, data, flatChildren[0].text);
} else {
return h(tag, data, flatChildren)
return h(tag, data, flatChildren);
}
}
}

@ -1,44 +1,44 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type Attrs = Record<string, string | number | boolean>
export type Attrs = Record<string, string | number | boolean>;
const xlinkNS = 'http://www.w3.org/1999/xlink'
const xmlNS = 'http://www.w3.org/XML/1998/namespace'
const colonChar = 58
const xChar = 120
const xlinkNS = "http://www.w3.org/1999/xlink";
const xmlNS = "http://www.w3.org/XML/1998/namespace";
const colonChar = 58;
const xChar = 120;
function updateAttrs (oldVnode: VNode, vnode: VNode): void {
let key: string
const elm: Element = vnode.elm as Element
let oldAttrs = (oldVnode.data as VNodeData).attrs
let attrs = (vnode.data as VNodeData).attrs
function updateAttrs(oldVnode: VNode, vnode: VNode): void {
let key: string;
const elm: Element = vnode.elm as Element;
let oldAttrs = (oldVnode.data as VNodeData).attrs;
let attrs = (vnode.data as VNodeData).attrs;
if (!oldAttrs && !attrs) return
if (oldAttrs === attrs) return
oldAttrs = oldAttrs || {}
attrs = attrs || {}
if (!oldAttrs && !attrs) return;
if (oldAttrs === attrs) return;
oldAttrs = oldAttrs || {};
attrs = attrs || {};
// update modified attributes, add new attributes
for (key in attrs) {
const cur = attrs[key]
const old = oldAttrs[key]
const cur = attrs[key];
const old = oldAttrs[key];
if (old !== cur) {
if (cur === true) {
elm.setAttribute(key, '')
elm.setAttribute(key, "");
} else if (cur === false) {
elm.removeAttribute(key)
elm.removeAttribute(key);
} else {
if (key.charCodeAt(0) !== xChar) {
elm.setAttribute(key, cur as any)
elm.setAttribute(key, cur as any);
} else if (key.charCodeAt(3) === colonChar) {
// Assume xml namespace
elm.setAttributeNS(xmlNS, key, cur as any)
elm.setAttributeNS(xmlNS, key, cur as any);
} else if (key.charCodeAt(5) === colonChar) {
// Assume xlink namespace
elm.setAttributeNS(xlinkNS, key, cur as any)
elm.setAttributeNS(xlinkNS, key, cur as any);
} else {
elm.setAttribute(key, cur as any)
elm.setAttribute(key, cur as any);
}
}
}
@ -48,9 +48,12 @@ function updateAttrs (oldVnode: VNode, vnode: VNode): void {
// the other option is to remove all attributes with value == undefined
for (key in oldAttrs) {
if (!(key in attrs)) {
elm.removeAttribute(key)
elm.removeAttribute(key);
}
}
}
export const attributesModule: Module = { create: updateAttrs, update: updateAttrs }
export const attributesModule: Module = {
create: updateAttrs,
update: updateAttrs,
};

@ -1,35 +1,32 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type Classes = Record<string, boolean>
export type Classes = Record<string, boolean>;
function updateClass (oldVnode: VNode, vnode: VNode): void {
let cur: any
let name: string
const elm: Element = vnode.elm as Element
let oldClass = (oldVnode.data as VNodeData).class
let klass = (vnode.data as VNodeData).class
function updateClass(oldVnode: VNode, vnode: VNode): void {
let cur: any;
let name: string;
const elm: Element = vnode.elm as Element;
let oldClass = (oldVnode.data as VNodeData).class;
let klass = (vnode.data as VNodeData).class;
if (!oldClass && !klass) return
if (oldClass === klass) return
oldClass = oldClass || {}
klass = klass || {}
if (!oldClass && !klass) return;
if (oldClass === klass) return;
oldClass = oldClass || {};
klass = klass || {};
for (name in oldClass) {
if (
oldClass[name] &&
!Object.prototype.hasOwnProperty.call(klass, name)
) {
if (oldClass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) {
// was `true` and now not provided
elm.classList.remove(name)
elm.classList.remove(name);
}
}
for (name in klass) {
cur = klass[name]
cur = klass[name];
if (cur !== oldClass[name]) {
(elm.classList as any)[cur ? 'add' : 'remove'](name)
(elm.classList as any)[cur ? "add" : "remove"](name);
}
}
}
export const classModule: Module = { create: updateClass, update: updateClass }
export const classModule: Module = { create: updateClass, update: updateClass };

@ -1,42 +1,50 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type Dataset = Record<string, string>
export type Dataset = Record<string, string>;
const CAPS_REGEX = /[A-Z]/g
const CAPS_REGEX = /[A-Z]/g;
function updateDataset (oldVnode: VNode, vnode: VNode): void {
const elm: HTMLElement = vnode.elm as HTMLElement
let oldDataset = (oldVnode.data as VNodeData).dataset
let dataset = (vnode.data as VNodeData).dataset
let key: string
function updateDataset(oldVnode: VNode, vnode: VNode): void {
const elm: HTMLElement = vnode.elm as HTMLElement;
let oldDataset = (oldVnode.data as VNodeData).dataset;
let dataset = (vnode.data as VNodeData).dataset;
let key: string;
if (!oldDataset && !dataset) return
if (oldDataset === dataset) return
oldDataset = oldDataset || {}
dataset = dataset || {}
const d = elm.dataset
if (!oldDataset && !dataset) return;
if (oldDataset === dataset) return;
oldDataset = oldDataset || {};
dataset = dataset || {};
const d = elm.dataset;
for (key in oldDataset) {
if (!dataset[key]) {
if (d) {
if (key in d) {
delete d[key]
delete d[key];
}
} else {
elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase())
elm.removeAttribute(
"data-" + key.replace(CAPS_REGEX, "-$&").toLowerCase()
);
}
}
}
for (key in dataset) {
if (oldDataset[key] !== dataset[key]) {
if (d) {
d[key] = dataset[key]
d[key] = dataset[key];
} else {
elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key])
elm.setAttribute(
"data-" + key.replace(CAPS_REGEX, "-$&").toLowerCase(),
dataset[key]
);
}
}
}
}
export const datasetModule: Module = { create: updateDataset, update: updateDataset }
export const datasetModule: Module = {
create: updateDataset,
update: updateDataset,
};

@ -1,55 +1,63 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
type Listener<T> = (this: VNode, ev: T, vnode: VNode) => void
type Listener<T> = (this: VNode, ev: T, vnode: VNode) => void;
export type On = {
[N in keyof HTMLElementEventMap]?: Listener<HTMLElementEventMap[N]> | Array<Listener<HTMLElementEventMap[N]>>
[N in keyof HTMLElementEventMap]?:
| Listener<HTMLElementEventMap[N]>
| Array<Listener<HTMLElementEventMap[N]>>;
} & {
[event: string]: Listener<any> | Array<Listener<any>>
}
[event: string]: Listener<any> | Array<Listener<any>>;
};
type SomeListener<N extends keyof HTMLElementEventMap> = Listener<HTMLElementEventMap[N]> | Listener<any>
type SomeListener<N extends keyof HTMLElementEventMap> =
| Listener<HTMLElementEventMap[N]>
| Listener<any>;
function invokeHandler<N extends keyof HTMLElementEventMap> (handler: SomeListener<N> | Array<SomeListener<N>>, vnode: VNode, event?: Event): void {
if (typeof handler === 'function') {
function invokeHandler<N extends keyof HTMLElementEventMap>(
handler: SomeListener<N> | Array<SomeListener<N>>,
vnode: VNode,
event?: Event
): void {
if (typeof handler === "function") {
// call function handler
handler.call(vnode, event, vnode)
} else if (typeof handler === 'object') {
handler.call(vnode, event, vnode);
} else if (typeof handler === "object") {
// call multiple handlers
for (let i = 0; i < handler.length; i++) {
invokeHandler(handler[i], vnode, event)
invokeHandler(handler[i], vnode, event);
}
}
}
function handleEvent (event: Event, vnode: VNode) {
const name = event.type
const on = (vnode.data as VNodeData).on
function handleEvent(event: Event, vnode: VNode) {
const name = event.type;
const on = (vnode.data as VNodeData).on;
// call event handler(s) if exists
if (on && on[name]) {
invokeHandler(on[name], vnode, event)
invokeHandler(on[name], vnode, event);
}
}
function createListener () {
return function handler (event: Event) {
handleEvent(event, (handler as any).vnode)
}
function createListener() {
return function handler(event: Event) {
handleEvent(event, (handler as any).vnode);
};
}
function updateEventListeners (oldVnode: VNode, vnode?: VNode): void {
const oldOn = (oldVnode.data as VNodeData).on
const oldListener = (oldVnode as any).listener
const oldElm: Element = oldVnode.elm as Element
const on = vnode && (vnode.data as VNodeData).on
const elm: Element = (vnode && vnode.elm) as Element
let name: string
function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
const oldOn = (oldVnode.data as VNodeData).on;
const oldListener = (oldVnode as any).listener;
const oldElm: Element = oldVnode.elm as Element;
const on = vnode && (vnode.data as VNodeData).on;
const elm: Element = (vnode && vnode.elm) as Element;
let name: string;
// optimization for reused immutable handlers
if (oldOn === on) {
return
return;
}
// remove existing listeners which no longer used
@ -58,13 +66,13 @@ function updateEventListeners (oldVnode: VNode, vnode?: VNode): void {
if (!on) {
for (name in oldOn) {
// remove listener if element was changed or existing listeners removed
oldElm.removeEventListener(name, oldListener, false)
oldElm.removeEventListener(name, oldListener, false);
}
} else {
for (name in oldOn) {
// remove listener if existing listener removed
if (!on[name]) {
oldElm.removeEventListener(name, oldListener, false)
oldElm.removeEventListener(name, oldListener, false);
}
}
}
@ -73,21 +81,22 @@ function updateEventListeners (oldVnode: VNode, vnode?: VNode): void {
// add new listeners which has not already attached
if (on) {
// reuse existing listener or create new
const listener = (vnode as any).listener = (oldVnode as any).listener || createListener()
const listener = ((vnode as any).listener =
(oldVnode as any).listener || createListener());
// update vnode for listener
listener.vnode = vnode
listener.vnode = vnode;
// if element changed or added we add all needed listeners unconditionally
if (!oldOn) {
for (name in on) {
// add listener if element was changed or new listeners added
elm.addEventListener(name, listener, false)
elm.addEventListener(name, listener, false);
}
} else {
for (name in on) {
// add listener if new listener added
if (!oldOn[name]) {
elm.addEventListener(name, listener, false)
elm.addEventListener(name, listener, false);
}
}
}
@ -97,5 +106,5 @@ function updateEventListeners (oldVnode: VNode, vnode?: VNode): void {
export const eventListenersModule: Module = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners
}
destroy: updateEventListeners,
};

@ -1,34 +1,35 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type Hero = { id: string }
export type Hero = { id: string };
const raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout
const raf =
(typeof window !== "undefined" && window.requestAnimationFrame) || setTimeout;
const nextFrame = function (fn: any) {
raf(function () {
raf(fn)
})
}
raf(fn);
});
};
function setNextFrame (obj: any, prop: string, val: any): void {
function setNextFrame(obj: any, prop: string, val: any): void {
nextFrame(function () {
obj[prop] = val
})
obj[prop] = val;
});
}
function getTextNodeRect (textNode: Text): ClientRect | undefined {
let rect: ClientRect | undefined
function getTextNodeRect(textNode: Text): ClientRect | undefined {
let rect: ClientRect | undefined;
if (document.createRange) {
const range = document.createRange()
range.selectNodeContents(textNode)
const range = document.createRange();
range.selectNodeContents(textNode);
if (range.getBoundingClientRect) {
rect = range.getBoundingClientRect()
rect = range.getBoundingClientRect();
}
}
return rect
return rect;
}
function calcTransformOrigin (
function calcTransformOrigin(
isTextNode: boolean,
textRect: ClientRect | undefined,
boundingRect: ClientRect
@ -36,147 +37,201 @@ function calcTransformOrigin (
if (isTextNode) {
if (textRect) {
// calculate pixels to center of text from left edge of bounding box
const relativeCenterX = textRect.left + textRect.width / 2 - boundingRect.left
const relativeCenterY = textRect.top + textRect.height / 2 - boundingRect.top
return relativeCenterX + 'px ' + relativeCenterY + 'px'
const relativeCenterX =
textRect.left + textRect.width / 2 - boundingRect.left;
const relativeCenterY =
textRect.top + textRect.height / 2 - boundingRect.top;
return relativeCenterX + "px " + relativeCenterY + "px";
}
}
return '0 0' // top left
return "0 0"; // top left
}
function getTextDx (
function getTextDx(
oldTextRect: ClientRect | undefined,
newTextRect: ClientRect | undefined
): number {
if (oldTextRect && newTextRect) {
return ((oldTextRect.left + oldTextRect.width / 2) - (newTextRect.left + newTextRect.width / 2))
return (
oldTextRect.left +
oldTextRect.width / 2 -
(newTextRect.left + newTextRect.width / 2)
);
}
return 0
return 0;
}
function getTextDy (
function getTextDy(
oldTextRect: ClientRect | undefined,
newTextRect: ClientRect | undefined
): number {
if (oldTextRect && newTextRect) {
return ((oldTextRect.top + oldTextRect.height / 2) - (newTextRect.top + newTextRect.height / 2))
return (
oldTextRect.top +
oldTextRect.height / 2 -
(newTextRect.top + newTextRect.height / 2)
);
}
return 0
return 0;
}
function isTextElement (elm: Element | Text): elm is Text {
return elm.childNodes.length === 1 && elm.childNodes[0].nodeType === 3
function isTextElement(elm: Element | Text): elm is Text {
return elm.childNodes.length === 1 && elm.childNodes[0].nodeType === 3;
}
let removed: any, created: any
let removed: any, created: any;
function pre () {
removed = {}
created = []
function pre() {
removed = {};
created = [];
}
function create (oldVnode: VNode, vnode: VNode): void {
const hero = (vnode.data as VNodeData).hero
function create(oldVnode: VNode, vnode: VNode): void {
const hero = (vnode.data as VNodeData).hero;
if (hero && hero.id) {
created.push(hero.id)
created.push(vnode)
created.push(hero.id);
created.push(vnode);
}
}
function destroy (vnode: VNode): void {
const hero = (vnode.data as VNodeData).hero
function destroy(vnode: VNode): void {
const hero = (vnode.data as VNodeData).hero;
if (hero && hero.id) {
const elm = vnode.elm;
(vnode as any).isTextNode = isTextElement(elm as Element | Text); // is this a text node?
(vnode as any).boundingRect = (elm as Element).getBoundingClientRect(); // save the bounding rectangle to a new property on the vnode
(vnode as any).textRect = (vnode as any).isTextNode ? getTextNodeRect((elm as Element).childNodes[0] as Text) : null // save bounding rect of inner text node
(vnode as any).textRect = (vnode as any).isTextNode
? getTextNodeRect((elm as Element).childNodes[0] as Text)
: null; // save bounding rect of inner text node
const computedStyle = window.getComputedStyle(elm as Element, undefined); // get current styles (includes inherited properties)
(vnode as any).savedStyle = JSON.parse(JSON.stringify(computedStyle)) // save a copy of computed style values
removed[hero.id] = vnode
(vnode as any).savedStyle = JSON.parse(JSON.stringify(computedStyle)); // save a copy of computed style values
removed[hero.id] = vnode;
}
}
function post () {
let i: number, id: any, newElm: Element, oldVnode: VNode, oldElm: Element,
hRatio: number, wRatio: number,
oldRect: ClientRect, newRect: ClientRect, dx: number, dy: number,
origTransform: string | null, origTransition: string | null,
newStyle: CSSStyleDeclaration, oldStyle: CSSStyleDeclaration,
newComputedStyle: CSSStyleDeclaration, isTextNode: boolean,
newTextRect: ClientRect | undefined, oldTextRect: ClientRect | undefined
function post() {
let i: number,
id: any,
newElm: Element,
oldVnode: VNode,
oldElm: Element,
hRatio: number,
wRatio: number,
oldRect: ClientRect,
newRect: ClientRect,
dx: number,
dy: number,
origTransform: string | null,
origTransition: string | null,
newStyle: CSSStyleDeclaration,
oldStyle: CSSStyleDeclaration,
newComputedStyle: CSSStyleDeclaration,
isTextNode: boolean,
newTextRect: ClientRect | undefined,
oldTextRect: ClientRect | undefined;
for (i = 0; i < created.length; i += 2) {
id = created[i]
newElm = created[i + 1].elm
oldVnode = removed[id]
id = created[i];
newElm = created[i + 1].elm;
oldVnode = removed[id];
if (oldVnode) {
isTextNode = (oldVnode as any).isTextNode && isTextElement(newElm) // Are old & new both text?
newStyle = (newElm as HTMLElement).style
newComputedStyle = window.getComputedStyle(newElm, undefined) // get full computed style for new element
oldElm = oldVnode.elm as Element
oldStyle = (oldElm as HTMLElement).style
isTextNode = (oldVnode as any).isTextNode && isTextElement(newElm); // Are old & new both text?
newStyle = (newElm as HTMLElement).style;
newComputedStyle = window.getComputedStyle(newElm, undefined); // get full computed style for new element
oldElm = oldVnode.elm as Element;
oldStyle = (oldElm as HTMLElement).style;
// Overall element bounding boxes
newRect = newElm.getBoundingClientRect()
oldRect = (oldVnode as any).boundingRect // previously saved bounding rect
newRect = newElm.getBoundingClientRect();
oldRect = (oldVnode as any).boundingRect; // previously saved bounding rect
// Text node bounding boxes & distances
if (isTextNode) {
newTextRect = getTextNodeRect(newElm.childNodes[0] as Text)
oldTextRect = (oldVnode as any).textRect
dx = getTextDx(oldTextRect, newTextRect)
dy = getTextDy(oldTextRect, newTextRect)
newTextRect = getTextNodeRect(newElm.childNodes[0] as Text);
oldTextRect = (oldVnode as any).textRect;
dx = getTextDx(oldTextRect, newTextRect);
dy = getTextDy(oldTextRect, newTextRect);
} else {
// Calculate distances between old & new positions
dx = oldRect.left - newRect.left
dy = oldRect.top - newRect.top
dx = oldRect.left - newRect.left;
dy = oldRect.top - newRect.top;
}
hRatio = newRect.height / (Math.max(oldRect.height, 1))
wRatio = isTextNode ? hRatio : newRect.width / (Math.max(oldRect.width, 1)) // text scales based on hRatio
hRatio = newRect.height / Math.max(oldRect.height, 1);
wRatio = isTextNode ? hRatio : newRect.width / Math.max(oldRect.width, 1); // text scales based on hRatio
// Animate new element
origTransform = newStyle.transform
origTransition = newStyle.transition
if (newComputedStyle.display === 'inline') {
origTransform = newStyle.transform;
origTransition = newStyle.transition;
if (newComputedStyle.display === "inline") {
// inline elements cannot be transformed
newStyle.display = 'inline-block' // this does not appear to have any negative side effects
newStyle.display = "inline-block"; // this does not appear to have any negative side effects
}
newStyle.transition = origTransition + 'transform 0s'
newStyle.transformOrigin = calcTransformOrigin(isTextNode, newTextRect, newRect)
newStyle.opacity = '0'
newStyle.transform = origTransform +
'translate(' + dx + 'px, ' + dy + 'px) ' +
'scale(' + 1 / wRatio + ', ' + 1 / hRatio + ')'
setNextFrame(newStyle, 'transition', origTransition)
setNextFrame(newStyle, 'transform', origTransform)
setNextFrame(newStyle, 'opacity', '1')
newStyle.transition = origTransition + "transform 0s";
newStyle.transformOrigin = calcTransformOrigin(
isTextNode,
newTextRect,
newRect
);
newStyle.opacity = "0";
newStyle.transform =
origTransform +
"translate(" +
dx +
"px, " +
dy +
"px) " +
"scale(" +
1 / wRatio +
", " +
1 / hRatio +
")";
setNextFrame(newStyle, "transition", origTransition);
setNextFrame(newStyle, "transform", origTransform);
setNextFrame(newStyle, "opacity", "1");
// Animate old element
for (const key in (oldVnode as any).savedStyle) { // re-apply saved inherited properties
for (const key in (oldVnode as any).savedStyle) {
// re-apply saved inherited properties
if (String(parseInt(key)) !== key) {
const ms = key.substring(0, 2) === 'ms'
const moz = key.substring(0, 3) === 'moz'
const webkit = key.substring(0, 6) === 'webkit'
const ms = key.substring(0, 2) === "ms";
const moz = key.substring(0, 3) === "moz";
const webkit = key.substring(0, 6) === "webkit";
if (!ms && !moz && !webkit) {
// ignore prefixed style properties
(oldStyle as any)[key] = (oldVnode as any).savedStyle[key]
(oldStyle as any)[key] = (oldVnode as any).savedStyle[key];
}
}
}
oldStyle.position = 'absolute'
oldStyle.top = oldRect.top + 'px' // start at existing position
oldStyle.left = oldRect.left + 'px'
oldStyle.width = oldRect.width + 'px' // Needed for elements who were sized relative to their parents
oldStyle.height = oldRect.height + 'px' // Needed for elements who were sized relative to their parents
oldStyle.margin = '0' // Margin on hero element leads to incorrect positioning
oldStyle.transformOrigin = calcTransformOrigin(isTextNode, oldTextRect, oldRect)
oldStyle.transform = ''
oldStyle.opacity = '1'
document.body.appendChild(oldElm)
setNextFrame(oldStyle, 'transform', 'translate(' + -dx + 'px, ' + -dy + 'px) scale(' + wRatio + ', ' + hRatio + ')') // scale must be on far right for translate to be correct
setNextFrame(oldStyle, 'opacity', '0')
oldElm.addEventListener('transitionend', function (ev: TransitionEvent) {
if (ev.propertyName === 'transform') {
document.body.removeChild(ev.target as Node)
oldStyle.position = "absolute";
oldStyle.top = oldRect.top + "px"; // start at existing position
oldStyle.left = oldRect.left + "px";
oldStyle.width = oldRect.width + "px"; // Needed for elements who were sized relative to their parents
oldStyle.height = oldRect.height + "px"; // Needed for elements who were sized relative to their parents
oldStyle.margin = "0"; // Margin on hero element leads to incorrect positioning
oldStyle.transformOrigin = calcTransformOrigin(
isTextNode,
oldTextRect,
oldRect
);
oldStyle.transform = "";
oldStyle.opacity = "1";
document.body.appendChild(oldElm);
setNextFrame(
oldStyle,
"transform",
"translate(" +
-dx +
"px, " +
-dy +
"px) scale(" +
wRatio +
", " +
hRatio +
")"
); // scale must be on far right for translate to be correct
setNextFrame(oldStyle, "opacity", "0");
oldElm.addEventListener("transitionend", function (ev: TransitionEvent) {
if (ev.propertyName === "transform") {
document.body.removeChild(ev.target as Node);
}
})
});
}
}
removed = created = undefined
removed = created = undefined;
}
export const heroModule: Module = { pre, create, destroy, post }
export const heroModule: Module = { pre, create, destroy, post };

@ -1,10 +1,17 @@
import { PreHook, CreateHook, UpdateHook, DestroyHook, RemoveHook, PostHook } from '../hooks'
import {
PreHook,
CreateHook,
UpdateHook,
DestroyHook,
RemoveHook,
PostHook,
} from "../hooks";
export type Module = Partial<{
pre: PreHook
create: CreateHook
update: UpdateHook
destroy: DestroyHook
remove: RemoveHook
post: PostHook
}>
pre: PreHook;
create: CreateHook;
update: UpdateHook;
destroy: DestroyHook;
remove: RemoveHook;
post: PostHook;
}>;

@ -1,28 +1,28 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type Props = Record<string, any>
export type Props = Record<string, any>;
function updateProps (oldVnode: VNode, vnode: VNode): void {
let key: string
let cur: any
let old: any
const elm = vnode.elm
let oldProps = (oldVnode.data as VNodeData).props
let props = (vnode.data as VNodeData).props
function updateProps(oldVnode: VNode, vnode: VNode): void {
let key: string;
let cur: any;
let old: any;
const elm = vnode.elm;
let oldProps = (oldVnode.data as VNodeData).props;
let props = (vnode.data as VNodeData).props;
if (!oldProps && !props) return
if (oldProps === props) return
oldProps = oldProps || {}
props = props || {}
if (!oldProps && !props) return;
if (oldProps === props) return;
oldProps = oldProps || {};
props = props || {};
for (key in props) {
cur = props[key]
old = oldProps[key]
if (old !== cur && (key !== 'value' || (elm as any)[key] !== cur)) {
(elm as any)[key] = cur
cur = props[key];
old = oldProps[key];
if (old !== cur && (key !== "value" || (elm as any)[key] !== cur)) {
(elm as any)[key] = cur;
}
}
}
export const propsModule: Module = { create: updateProps, update: updateProps }
export const propsModule: Module = { create: updateProps, update: updateProps };

@ -1,112 +1,118 @@
import { VNode, VNodeData } from '../vnode'
import { Module } from './module'
import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";
export type VNodeStyle = Record<string, string> & {
delayed?: Record<string, string>
remove?: Record<string, string>
}
delayed?: Record<string, string>;
remove?: Record<string, string>;
};
// Bindig `requestAnimationFrame` like this fixes a bug in IE/Edge. See #360 and #409.
const raf = (typeof window !== 'undefined' && (window.requestAnimationFrame).bind(window)) || setTimeout
const raf =
(typeof window !== "undefined" &&
window.requestAnimationFrame.bind(window)) ||
setTimeout;
const nextFrame = function (fn: any) {
raf(function () {
raf(fn)
})
}
let reflowForced = false
raf(fn);
});
};
let reflowForced = false;
function setNextFrame (obj: any, prop: string, val: any): void {
function setNextFrame(obj: any, prop: string, val: any): void {
nextFrame(function () {
obj[prop] = val
})
obj[prop] = val;
});
}
function updateStyle (oldVnode: VNode, vnode: VNode): void {
let cur: any
let name: string
const elm = vnode.elm
let oldStyle = (oldVnode.data as VNodeData).style
let style = (vnode.data as VNodeData).style
function updateStyle(oldVnode: VNode, vnode: VNode): void {
let cur: any;
let name: string;
const elm = vnode.elm;
let oldStyle = (oldVnode.data as VNodeData).style;
let style = (vnode.data as VNodeData).style;
if (!oldStyle && !style) return
if (oldStyle === style) return
oldStyle = oldStyle || {}
style = style || {}
const oldHasDel = 'delayed' in oldStyle
if (!oldStyle && !style) return;
if (oldStyle === style) return;
oldStyle = oldStyle || {};
style = style || {};
const oldHasDel = "delayed" in oldStyle;
for (name in oldStyle) {
if (!style[name]) {
if (name[0] === '-' && name[1] === '-') {
(elm as any).style.removeProperty(name)
if (name[0] === "-" && name[1] === "-") {
(elm as any).style.removeProperty(name);
} else {
(elm as any).style[name] = ''
(elm as any).style[name] = "";
}
}
}
for (name in style) {
cur = style[name]
if (name === 'delayed' && style.delayed) {
cur = style[name];
if (name === "delayed" && style.delayed) {
for (const name2 in style.delayed) {
cur = style.delayed[name2]
cur = style.delayed[name2];
if (!oldHasDel || cur !== (oldStyle.delayed as any)[name2]) {
setNextFrame((elm as any).style, name2, cur)
setNextFrame((elm as any).style, name2, cur);
}
}
} else if (name !== 'remove' && cur !== oldStyle[name]) {
if (name[0] === '-' && name[1] === '-') {
(elm as any).style.setProperty(name, cur)
} else if (name !== "remove" && cur !== oldStyle[name]) {
if (name[0] === "-" && name[1] === "-") {
(elm as any).style.setProperty(name, cur);
} else {
(elm as any).style[name] = cur
(elm as any).style[name] = cur;
}
}
}
}
function applyDestroyStyle (vnode: VNode): void {
let style: any
let name: string
const elm = vnode.elm
const s = (vnode.data as VNodeData).style
if (!s || !(style = s.destroy)) return
function applyDestroyStyle(vnode: VNode): void {
let style: any;
let name: string;
const elm = vnode.elm;
const s = (vnode.data as VNodeData).style;
if (!s || !(style = s.destroy)) return;
for (name in style) {
(elm as any).style[name] = style[name]
(elm as any).style[name] = style[name];
}
}
function applyRemoveStyle (vnode: VNode, rm: () => void): void {
const s = (vnode.data as VNodeData).style
function applyRemoveStyle(vnode: VNode, rm: () => void): void {
const s = (vnode.data as VNodeData).style;
if (!s || !s.remove) {
rm()
return
rm();
return;
}
if (!reflowForced) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
(vnode.elm as any).offsetLeft
reflowForced = true
(vnode.elm as any).offsetLeft;
reflowForced = true;
}
let name: string
const elm = vnode.elm
let i = 0
const style = s.remove
let amount = 0
const applied: string[] = []
let name: string;
const elm = vnode.elm;
let i = 0;
const style = s.remove;
let amount = 0;
const applied: string[] = [];
for (name in style) {
applied.push(name);
(elm as any).style[name] = style[name]
(elm as any).style[name] = style[name];
}
const compStyle = getComputedStyle(elm as Element)
const props = (compStyle as any)['transition-property'].split(', ')
const compStyle = getComputedStyle(elm as Element);
const props = (compStyle as any)["transition-property"].split(", ");
for (; i < props.length; ++i) {
if (applied.indexOf(props[i]) !== -1) amount++
if (applied.indexOf(props[i]) !== -1) amount++;
}
(elm as Element).addEventListener('transitionend', function (ev: TransitionEvent) {
if (ev.target === elm) --amount
if (amount === 0) rm()
})
(elm as Element).addEventListener(
"transitionend",
function (ev: TransitionEvent) {
if (ev.target === elm) --amount;
if (amount === 0) rm();
}
);
}
function forceReflow () {
reflowForced = false
function forceReflow() {
reflowForced = false;
}
export const styleModule: Module = {
@ -114,5 +120,5 @@ export const styleModule: Module = {
create: updateStyle,
update: updateStyle,
destroy: applyDestroyStyle,
remove: applyRemoveStyle
}
remove: applyRemoveStyle,
};

@ -1,64 +1,69 @@
import { VNode, VNodeData } from './vnode'
import { h } from './h'
import { VNode, VNodeData } from "./vnode";
import { h } from "./h";
export interface ThunkData extends VNodeData {
fn: () => VNode
args: any[]
fn: () => VNode;
args: any[];
}
export interface Thunk extends VNode {
data: ThunkData
data: ThunkData;
}
export interface ThunkFn {
(sel: string, fn: Function, args: any[]): Thunk
(sel: string, key: any, fn: Function, args: any[]): Thunk
(sel: string, fn: Function, args: any[]): Thunk;
(sel: string, key: any, fn: Function, args: any[]): Thunk;
}
function copyToThunk (vnode: VNode, thunk: VNode): void {
function copyToThunk(vnode: VNode, thunk: VNode): void {
(vnode.data as VNodeData).fn = (thunk.data as VNodeData).fn;
(vnode.data as VNodeData).args = (thunk.data as VNodeData).args
thunk.data = vnode.data
thunk.children = vnode.children
thunk.text = vnode.text
thunk.elm = vnode.elm
(vnode.data as VNodeData).args = (thunk.data as VNodeData).args;
thunk.data = vnode.data;
thunk.children = vnode.children;
thunk.text = vnode.text;
thunk.elm = vnode.elm;
}
function init (thunk: VNode): void {
const cur = thunk.data as VNodeData
const vnode = (cur.fn as any).apply(undefined, cur.args)
copyToThunk(vnode, thunk)
function init(thunk: VNode): void {
const cur = thunk.data as VNodeData;
const vnode = (cur.fn as any).apply(undefined, cur.args);
copyToThunk(vnode, thunk);
}
function prepatch (oldVnode: VNode, thunk: VNode): void {
let i: number
const old = oldVnode.data as VNodeData
const cur = thunk.data as VNodeData
const oldArgs = old.args
const args = cur.args
function prepatch(oldVnode: VNode, thunk: VNode): void {
let i: number;
const old = oldVnode.data as VNodeData;
const cur = thunk.data as VNodeData;
const oldArgs = old.args;
const args = cur.args;
if (old.fn !== cur.fn || (oldArgs as any).length !== (args as any).length) {
copyToThunk((cur.fn as any).apply(undefined, args), thunk)
return
copyToThunk((cur.fn as any).apply(undefined, args), thunk);
return;
}
for (i = 0; i < (args as any).length; ++i) {
if ((oldArgs as any)[i] !== (args as any)[i]) {
copyToThunk((cur.fn as any).apply(undefined, args), thunk)
return
copyToThunk((cur.fn as any).apply(undefined, args), thunk);
return;
}
}
copyToThunk(oldVnode, thunk)
copyToThunk(oldVnode, thunk);
}
export const thunk = function thunk (sel: string, key?: any, fn?: any, args?: any): VNode {
export const thunk = function thunk(
sel: string,
key?: any,
fn?: any,
args?: any
): VNode {
if (args === undefined) {
args = fn
fn = key
key = undefined
args = fn;
fn = key;
key = undefined;
}
return h(sel, {
key: key,
hook: { init, prepatch },
fn: fn,
args: args
})
} as ThunkFn
args: args,
});
} as ThunkFn;

@ -1,37 +1,37 @@
import { vnode, VNode } from './vnode'
import { htmlDomApi, DOMAPI } from './htmldomapi'
import { vnode, VNode } from "./vnode";
import { htmlDomApi, DOMAPI } from "./htmldomapi";
export function toVNode (node: Node, domApi?: DOMAPI): VNode {
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi
let text: string
export function toVNode(node: Node, domApi?: DOMAPI): VNode {
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
let text: string;
if (api.isElement(node)) {
const id = node.id ? '#' + node.id : ''
const cn = node.getAttribute('class')
const c = cn ? '.' + cn.split(' ').join('.') : ''
const sel = api.tagName(node).toLowerCase() + id + c
const attrs: any = {}
const children: VNode[] = []
let name: string
let i: number, n: number
const elmAttrs = node.attributes
const elmChildren = node.childNodes
const id = node.id ? "#" + node.id : "";
const cn = node.getAttribute("class");
const c = cn ? "." + cn.split(" ").join(".") : "";
const sel = api.tagName(node).toLowerCase() + id + c;
const attrs: any = {};
const children: VNode[] = [];
let name: string;
let i: number, n: number;
const elmAttrs = node.attributes;
const elmChildren = node.childNodes;
for (i = 0, n = elmAttrs.length; i < n; i++) {
name = elmAttrs[i].nodeName
if (name !== 'id' && name !== 'class') {
attrs[name] = elmAttrs[i].nodeValue
name = elmAttrs[i].nodeName;
if (name !== "id" && name !== "class") {
attrs[name] = elmAttrs[i].nodeValue;
}
}
for (i = 0, n = elmChildren.length; i < n; i++) {
children.push(toVNode(elmChildren[i], domApi))
children.push(toVNode(elmChildren[i], domApi));
}
return vnode(sel, { attrs }, children, undefined, node)
return vnode(sel, { attrs }, children, undefined, node);
} else if (api.isText(node)) {
text = api.getTextContent(node) as string
return vnode(undefined, undefined, undefined, text, node)
text = api.getTextContent(node) as string;
return vnode(undefined, undefined, undefined, text, node);
} else if (api.isComment(node)) {
text = api.getTextContent(node) as string
return vnode('!', {}, [], text, node as any)
text = api.getTextContent(node) as string;
return vnode("!", {}, [], text, node as any);
} else {
return vnode('', {}, [], undefined, node as any)
return vnode("", {}, [], undefined, node as any);
}
}

@ -1,47 +1,49 @@
import { Hooks } from './hooks'
import { AttachData } from './helpers/attachto'
import { VNodeStyle } from './modules/style'
import { On } from './modules/eventlisteners'
import { Attrs } from './modules/attributes'
import { Classes } from './modules/class'
import { Props } from './modules/props'
import { Dataset } from './modules/dataset'
import { Hero } from './modules/hero'
import { Hooks } from "./hooks";
import { AttachData } from "./helpers/attachto";
import { VNodeStyle } from "./modules/style";
import { On } from "./modules/eventlisteners";
import { Attrs } from "./modules/attributes";
import { Classes } from "./modules/class";
import { Props } from "./modules/props";
import { Dataset } from "./modules/dataset";
import { Hero } from "./modules/hero";
export type Key = string | number
export type Key = string | number;
export interface VNode {
sel: string | undefined
data: VNodeData | undefined
children: Array<VNode | string> | undefined
elm: Node | undefined
text: string | undefined
key: Key | undefined
sel: string | undefined;
data: VNodeData | undefined;
children: Array<VNode | string> | undefined;
elm: Node | undefined;
text: string | undefined;
key: Key | undefined;
}
export interface VNodeData {
props?: Props
attrs?: Attrs
class?: Classes
style?: VNodeStyle
dataset?: Dataset
on?: On
hero?: Hero
attachData?: AttachData
hook?: Hooks
key?: Key
ns?: string // for SVGs
fn?: () => VNode // for thunks
args?: any[] // for thunks
is?: string // for custom elements v1
[key: string]: any // for any other 3rd party module
props?: Props;
attrs?: Attrs;
class?: Classes;
style?: VNodeStyle;
dataset?: Dataset;
on?: On;
hero?: Hero;
attachData?: AttachData;
hook?: Hooks;
key?: Key;
ns?: string; // for SVGs
fn?: () => VNode; // for thunks
args?: any[]; // for thunks
is?: string; // for custom elements v1
[key: string]: any; // for any other 3rd party module
}
export function vnode (sel: string | undefined,
export function vnode(
sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined): VNode {
const key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key }
elm: Element | Text | undefined
): VNode {
const key = data === undefined ? undefined : data.key;
return { sel, data, children, text, elm, key };
}

@ -1,40 +1,40 @@
import 'core-js/stable/array/fill.js'
import faker from 'faker'
import { VNode } from '../../package/vnode'
import { h } from '../../package/h'
import { init as curInit } from '../../package/init'
import { init as refInit } from 'latest-snabbdom-release/init'
import { assert } from 'chai'
import pReduce from 'p-reduce'
import pMapSeries from 'p-map-series'
import { std, mean } from 'mathjs'
const RUNS = 5
const PATCHES_PER_RUN = 100
const WARM_UP_RUNS = 1
const REQUEST_ANIMATION_FRAME_EVERY_N_PATCHES = 1
const BENCHMARK_TIMEOUT_MINUTES = 10
const REQUIRED_PRECISION = 0.02
import "core-js/stable/array/fill.js";
import faker from "faker";
import { VNode } from "../../package/vnode";
import { h } from "../../package/h";
import { init as curInit } from "../../package/init";
import { init as refInit } from "latest-snabbdom-release/init";
import { assert } from "chai";
import pReduce from "p-reduce";
import pMapSeries from "p-map-series";
import { std, mean } from "mathjs";
const RUNS = 5;
const PATCHES_PER_RUN = 100;
const WARM_UP_RUNS = 1;
const REQUEST_ANIMATION_FRAME_EVERY_N_PATCHES = 1;
const BENCHMARK_TIMEOUT_MINUTES = 10;
const REQUIRED_PRECISION = 0.02;
/* eslint-disable @typescript-eslint/no-unused-vars */
declare global {
// eslint-disable-next-line @typescript-eslint/naming-convention
const __karma__: {
info(info: unknown): void
}
info(info: unknown): void;
};
}
/* eslint-enable @typescript-eslint/no-unused-vars */
const ALLOWED_REGRESSION = 0.03
describe('core benchmark', () => {
it('does not regress', async function Benchmark () {
this.timeout(BENCHMARK_TIMEOUT_MINUTES * 1000 * 60)
const ALLOWED_REGRESSION = 0.03;
describe("core benchmark", () => {
it("does not regress", async function Benchmark() {
this.timeout(BENCHMARK_TIMEOUT_MINUTES * 1000 * 60);
faker.seed(0)
const inputs = Array(PATCHES_PER_RUN).fill(null).map(() => {
return new Array(faker.random.number(20))
.fill(null)
.map(() => ({
faker.seed(0);
const inputs = Array(PATCHES_PER_RUN)
.fill(null)
.map(() => {
return new Array(faker.random.number(20)).fill(null).map(() => ({
name: faker.company.companyName(),
catchPhrase: faker.company.catchPhrase(),
suffix: faker.company.companySuffix(),
@ -45,116 +45,142 @@ describe('core benchmark', () => {
color: faker.commerce.color(),
price: faker.commerce.price() + faker.finance.currencySymbol(),
})),
founded: faker.date.past()
}))
})
type Input = (typeof inputs)[0]
const view = (companies: Input): VNode => h('table', [
h('caption', ['Companies']),
h('thead', [
h('tr', [
'Details',
'Products',
].map((th) => h('th', [th])))
]),
h('tbody', companies.map(function companyView (company) {
return h('tr', [
h('td', [
h('div', [
h('b', [company.name]),
company.suffix && `\xa0${company.suffix}`
]),
h('div', h('i', [company.catchPhrase])),
h('td', [
h('dt', ['Founded']),
h('dd', [company.founded.toLocaleDateString()])
])
]),
h('td', [h('ul', company.products.map(function productView (product) {
return h('li', [h('dl', [
h('dt', ['Name']),
h('dd', [product.name]),
h('dt', ['Color']),
h('dd', [product.color]),
h('dt', ['Price']),
h('dd', [product.price]),
])])
}))])
])
}))
])
type Patcher = ReturnType<typeof refInit | typeof curInit>
founded: faker.date.past(),
}));
});
type Input = typeof inputs[0];
const view = (companies: Input): VNode =>
h("table", [
h("caption", ["Companies"]),
h("thead", [
h(
"tr",
["Details", "Products"].map((th) => h("th", [th]))
),
]),
h(
"tbody",
companies.map(function companyView(company) {
return h("tr", [
h("td", [
h("div", [
h("b", [company.name]),
company.suffix && `\xa0${company.suffix}`,
]),
h("div", h("i", [company.catchPhrase])),
h("td", [
h("dt", ["Founded"]),
h("dd", [company.founded.toLocaleDateString()]),
]),
]),
h("td", [
h(
"ul",
company.products.map(function productView(product) {
return h("li", [
h("dl", [
h("dt", ["Name"]),
h("dd", [product.name]),
h("dt", ["Color"]),
h("dd", [product.color]),
h("dt", ["Price"]),
h("dd", [product.price]),
]),
]);
})
),
]),
]);
})
),
]);
type Patcher = ReturnType<typeof refInit | typeof curInit>;
interface SingleRunResult {
i: number
cur: number
ref: number
i: number;
cur: number;
ref: number;
}
const subjectToResult = async (subject: Patcher, subjectId: string): Promise<number> => {
const subjectToResult = async (
subject: Patcher,
subjectId: string
): Promise<number> => {
await new Promise((resolve) => {
requestAnimationFrame(resolve)
})
const markName = `mark:${subjectId}`
const measureName = `measure:${subjectId}`
performance.mark(markName)
requestAnimationFrame(resolve);
});
const markName = `mark:${subjectId}`;
const measureName = `measure:${subjectId}`;
performance.mark(markName);
const lastVnode = await pReduce(
inputs,
async function subjectToResultReducer (acc: HTMLElement | VNode, input, i) {
const vnode = view(input)
subject(acc, vnode)
async function subjectToResultReducer(
acc: HTMLElement | VNode,
input,
i
) {
const vnode = view(input);
subject(acc, vnode);
if (i % REQUEST_ANIMATION_FRAME_EVERY_N_PATCHES === 0) {
await new Promise((resolve) => {
requestAnimationFrame(resolve)
})
requestAnimationFrame(resolve);
});
}
return vnode
return vnode;
},
document.body.appendChild(document.createElement('section')),
)
performance.measure(measureName, markName)
if (!('elm' in lastVnode)) throw new Error()
if (!lastVnode.elm) throw new Error()
document.body.removeChild(lastVnode.elm)
const measure = performance.getEntriesByName(measureName)[0]
performance.clearMarks(markName)
performance.clearMeasures(measureName)
return measure.duration
}
const singleRun = async (_: null, runI: number): Promise<SingleRunResult> => {
const cur = await subjectToResult(curInit([]), `cur:${runI}`)
const ref = await subjectToResult(refInit([]), `ref:${runI}`)
return { i: runI, cur, ref }
}
const runResults = (await pMapSeries(Array(RUNS + WARM_UP_RUNS).fill(null), singleRun))
.slice(WARM_UP_RUNS)
__karma__.info({ benchmark: runResults })
document.body.appendChild(document.createElement("section"))
);
performance.measure(measureName, markName);
if (!("elm" in lastVnode)) throw new Error();
if (!lastVnode.elm) throw new Error();
document.body.removeChild(lastVnode.elm);
const measure = performance.getEntriesByName(measureName)[0];
performance.clearMarks(markName);
performance.clearMeasures(measureName);
return measure.duration;
};
const singleRun = async (
_: null,
runI: number
): Promise<SingleRunResult> => {
const cur = await subjectToResult(curInit([]), `cur:${runI}`);
const ref = await subjectToResult(refInit([]), `ref:${runI}`);
return { i: runI, cur, ref };
};
const runResults = (
await pMapSeries(Array(RUNS + WARM_UP_RUNS).fill(null), singleRun)
).slice(WARM_UP_RUNS);
__karma__.info({ benchmark: runResults });
const results = {
ref: runResults.map((result) => result.ref),
cur: runResults.map((result) => result.cur),
}
};
const means = {
ref: mean(results.ref),
cur: mean(results.cur),
}
};
const stds = {
ref: std(results.ref, 'uncorrected'),
cur: std(results.cur, 'uncorrected'),
}
;(['ref', 'cur'] as const).forEach((subject) => {
const stdRatio = stds[subject] / means[subject]
assert.isAtMost(stdRatio, REQUIRED_PRECISION, `${subject} not precise enough`)
})
assert.isAtMost(means.cur, means.ref * (1 + ALLOWED_REGRESSION))
})
})
ref: std(results.ref, "uncorrected"),
cur: std(results.cur, "uncorrected"),
};
(["ref", "cur"] as const).forEach((subject) => {
const stdRatio = stds[subject] / means[subject];
assert.isAtMost(
stdRatio,
REQUIRED_PRECISION,
`${subject} not precise enough`
);
});
assert.isAtMost(means.cur, means.ref * (1 + ALLOWED_REGRESSION));
});
});

@ -1,98 +1,93 @@
import { assert } from 'chai'
import { assert } from "chai";
import { init, RemoveHook, attachTo, h } from '../../src/index';
import { init, RemoveHook, attachTo, h } from "../../src/index";
const patch = init([])
const patch = init([]);
describe('attachTo', function () {
let elm: any, vnode0: any
describe("attachTo", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('div')
vnode0 = elm
})
it('adds element to target', function () {
const vnode1 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', 'Test')),
elm = document.createElement("div");
vnode0 = elm;
});
it("adds element to target", function () {
const vnode1 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(elm, h("div#attached", "Test")),
]),
])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.children.length, 2)
})
it('updates element at target', function () {
const vnode1 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', 'First text')),
]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 2);
});
it("updates element at target", function () {
const vnode1 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(elm, h("div#attached", "First text")),
]),
])
const vnode2 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', 'New text')),
]);
const vnode2 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(elm, h("div#attached", "New text")),
]),
])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.children[0].innerHTML, 'First text')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.children[0].innerHTML, 'New text')
})
it('element can be inserted before modal', function () {
const vnode1 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', 'Text')),
]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children[0].innerHTML, "First text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children[0].innerHTML, "New text");
});
it("element can be inserted before modal", function () {
const vnode1 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(elm, h("div#attached", "Text")),
]),
])
const vnode2 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
h('div', 'A new element'),
attachTo(elm, h('div#attached', 'Text')),
]);
const vnode2 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
h("div", "A new element"),
attachTo(elm, h("div#attached", "Text")),
]),
])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.children[0].innerHTML, 'Text')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.children[0].innerHTML, 'Text')
})
it('removes element at target', function () {
const vnode1 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', 'First text')),
]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children[0].innerHTML, "Text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children[0].innerHTML, "Text");
});
it("removes element at target", function () {
const vnode1 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(elm, h("div#attached", "First text")),
]),
])
const vnode2 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
]),
])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.children[0].innerHTML, 'First text')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.children.length, 1)
})
it('remove hook receives real element', function () {
]);
const vnode2 = h("div", [h("div#wrapper", [h("div", "Some element")])]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children[0].innerHTML, "First text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 1);
});
it("remove hook receives real element", function () {
const rm: RemoveHook = (vnode, cb) => {
const elm = vnode.elm as HTMLDivElement
assert.strictEqual(elm.tagName, 'DIV')
assert.strictEqual(elm.innerHTML, 'First text')
cb()
}
const vnode1 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
attachTo(elm, h('div#attached', { hook: { remove: rm } }, 'First text')),
]),
])
const vnode2 = h('div', [
h('div#wrapper', [
h('div', 'Some element'),
const elm = vnode.elm as HTMLDivElement;
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.innerHTML, "First text");
cb();
};
const vnode1 = h("div", [
h("div#wrapper", [
h("div", "Some element"),
attachTo(
elm,
h("div#attached", { hook: { remove: rm } }, "First text")
),
]),
])
elm = patch(vnode0, vnode1).elm
elm = patch(vnode1, vnode2).elm
})
})
]);
const vnode2 = h("div", [h("div#wrapper", [h("div", "Some element")])]);
elm = patch(vnode0, vnode1).elm;
elm = patch(vnode1, vnode2).elm;
});
});

@ -1,97 +1,106 @@
import { assert } from 'chai'
import { assert } from "chai";
import { init, attributesModule, h } from '../../src/index';
import { init, attributesModule, h } from "../../src/index";
const patch = init([
attributesModule
])
const patch = init([attributesModule]);
describe('attributes', function () {
let elm: any, vnode0: any
describe("attributes", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('div')
vnode0 = elm
})
it('have their provided values', function () {
const vnode1 = h('div', { attrs: { href: '/foo', minlength: 1, selected: true, disabled: false } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.getAttribute('href'), '/foo')
assert.strictEqual(elm.getAttribute('minlength'), '1')
assert.strictEqual(elm.hasAttribute('selected'), true)
assert.strictEqual(elm.getAttribute('selected'), '')
assert.strictEqual(elm.hasAttribute('disabled'), false)
})
it('can be memoized', function () {
const cachedAttrs = { href: '/foo', minlength: 1, selected: true }
const vnode1 = h('div', { attrs: cachedAttrs })
const vnode2 = h('div', { attrs: cachedAttrs })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.getAttribute('href'), '/foo')
assert.strictEqual(elm.getAttribute('minlength'), '1')
assert.strictEqual(elm.getAttribute('selected'), '')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.getAttribute('href'), '/foo')
assert.strictEqual(elm.getAttribute('minlength'), '1')
assert.strictEqual(elm.getAttribute('selected'), '')
})
it('are not omitted when falsy values are provided', function () {
const vnode1 = h('div', { attrs: { href: null as any, minlength: 0, value: '', title: 'undefined' } })
elm = patch(vnode0, vnode1).elm
assert.ok(elm.hasAttribute('href'))
assert.ok(elm.hasAttribute('minlength'))
assert.ok(elm.hasAttribute('value'))
assert.ok(elm.hasAttribute('title'))
})
it('are set correctly when namespaced', function () {
const vnode1 = h('div', { attrs: { 'xlink:href': '#foo' } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), '#foo')
})
it('should not touch class nor id fields', function () {
elm = document.createElement('div')
elm.id = 'myId'
elm.className = 'myClass'
vnode0 = elm
const vnode1 = h('div#myId.myClass', { attrs: {} }, ['Hello'])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.tagName, 'DIV')
assert.strictEqual(elm.id, 'myId')
assert.strictEqual(elm.className, 'myClass')
assert.strictEqual(elm.textContent, 'Hello')
})
describe('boolean attribute', function () {
it('is present and empty string if the value is truthy', function () {
const vnode1 = h('div', { attrs: { required: true, readonly: 1, noresize: 'truthy' } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.hasAttribute('required'), true)
assert.strictEqual(elm.getAttribute('required'), '')
assert.strictEqual(elm.hasAttribute('readonly'), true)
assert.strictEqual(elm.getAttribute('readonly'), '1')
assert.strictEqual(elm.hasAttribute('noresize'), true)
assert.strictEqual(elm.getAttribute('noresize'), 'truthy')
})
it('is omitted if the value is false', function () {
const vnode1 = h('div', { attrs: { required: false } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.hasAttribute('required'), false)
assert.strictEqual(elm.getAttribute('required'), null)
})
it('is not omitted if the value is falsy', function () {
const vnode1 = h('div', { attrs: { readonly: 0, noresize: null as any } })
elm = patch(vnode0, vnode1).elm
assert.ok(elm.hasAttribute('readonly'))
assert.ok(elm.hasAttribute('noresize'))
})
})
describe('Object.prototype property', function () {
it('is not considered as a boolean attribute and shouldn\'t be omitted', function () {
const vnode1 = h('div', { attrs: { constructor: true } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.hasAttribute('constructor'), true)
assert.strictEqual(elm.getAttribute('constructor'), '')
const vnode2 = h('div', { attrs: { constructor: false } })
elm = patch(vnode0, vnode2).elm
assert.strictEqual(elm.hasAttribute('constructor'), false)
})
})
})
elm = document.createElement("div");
vnode0 = elm;
});
it("have their provided values", function () {
const vnode1 = h("div", {
attrs: { href: "/foo", minlength: 1, selected: true, disabled: false },
});
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.getAttribute("href"), "/foo");
assert.strictEqual(elm.getAttribute("minlength"), "1");
assert.strictEqual(elm.hasAttribute("selected"), true);
assert.strictEqual(elm.getAttribute("selected"), "");
assert.strictEqual(elm.hasAttribute("disabled"), false);
});
it("can be memoized", function () {
const cachedAttrs = { href: "/foo", minlength: 1, selected: true };
const vnode1 = h("div", { attrs: cachedAttrs });
const vnode2 = h("div", { attrs: cachedAttrs });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.getAttribute("href"), "/foo");
assert.strictEqual(elm.getAttribute("minlength"), "1");
assert.strictEqual(elm.getAttribute("selected"), "");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.getAttribute("href"), "/foo");
assert.strictEqual(elm.getAttribute("minlength"), "1");
assert.strictEqual(elm.getAttribute("selected"), "");
});
it("are not omitted when falsy values are provided", function () {
const vnode1 = h("div", {
attrs: { href: null as any, minlength: 0, value: "", title: "undefined" },
});
elm = patch(vnode0, vnode1).elm;
assert.ok(elm.hasAttribute("href"));
assert.ok(elm.hasAttribute("minlength"));
assert.ok(elm.hasAttribute("value"));
assert.ok(elm.hasAttribute("title"));
});
it("are set correctly when namespaced", function () {
const vnode1 = h("div", { attrs: { "xlink:href": "#foo" } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(
elm.getAttributeNS("http://www.w3.org/1999/xlink", "href"),
"#foo"
);
});
it("should not touch class nor id fields", function () {
elm = document.createElement("div");
elm.id = "myId";
elm.className = "myClass";
vnode0 = elm;
const vnode1 = h("div#myId.myClass", { attrs: {} }, ["Hello"]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.id, "myId");
assert.strictEqual(elm.className, "myClass");
assert.strictEqual(elm.textContent, "Hello");
});
describe("boolean attribute", function () {
it("is present and empty string if the value is truthy", function () {
const vnode1 = h("div", {
attrs: { required: true, readonly: 1, noresize: "truthy" },
});
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.hasAttribute("required"), true);
assert.strictEqual(elm.getAttribute("required"), "");
assert.strictEqual(elm.hasAttribute("readonly"), true);
assert.strictEqual(elm.getAttribute("readonly"), "1");
assert.strictEqual(elm.hasAttribute("noresize"), true);
assert.strictEqual(elm.getAttribute("noresize"), "truthy");
});
it("is omitted if the value is false", function () {
const vnode1 = h("div", { attrs: { required: false } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.hasAttribute("required"), false);
assert.strictEqual(elm.getAttribute("required"), null);
});
it("is not omitted if the value is falsy", function () {
const vnode1 = h("div", {
attrs: { readonly: 0, noresize: null as any },
});
elm = patch(vnode0, vnode1).elm;
assert.ok(elm.hasAttribute("readonly"));
assert.ok(elm.hasAttribute("noresize"));
});
});
describe("Object.prototype property", function () {
it("is not considered as a boolean attribute and shouldn't be omitted", function () {
const vnode1 = h("div", { attrs: { constructor: true } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.hasAttribute("constructor"), true);
assert.strictEqual(elm.getAttribute("constructor"), "");
const vnode2 = h("div", { attrs: { constructor: false } });
elm = patch(vnode0, vnode2).elm;
assert.strictEqual(elm.hasAttribute("constructor"), false);
});
});
});

File diff suppressed because it is too large Load Diff

@ -1,57 +1,64 @@
import { assert } from 'chai'
import { assert } from "chai";
import { datasetModule, init, h } from '../../src/index';
import { datasetModule, init, h } from "../../src/index";
const patch = init([
datasetModule
])
const patch = init([datasetModule]);
describe('dataset', function () {
describe("dataset", function () {
before(function () {
if (!Object.hasOwnProperty.call(HTMLElement.prototype, 'dataset')) {
this.skip()
if (!Object.hasOwnProperty.call(HTMLElement.prototype, "dataset")) {
this.skip();
}
})
});
let elm: any, vnode0: any
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('div')
vnode0 = elm
})
it('is set on initial element creation', function () {
elm = patch(vnode0, h('div', { dataset: { foo: 'foo' } })).elm
assert.strictEqual(elm.dataset.foo, 'foo')
})
it('updates dataset', function () {
const vnode1 = h('i', { dataset: { foo: 'foo', bar: 'bar' } })
const vnode2 = h('i', { dataset: { baz: 'baz' } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.dataset.foo, 'foo')
assert.strictEqual(elm.dataset.bar, 'bar')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.dataset.baz, 'baz')
assert.strictEqual(elm.dataset.foo, undefined)
})
it('can be memoized', function () {
const cachedDataset = { foo: 'foo', bar: 'bar' }
const vnode1 = h('i', { dataset: cachedDataset })
const vnode2 = h('i', { dataset: cachedDataset })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.dataset.foo, 'foo')
assert.strictEqual(elm.dataset.bar, 'bar')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.dataset.foo, 'foo')
assert.strictEqual(elm.dataset.bar, 'bar')
})
it('handles string conversions', function () {
const vnode1 = h('i', { dataset: { empty: '', dash: '-', dashed: 'foo-bar', camel: 'fooBar', integer: 0 as any, float: 0.1 as any } })
elm = patch(vnode0, vnode1).elm
elm = document.createElement("div");
vnode0 = elm;
});
it("is set on initial element creation", function () {
elm = patch(vnode0, h("div", { dataset: { foo: "foo" } })).elm;
assert.strictEqual(elm.dataset.foo, "foo");
});
it("updates dataset", function () {
const vnode1 = h("i", { dataset: { foo: "foo", bar: "bar" } });
const vnode2 = h("i", { dataset: { baz: "baz" } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.dataset.foo, "foo");
assert.strictEqual(elm.dataset.bar, "bar");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.dataset.baz, "baz");
assert.strictEqual(elm.dataset.foo, undefined);
});
it("can be memoized", function () {
const cachedDataset = { foo: "foo", bar: "bar" };
const vnode1 = h("i", { dataset: cachedDataset });
const vnode2 = h("i", { dataset: cachedDataset });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.dataset.foo, "foo");
assert.strictEqual(elm.dataset.bar, "bar");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.dataset.foo, "foo");
assert.strictEqual(elm.dataset.bar, "bar");
});
it("handles string conversions", function () {
const vnode1 = h("i", {
dataset: {
empty: "",
dash: "-",
dashed: "foo-bar",
camel: "fooBar",
integer: 0 as any,
float: 0.1 as any,
},
});
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.dataset.empty, '')
assert.strictEqual(elm.dataset.dash, '-')
assert.strictEqual(elm.dataset.dashed, 'foo-bar')
assert.strictEqual(elm.dataset.camel, 'fooBar')
assert.strictEqual(elm.dataset.integer, '0')
assert.strictEqual(elm.dataset.float, '0.1')
})
})
assert.strictEqual(elm.dataset.empty, "");
assert.strictEqual(elm.dataset.dash, "-");
assert.strictEqual(elm.dataset.dashed, "foo-bar");
assert.strictEqual(elm.dataset.camel, "fooBar");
assert.strictEqual(elm.dataset.integer, "0");
assert.strictEqual(elm.dataset.float, "0.1");
});
});

@ -1,123 +1,125 @@
import { assert } from 'chai'
import { assert } from "chai";
import { VNode, init, eventListenersModule, h } from '../../src/index';
import { VNode, init, eventListenersModule, h } from "../../src/index";
const patch = init([
eventListenersModule
])
const patch = init([eventListenersModule]);
describe('event listeners', function () {
let elm: any, vnode0: any
describe("event listeners", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('div')
vnode0 = elm
})
it('attaches click event handler to element', function () {
const result = []
function clicked (ev: Event) {
result.push(ev)
elm = document.createElement("div");
vnode0 = elm;
});
it("attaches click event handler to element", function () {
const result = [];
function clicked(ev: Event) {
result.push(ev);
}
const vnode = h('div', { on: { click: clicked } }, [
h('a', 'Click my parent'),
])
elm = patch(vnode0, vnode).elm
elm.click()
assert.strictEqual(1, result.length)
})
it('does not attach new listener', function () {
const result: number[] = []
const vnode = h("div", { on: { click: clicked } }, [
h("a", "Click my parent"),
]);
elm = patch(vnode0, vnode).elm;
elm.click();
assert.strictEqual(1, result.length);
});
it("does not attach new listener", function () {
const result: number[] = [];
// function clicked(ev) { result.push(ev); }
const vnode1 = h('div', {
on: {
click: function (ev) {
result.push(1)
}
}
}, [
h('a', 'Click my parent'),
])
const vnode2 = h('div', {
on: {
click: function (ev) {
result.push(2)
}
}
}, [
h('a', 'Click my parent'),
])
elm = patch(vnode0, vnode1).elm
elm.click()
elm = patch(vnode1, vnode2).elm
elm.click()
assert.deepEqual(result, [1, 2])
})
it('detach attached click event handler to element', function () {
const result: Event[] = []
function clicked (ev: Event) {
result.push(ev)
const vnode1 = h(
"div",
{
on: {
click: function (ev) {
result.push(1);
},
},
},
[h("a", "Click my parent")]
);
const vnode2 = h(
"div",
{
on: {
click: function (ev) {
result.push(2);
},
},
},
[h("a", "Click my parent")]
);
elm = patch(vnode0, vnode1).elm;
elm.click();
elm = patch(vnode1, vnode2).elm;
elm.click();
assert.deepEqual(result, [1, 2]);
});
it("detach attached click event handler to element", function () {
const result: Event[] = [];
function clicked(ev: Event) {
result.push(ev);
}
const vnode1 = h('div', { on: { click: clicked } }, [
h('a', 'Click my parent'),
])
elm = patch(vnode0, vnode1).elm
elm.click()
assert.strictEqual(1, result.length)
const vnode2 = h('div', { on: {} }, [
h('a', 'Click my parent'),
])
elm = patch(vnode1, vnode2).elm
elm.click()
assert.strictEqual(1, result.length)
})
it('multiple event handlers for same event on same element', function () {
let called = 0
function clicked (ev: Event, vnode: VNode) {
++called
const vnode1 = h("div", { on: { click: clicked } }, [
h("a", "Click my parent"),
]);
elm = patch(vnode0, vnode1).elm;
elm.click();
assert.strictEqual(1, result.length);
const vnode2 = h("div", { on: {} }, [h("a", "Click my parent")]);
elm = patch(vnode1, vnode2).elm;
elm.click();
assert.strictEqual(1, result.length);
});
it("multiple event handlers for same event on same element", function () {
let called = 0;
function clicked(ev: Event, vnode: VNode) {
++called;
// Check that the first argument is an event
assert.strictEqual(true, 'target' in ev)
assert.strictEqual(true, "target" in ev);
// Check that the second argument was a vnode
assert.strictEqual(vnode.sel, 'div')
assert.strictEqual(vnode.sel, "div");
}
const vnode1 = h('div', { on: { click: [clicked, clicked, clicked] } }, [
h('a', 'Click my parent'),
])
elm = patch(vnode0, vnode1).elm
elm.click()
assert.strictEqual(3, called)
const vnode2 = h('div', { on: { click: [clicked, clicked] } }, [
h('a', 'Click my parent'),
])
elm = patch(vnode1, vnode2).elm
elm.click()
assert.strictEqual(5, called)
})
it('access to virtual node in event handler', function () {
const result: VNode[] = []
function clicked (this: VNode, ev: Event, vnode: VNode) {
result.push(this)
result.push(vnode)
const vnode1 = h("div", { on: { click: [clicked, clicked, clicked] } }, [
h("a", "Click my parent"),
]);
elm = patch(vnode0, vnode1).elm;
elm.click();
assert.strictEqual(3, called);
const vnode2 = h("div", { on: { click: [clicked, clicked] } }, [
h("a", "Click my parent"),
]);
elm = patch(vnode1, vnode2).elm;
elm.click();
assert.strictEqual(5, called);
});
it("access to virtual node in event handler", function () {
const result: VNode[] = [];
function clicked(this: VNode, ev: Event, vnode: VNode) {
result.push(this);
result.push(vnode);
}
const vnode1 = h('div', { on: { click: clicked } }, [
h('a', 'Click my parent'),
])
elm = patch(vnode0, vnode1).elm
elm.click()
assert.strictEqual(2, result.length)
assert.strictEqual(vnode1, result[0])
assert.strictEqual(vnode1, result[1])
})
it('shared handlers in parent and child nodes', function () {
const result = []
const vnode1 = h("div", { on: { click: clicked } }, [
h("a", "Click my parent"),
]);
elm = patch(vnode0, vnode1).elm;
elm.click();
assert.strictEqual(2, result.length);
assert.strictEqual(vnode1, result[0]);
assert.strictEqual(vnode1, result[1]);
});
it("shared handlers in parent and child nodes", function () {
const result = [];
const sharedHandlers = {
click: function (ev: Event) { result.push(ev) }
}
const vnode1 = h('div', { on: sharedHandlers }, [
h('a', { on: sharedHandlers }, 'Click my parent'),
])
elm = patch(vnode0, vnode1).elm
elm.click()
assert.strictEqual(1, result.length)
elm.firstChild.click()
assert.strictEqual(3, result.length)
})
})
click: function (ev: Event) {
result.push(ev);
},
};
const vnode1 = h("div", { on: sharedHandlers }, [
h("a", { on: sharedHandlers }, "Click my parent"),
]);
elm = patch(vnode0, vnode1).elm;
elm.click();
assert.strictEqual(1, result.length);
elm.firstChild.click();
assert.strictEqual(3, result.length);
});
});

@ -1,53 +1,50 @@
import { assert } from 'chai'
import { assert } from "chai";
import { init, h, attributesModule } from '../../src/index';
import { init, h, attributesModule } from "../../src/index";
const patch = init([
attributesModule
])
const patch = init([attributesModule]);
describe('svg', function () {
let elm: any, vnode0: any
describe("svg", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('svg')
vnode0 = elm
})
it('removes child svg elements', function () {
const a = h('svg', {}, [
h('g'),
h('g')
])
const b = h('svg', {}, [
h('g')
])
const result = patch(patch(vnode0, a), b).elm as SVGElement
assert.strictEqual(result.childNodes.length, 1)
})
it('adds correctly xlink namespaced attribute', function () {
const xlinkNS = 'http://www.w3.org/1999/xlink'
const testUrl = '/test'
const a = h('svg', {}, [
h('use', {
attrs: { 'xlink:href': testUrl }
}, [])
])
const result = patch(vnode0, a).elm as SVGElement
assert.strictEqual(result.childNodes.length, 1)
const child = result.childNodes[0] as SVGUseElement
assert.strictEqual(child.getAttribute('xlink:href'), testUrl)
assert.strictEqual(child.getAttributeNS(xlinkNS, 'href'), testUrl)
})
it('adds correctly xml namespaced attribute', function () {
const xmlNS = 'http://www.w3.org/XML/1998/namespace'
const testAttrValue = 'und'
const a = h('svg', { attrs: { 'xml:lang': testAttrValue } }, [])
const result = patch(vnode0, a).elm as SVGElement
assert.strictEqual(result.getAttributeNS(xmlNS, 'lang'), testAttrValue)
assert.strictEqual(result.getAttribute('xml:lang'), testAttrValue)
})
})
elm = document.createElement("svg");
vnode0 = elm;
});
it("removes child svg elements", function () {
const a = h("svg", {}, [h("g"), h("g")]);
const b = h("svg", {}, [h("g")]);
const result = patch(patch(vnode0, a), b).elm as SVGElement;
assert.strictEqual(result.childNodes.length, 1);
});
it("adds correctly xlink namespaced attribute", function () {
const xlinkNS = "http://www.w3.org/1999/xlink";
const testUrl = "/test";
const a = h("svg", {}, [
h(
"use",
{
attrs: { "xlink:href": testUrl },
},
[]
),
]);
const result = patch(vnode0, a).elm as SVGElement;
assert.strictEqual(result.childNodes.length, 1);
const child = result.childNodes[0] as SVGUseElement;
assert.strictEqual(child.getAttribute("xlink:href"), testUrl);
assert.strictEqual(child.getAttributeNS(xlinkNS, "href"), testUrl);
});
it("adds correctly xml namespaced attribute", function () {
const xmlNS = "http://www.w3.org/XML/1998/namespace";
const testAttrValue = "und";
const a = h("svg", { attrs: { "xml:lang": testAttrValue } }, []);
const result = patch(vnode0, a).elm as SVGElement;
assert.strictEqual(result.getAttributeNS(xmlNS, "lang"), testAttrValue);
assert.strictEqual(result.getAttribute("xml:lang"), testAttrValue);
});
});

@ -1,42 +1,47 @@
import { assert } from 'chai'
import { assert } from "chai";
// workaround linter issue
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jsx } from '../../package/jsx'
import '../../package/jsx-global'
import { jsx } from "../../package/jsx";
import "../../package/jsx-global";
describe('snabbdom', function () {
describe('jsx', function () {
it('can be used as a jsxFactory method', function () {
const vnode = <div title="Hello World">Hello World</div>
describe("snabbdom", function () {
describe("jsx", function () {
it("can be used as a jsxFactory method", function () {
const vnode = <div title="Hello World">Hello World</div>;
assert.deepStrictEqual(vnode, {
sel: 'div',
data: { title: 'Hello World' },
sel: "div",
data: { title: "Hello World" },
children: undefined,
elm: undefined,
text: 'Hello World',
key: undefined
})
})
text: "Hello World",
key: undefined,
});
});
it('creates text property for text only child', function () {
const vnode = <div>foo bar</div>
it("creates text property for text only child", function () {
const vnode = <div>foo bar</div>;
assert.deepStrictEqual(vnode, {
sel: 'div',
sel: "div",
data: {},
children: undefined,
elm: undefined,
text: 'foo bar',
key: undefined
})
})
text: "foo bar",
key: undefined,
});
});
it('creates an array of children for multiple children', function () {
const vnode = <div>{'foo'}{'bar'}</div>
it("creates an array of children for multiple children", function () {
const vnode = (
<div>
{"foo"}
{"bar"}
</div>
);
assert.deepStrictEqual(vnode, {
sel: 'div',
sel: "div",
data: {},
children: [
{
@ -44,83 +49,85 @@ describe('snabbdom', function () {
data: undefined,
children: undefined,
elm: undefined,
text: 'foo',
key: undefined
text: "foo",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'bar',
key: undefined
text: "bar",
key: undefined,
},
],
elm: undefined,
text: undefined,
key: undefined
})
})
key: undefined,
});
});
it('flattens children', function () {
it("flattens children", function () {
const vnode = (
<section>
<h1>A Heading</h1>
some description
{['part1', 'part2'].map(part => <span>{part}</span>)}
{["part1", "part2"].map((part) => (
<span>{part}</span>
))}
</section>
)
);
assert.deepStrictEqual(vnode, {
sel: 'section',
sel: "section",
data: {},
children: [
{
sel: 'h1',
sel: "h1",
data: {},
children: undefined,
elm: undefined,
text: 'A Heading',
key: undefined
text: "A Heading",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'some description',
key: undefined
text: "some description",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'part1',
key: undefined
text: "part1",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'part2',
key: undefined
text: "part2",
key: undefined,
},
],
elm: undefined,
text: undefined,
key: undefined
})
})
key: undefined,
});
});
it('removes falsey children', function () {
const showLogin = false
const showCaptcha = false
const loginAttempts = 0
const userName = ''
const profilePic = undefined
const isLoggedIn = true
it("removes falsey children", function () {
const showLogin = false;
const showCaptcha = false;
const loginAttempts = 0;
const userName = "";
const profilePic = undefined;
const isLoggedIn = true;
const vnode = (
<div>
Login Form
@ -131,10 +138,10 @@ describe('snabbdom', function () {
Login Attempts: {loginAttempts}
Logged In: {isLoggedIn}
</div>
)
);
assert.deepStrictEqual(vnode, {
sel: 'div',
sel: "div",
data: {},
children: [
{
@ -142,126 +149,130 @@ describe('snabbdom', function () {
data: undefined,
children: undefined,
elm: undefined,
text: 'Login Form',
key: undefined
text: "Login Form",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'Login Attempts: ',
key: undefined
text: "Login Attempts: ",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: '0',
key: undefined
text: "0",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'Logged In: ',
key: undefined
text: "Logged In: ",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'true',
key: undefined
text: "true",
key: undefined,
},
],
elm: undefined,
text: undefined,
key: undefined
})
})
key: undefined,
});
});
it('works with a function component', function () {
it("works with a function component", function () {
// workaround linter issue
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Part = ({ part }: {part: string}) => <span>{part}</span>
const Part = ({ part }: { part: string }) => <span>{part}</span>;
const vnode = (
<div>
<a attrs={{ href: 'https://github.com/snabbdom/snabbdom' }}>Snabbdom</a>
<a attrs={{ href: "https://github.com/snabbdom/snabbdom" }}>
Snabbdom
</a>
and tsx
{['work', 'like', 'a', 'charm!'].map(part => <Part part={part}></Part>)}
{'💃🕺🎉'}
{["work", "like", "a", "charm!"].map((part) => (
<Part part={part}></Part>
))}
{"💃🕺🎉"}
</div>
)
);
assert.deepStrictEqual(vnode, {
sel: 'div',
sel: "div",
data: {},
children: [
{
sel: 'a',
data: { attrs: { href: 'https://github.com/snabbdom/snabbdom' } },
sel: "a",
data: { attrs: { href: "https://github.com/snabbdom/snabbdom" } },
children: undefined,
elm: undefined,
text: 'Snabbdom',
key: undefined
text: "Snabbdom",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: 'and tsx',
key: undefined
text: "and tsx",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'work',
key: undefined
text: "work",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'like',
key: undefined
text: "like",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'a',
key: undefined
text: "a",
key: undefined,
},
{
sel: 'span',
sel: "span",
data: {},
children: undefined,
elm: undefined,
text: 'charm!',
key: undefined
text: "charm!",
key: undefined,
},
{
sel: undefined,
data: undefined,
children: undefined,
elm: undefined,
text: '💃🕺🎉',
key: undefined
text: "💃🕺🎉",
key: undefined,
},
],
elm: undefined,
text: undefined,
key: undefined
})
})
})
})
key: undefined,
});
});
});
});

@ -1,167 +1,174 @@
import { assert } from 'chai'
import { assert } from "chai";
import { init, styleModule, h, toVNode } from '../../src/index';
import { init, styleModule, h, toVNode } from "../../src/index";
const patch = init([
styleModule
])
const patch = init([styleModule]);
const featureDiscoveryElm = document.createElement('div')
featureDiscoveryElm.style.setProperty('--foo', 'foo')
const hasCssVariables = featureDiscoveryElm.style.getPropertyValue('--foo') === 'foo'
const featureDiscoveryElm = document.createElement("div");
featureDiscoveryElm.style.setProperty("--foo", "foo");
const hasCssVariables =
featureDiscoveryElm.style.getPropertyValue("--foo") === "foo";
describe('style', function () {
let elm: any, vnode0: any
describe("style", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement('div')
vnode0 = elm
})
it('is being styled', function () {
elm = patch(vnode0, h('div', { style: { fontSize: '12px' } })).elm
assert.strictEqual(elm.style.fontSize, '12px')
})
it('can be memoized', function () {
const cachedStyles = { fontSize: '14px', display: 'inline' }
const vnode1 = h('i', { style: cachedStyles })
const vnode2 = h('i', { style: cachedStyles })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.fontSize, '14px')
assert.strictEqual(elm.style.display, 'inline')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.style.fontSize, '14px')
assert.strictEqual(elm.style.display, 'inline')
})
it('updates styles', function () {
const vnode1 = h('i', { style: { fontSize: '14px', display: 'inline' } })
const vnode2 = h('i', { style: { fontSize: '12px', display: 'block' } })
const vnode3 = h('i', { style: { fontSize: '10px', display: 'block' } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.fontSize, '14px')
assert.strictEqual(elm.style.display, 'inline')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.style.fontSize, '12px')
assert.strictEqual(elm.style.display, 'block')
elm = patch(vnode2, vnode3).elm
assert.strictEqual(elm.style.fontSize, '10px')
assert.strictEqual(elm.style.display, 'block')
})
it('explicialy removes styles', function () {
const vnode1 = h('i', { style: { fontSize: '14px' } })
const vnode2 = h('i', { style: { fontSize: '' } })
const vnode3 = h('i', { style: { fontSize: '10px' } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.fontSize, '14px')
patch(vnode1, vnode2)
assert.strictEqual(elm.style.fontSize, '')
patch(vnode2, vnode3)
assert.strictEqual(elm.style.fontSize, '10px')
})
it('implicially removes styles from element', function () {
const vnode1 = h('div', [h('i', { style: { fontSize: '14px' } })])
const vnode2 = h('div', [h('i')])
const vnode3 = h('div', [h('i', { style: { fontSize: '10px' } })])
patch(vnode0, vnode1)
assert.strictEqual(elm.firstChild.style.fontSize, '14px')
patch(vnode1, vnode2)
assert.strictEqual(elm.firstChild.style.fontSize, '')
patch(vnode2, vnode3)
assert.strictEqual(elm.firstChild.style.fontSize, '10px')
})
it('updates css variables', function () {
elm = document.createElement("div");
vnode0 = elm;
});
it("is being styled", function () {
elm = patch(vnode0, h("div", { style: { fontSize: "12px" } })).elm;
assert.strictEqual(elm.style.fontSize, "12px");
});
it("can be memoized", function () {
const cachedStyles = { fontSize: "14px", display: "inline" };
const vnode1 = h("i", { style: cachedStyles });
const vnode2 = h("i", { style: cachedStyles });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.fontSize, "14px");
assert.strictEqual(elm.style.display, "inline");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.style.fontSize, "14px");
assert.strictEqual(elm.style.display, "inline");
});
it("updates styles", function () {
const vnode1 = h("i", { style: { fontSize: "14px", display: "inline" } });
const vnode2 = h("i", { style: { fontSize: "12px", display: "block" } });
const vnode3 = h("i", { style: { fontSize: "10px", display: "block" } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.fontSize, "14px");
assert.strictEqual(elm.style.display, "inline");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.style.fontSize, "12px");
assert.strictEqual(elm.style.display, "block");
elm = patch(vnode2, vnode3).elm;
assert.strictEqual(elm.style.fontSize, "10px");
assert.strictEqual(elm.style.display, "block");
});
it("explicialy removes styles", function () {
const vnode1 = h("i", { style: { fontSize: "14px" } });
const vnode2 = h("i", { style: { fontSize: "" } });
const vnode3 = h("i", { style: { fontSize: "10px" } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.fontSize, "14px");
patch(vnode1, vnode2);
assert.strictEqual(elm.style.fontSize, "");
patch(vnode2, vnode3);
assert.strictEqual(elm.style.fontSize, "10px");
});
it("implicially removes styles from element", function () {
const vnode1 = h("div", [h("i", { style: { fontSize: "14px" } })]);
const vnode2 = h("div", [h("i")]);
const vnode3 = h("div", [h("i", { style: { fontSize: "10px" } })]);
patch(vnode0, vnode1);
assert.strictEqual(elm.firstChild.style.fontSize, "14px");
patch(vnode1, vnode2);
assert.strictEqual(elm.firstChild.style.fontSize, "");
patch(vnode2, vnode3);
assert.strictEqual(elm.firstChild.style.fontSize, "10px");
});
it("updates css variables", function () {
if (!hasCssVariables) {
this.skip()
this.skip();
} else {
const vnode1 = h('div', { style: { '--myVar': 1 as any } })
const vnode2 = h('div', { style: { '--myVar': 2 as any } })
const vnode3 = h('div', { style: { '--myVar': 3 as any } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '1')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '2')
elm = patch(vnode2, vnode3).elm
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '3')
const vnode1 = h("div", { style: { "--myVar": 1 as any } });
const vnode2 = h("div", { style: { "--myVar": 2 as any } });
const vnode3 = h("div", { style: { "--myVar": 3 as any } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "1");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "2");
elm = patch(vnode2, vnode3).elm;
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "3");
}
})
it('explicialy removes css variables', function () {
});
it("explicialy removes css variables", function () {
if (!hasCssVariables) {
this.skip()
this.skip();
} else {
const vnode1 = h('i', { style: { '--myVar': 1 as any } })
const vnode2 = h('i', { style: { '--myVar': '' } })
const vnode3 = h('i', { style: { '--myVar': 2 as any } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '1')
patch(vnode1, vnode2)
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '')
patch(vnode2, vnode3)
assert.strictEqual(elm.style.getPropertyValue('--myVar'), '2')
const vnode1 = h("i", { style: { "--myVar": 1 as any } });
const vnode2 = h("i", { style: { "--myVar": "" } });
const vnode3 = h("i", { style: { "--myVar": 2 as any } });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "1");
patch(vnode1, vnode2);
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "");
patch(vnode2, vnode3);
assert.strictEqual(elm.style.getPropertyValue("--myVar"), "2");
}
})
it('implicially removes css variables from element', function () {
});
it("implicially removes css variables from element", function () {
if (!hasCssVariables) {
this.skip()
this.skip();
} else {
const vnode1 = h('div', [h('i', { style: { '--myVar': 1 as any } })])
const vnode2 = h('div', [h('i')])
const vnode3 = h('div', [h('i', { style: { '--myVar': 2 as any } })])
patch(vnode0, vnode1)
assert.strictEqual(elm.firstChild.style.getPropertyValue('--myVar'), '1')
patch(vnode1, vnode2)
assert.strictEqual(elm.firstChild.style.getPropertyValue('--myVar'), '')
patch(vnode2, vnode3)
assert.strictEqual(elm.firstChild.style.getPropertyValue('--myVar'), '2')
const vnode1 = h("div", [h("i", { style: { "--myVar": 1 as any } })]);
const vnode2 = h("div", [h("i")]);
const vnode3 = h("div", [h("i", { style: { "--myVar": 2 as any } })]);
patch(vnode0, vnode1);
assert.strictEqual(elm.firstChild.style.getPropertyValue("--myVar"), "1");
patch(vnode1, vnode2);
assert.strictEqual(elm.firstChild.style.getPropertyValue("--myVar"), "");
patch(vnode2, vnode3);
assert.strictEqual(elm.firstChild.style.getPropertyValue("--myVar"), "2");
}
})
it('updates delayed styles in next frame', function (done) {
const vnode1 = h('i', { style: { fontSize: '14px', delayed: { fontSize: '16px' } as any } })
const vnode2 = h('i', { style: { fontSize: '18px', delayed: { fontSize: '20px' } as any } })
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.style.fontSize, '14px')
});
it("updates delayed styles in next frame", function (done) {
const vnode1 = h("i", {
style: { fontSize: "14px", delayed: { fontSize: "16px" } as any },
});
const vnode2 = h("i", {
style: { fontSize: "18px", delayed: { fontSize: "20px" } as any },
});
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.style.fontSize, "14px");
requestAnimationFrame(() => {
requestAnimationFrame(() => {
assert.strictEqual(elm.style.fontSize, '16px')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.style.fontSize, '18px')
assert.strictEqual(elm.style.fontSize, "16px");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.style.fontSize, "18px");
requestAnimationFrame(() => {
requestAnimationFrame(() => {
assert.strictEqual(elm.style.fontSize, '20px')
done()
})
})
})
})
})
it('applies tranform as transition on remove', function (done) {
const btn = h('button', {
style: {
transition: 'transform 0.1s',
remove: { transform: 'translateY(100%)' } as any
}
}, ['A button'])
const vnode1 = h('div.parent', {}, [btn])
const vnode2 = h('div.parent', {}, [null])
document.body.appendChild(vnode0)
patch(vnode0, vnode1)
patch(vnode1, vnode2)
const button = document.querySelector('button') as HTMLButtonElement
assert.notStrictEqual(button, null)
button.addEventListener('transitionend', function () {
assert.strictEqual(document.querySelector('button'), null)
done()
})
})
describe('using toVNode()', function () {
it('handles (ignoring) comment nodes', function () {
const comment = document.createComment('yolo')
const prevElm = document.createElement('div')
prevElm.appendChild(comment)
const nextVNode = h('div', [h('span', 'Hi')])
elm = patch(toVNode(prevElm), nextVNode).elm
assert.strictEqual(elm, prevElm)
assert.strictEqual(elm.tagName, 'DIV')
assert.strictEqual(elm.childNodes.length, 1)
assert.strictEqual(elm.childNodes[0].tagName, 'SPAN')
assert.strictEqual(elm.childNodes[0].textContent, 'Hi')
})
})
})
assert.strictEqual(elm.style.fontSize, "20px");
done();
});
});
});
});
});
it("applies tranform as transition on remove", function (done) {
const btn = h(
"button",
{
style: {
transition: "transform 0.1s",
remove: { transform: "translateY(100%)" } as any,
},
},
["A button"]
);
const vnode1 = h("div.parent", {}, [btn]);
const vnode2 = h("div.parent", {}, [null]);
document.body.appendChild(vnode0);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
const button = document.querySelector("button") as HTMLButtonElement;
assert.notStrictEqual(button, null);
button.addEventListener("transitionend", function () {
assert.strictEqual(document.querySelector("button"), null);
done();
});
});
describe("using toVNode()", function () {
it("handles (ignoring) comment nodes", function () {
const comment = document.createComment("yolo");
const prevElm = document.createElement("div");
prevElm.appendChild(comment);
const nextVNode = h("div", [h("span", "Hi")]);
elm = patch(toVNode(prevElm), nextVNode).elm;
assert.strictEqual(elm, prevElm);
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].tagName, "SPAN");
assert.strictEqual(elm.childNodes[0].textContent, "Hi");
});
});
});

@ -1,231 +1,210 @@
import { assert } from 'chai'
import { assert } from "chai";
import { init, h, thunk, VNode } from '../../src/index';
import { init, h, thunk, VNode } from "../../src/index";
const patch = init([
])
const patch = init([]);
describe('thunk', function () {
let elm: any, vnode0: any
describe("thunk", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = vnode0 = document.createElement('div')
})
it('returns vnode with data and render function', function () {
function numberInSpan (n: number) {
return h('span', 'Number is ' + n)
}
const vnode = thunk('span', 'num', numberInSpan, [22])
assert.deepEqual(vnode.sel, 'span')
assert.deepEqual(vnode.data.key, 'num')
assert.deepEqual(vnode.data.args, [22])
})
it('calls render function once on data change', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
const vnode1 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode2 = h('div', [
thunk('span', 'num', numberInSpan, [2])
])
patch(vnode0, vnode1)
assert.strictEqual(called, 1)
patch(vnode1, vnode2)
assert.strictEqual(called, 2)
})
it('does not call render function on data unchanged', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
const vnode1 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode2 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
patch(vnode0, vnode1)
assert.strictEqual(called, 1)
patch(vnode1, vnode2)
assert.strictEqual(called, 1)
})
it('calls render function once on data-length change', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
const vnode1 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode2 = h('div', [
thunk('span', 'num', numberInSpan, [1, 2])
])
patch(vnode0, vnode1)
assert.strictEqual(called, 1)
patch(vnode1, vnode2)
assert.strictEqual(called, 2)
})
it('calls render function once on function change', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
function numberInSpan2 (n: number) {
called++
return h('span', { key: 'num' }, 'Number really is ' + n)
}
const vnode1 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode2 = h('div', [
thunk('span', 'num', numberInSpan2, [1])
])
patch(vnode0, vnode1)
assert.strictEqual(called, 1)
patch(vnode1, vnode2)
assert.strictEqual(called, 2)
})
it('renders correctly', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
const vnode1 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode2 = h('div', [
thunk('span', 'num', numberInSpan, [1])
])
const vnode3 = h('div', [
thunk('span', 'num', numberInSpan, [2])
])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.firstChild.innerHTML, 'Number is 1')
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.firstChild.innerHTML, 'Number is 1')
elm = patch(vnode2, vnode3).elm
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.firstChild.innerHTML, 'Number is 2')
assert.strictEqual(called, 2)
})
it('supports leaving out the `key` argument', function () {
function vnodeFn (s: string) {
return h('span.number', 'Hello ' + s)
}
const vnode1 = thunk('span.number', vnodeFn, ['World!'])
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.innerText, 'Hello World!')
})
it('renders correctly when root', function () {
let called = 0
function numberInSpan (n: number) {
called++
return h('span', { key: 'num' }, 'Number is ' + n)
}
const vnode1 = thunk('span', 'num', numberInSpan, [1])
const vnode2 = thunk('span', 'num', numberInSpan, [1])
const vnode3 = thunk('span', 'num', numberInSpan, [2])
elm = vnode0 = document.createElement("div");
});
it("returns vnode with data and render function", function () {
function numberInSpan(n: number) {
return h("span", "Number is " + n);
}
const vnode = thunk("span", "num", numberInSpan, [22]);
assert.deepEqual(vnode.sel, "span");
assert.deepEqual(vnode.data.key, "num");
assert.deepEqual(vnode.data.args, [22]);
});
it("calls render function once on data change", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("span", "num", numberInSpan, [2])]);
patch(vnode0, vnode1);
assert.strictEqual(called, 1);
patch(vnode1, vnode2);
assert.strictEqual(called, 2);
});
it("does not call render function on data unchanged", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("span", "num", numberInSpan, [1])]);
patch(vnode0, vnode1);
assert.strictEqual(called, 1);
patch(vnode1, vnode2);
assert.strictEqual(called, 1);
});
it("calls render function once on data-length change", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("span", "num", numberInSpan, [1, 2])]);
patch(vnode0, vnode1);
assert.strictEqual(called, 1);
patch(vnode1, vnode2);
assert.strictEqual(called, 2);
});
it("calls render function once on function change", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
function numberInSpan2(n: number) {
called++;
return h("span", { key: "num" }, "Number really is " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("span", "num", numberInSpan2, [1])]);
patch(vnode0, vnode1);
assert.strictEqual(called, 1);
patch(vnode1, vnode2);
assert.strictEqual(called, 2);
});
it("renders correctly", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode3 = h("div", [thunk("span", "num", numberInSpan, [2])]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), "span");
assert.strictEqual(elm.firstChild.innerHTML, "Number is 1");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), "span");
assert.strictEqual(elm.firstChild.innerHTML, "Number is 1");
elm = patch(vnode2, vnode3).elm;
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), "span");
assert.strictEqual(elm.firstChild.innerHTML, "Number is 2");
assert.strictEqual(called, 2);
});
it("supports leaving out the `key` argument", function () {
function vnodeFn(s: string) {
return h("span.number", "Hello " + s);
}
const vnode1 = thunk("span.number", vnodeFn, ["World!"]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.innerText, "Hello World!");
});
it("renders correctly when root", function () {
let called = 0;
function numberInSpan(n: number) {
called++;
return h("span", { key: "num" }, "Number is " + n);
}
const vnode1 = thunk("span", "num", numberInSpan, [1]);
const vnode2 = thunk("span", "num", numberInSpan, [1]);
const vnode3 = thunk("span", "num", numberInSpan, [2]);
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.innerHTML, 'Number is 1')
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.tagName.toLowerCase(), "span");
assert.strictEqual(elm.innerHTML, "Number is 1");
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.innerHTML, 'Number is 1')
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.tagName.toLowerCase(), "span");
assert.strictEqual(elm.innerHTML, "Number is 1");
elm = patch(vnode2, vnode3).elm
assert.strictEqual(elm.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.innerHTML, 'Number is 2')
assert.strictEqual(called, 2)
})
it('can be replaced and removed', function () {
function numberInSpan (n: number) {
return h('span', { key: 'num' }, 'Number is ' + n)
}
function oddEven (n: number): VNode {
const prefix = (n % 2) === 0 ? 'Even' : 'Odd'
return h('div', { key: oddEven as any }, prefix + ': ' + n)
}
const vnode1 = h('div', [thunk('span', 'num', numberInSpan, [1])])
const vnode2 = h('div', [thunk('div', 'oddEven', oddEven, [4])])
elm = patch(vnode2, vnode3).elm;
assert.strictEqual(elm.tagName.toLowerCase(), "span");
assert.strictEqual(elm.innerHTML, "Number is 2");
assert.strictEqual(called, 2);
});
it("can be replaced and removed", function () {
function numberInSpan(n: number) {
return h("span", { key: "num" }, "Number is " + n);
}
function oddEven(n: number): VNode {
const prefix = n % 2 === 0 ? "Even" : "Odd";
return h("div", { key: oddEven as any }, prefix + ": " + n);
}
const vnode1 = h("div", [thunk("span", "num", numberInSpan, [1])]);
const vnode2 = h("div", [thunk("div", "oddEven", oddEven, [4])]);
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.firstChild.innerHTML, 'Number is 1')
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), "span");
assert.strictEqual(elm.firstChild.innerHTML, "Number is 1");
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), 'div')
assert.strictEqual(elm.firstChild.innerHTML, 'Even: 4')
})
it('can be replaced and removed when root', function () {
function numberInSpan (n: number) {
return h('span', { key: 'num' }, 'Number is ' + n)
}
function oddEven (n: number): VNode {
const prefix = (n % 2) === 0 ? 'Even' : 'Odd'
return h('div', { key: oddEven as any }, prefix + ': ' + n)
}
const vnode1 = thunk('span', 'num', numberInSpan, [1])
const vnode2 = thunk('div', 'oddEven', oddEven, [4])
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.firstChild.tagName.toLowerCase(), "div");
assert.strictEqual(elm.firstChild.innerHTML, "Even: 4");
});
it("can be replaced and removed when root", function () {
function numberInSpan(n: number) {
return h("span", { key: "num" }, "Number is " + n);
}
function oddEven(n: number): VNode {
const prefix = n % 2 === 0 ? "Even" : "Odd";
return h("div", { key: oddEven as any }, prefix + ": " + n);
}
const vnode1 = thunk("span", "num", numberInSpan, [1]);
const vnode2 = thunk("div", "oddEven", oddEven, [4]);
elm = patch(vnode0, vnode1).elm
assert.strictEqual(elm.tagName.toLowerCase(), 'span')
assert.strictEqual(elm.innerHTML, 'Number is 1')
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.tagName.toLowerCase(), "span");
assert.strictEqual(elm.innerHTML, "Number is 1");
elm = patch(vnode1, vnode2).elm
assert.strictEqual(elm.tagName.toLowerCase(), 'div')
assert.strictEqual(elm.innerHTML, 'Even: 4')
})
it('invokes destroy hook on thunks', function () {
let called = 0
function destroyHook () {
called++
}
function numberInSpan (n: number) {
return h('span', { key: 'num', hook: { destroy: destroyHook } }, 'Number is ' + n)
}
const vnode1 = h('div', [
h('div', 'Foo'),
thunk('span', 'num', numberInSpan, [1]),
h('div', 'Foo')
])
const vnode2 = h('div', [
h('div', 'Foo'),
h('div', 'Foo')
])
patch(vnode0, vnode1)
patch(vnode1, vnode2)
assert.strictEqual(called, 1)
})
it('invokes remove hook on thunks', function () {
let called = 0
function hook () {
called++
}
function numberInSpan (n: number) {
return h('span', { key: 'num', hook: { remove: hook } }, 'Number is ' + n)
}
const vnode1 = h('div', [
h('div', 'Foo'),
thunk('span', 'num', numberInSpan, [1]),
h('div', 'Foo')
])
const vnode2 = h('div', [
h('div', 'Foo'),
h('div', 'Foo')
])
patch(vnode0, vnode1)
patch(vnode1, vnode2)
assert.strictEqual(called, 1)
})
})
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.tagName.toLowerCase(), "div");
assert.strictEqual(elm.innerHTML, "Even: 4");
});
it("invokes destroy hook on thunks", function () {
let called = 0;
function destroyHook() {
called++;
}
function numberInSpan(n: number) {
return h(
"span",
{ key: "num", hook: { destroy: destroyHook } },
"Number is " + n
);
}
const vnode1 = h("div", [
h("div", "Foo"),
thunk("span", "num", numberInSpan, [1]),
h("div", "Foo"),
]);
const vnode2 = h("div", [h("div", "Foo"), h("div", "Foo")]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(called, 1);
});
it("invokes remove hook on thunks", function () {
let called = 0;
function hook() {
called++;
}
function numberInSpan(n: number) {
return h(
"span",
{ key: "num", hook: { remove: hook } },
"Number is " + n
);
}
const vnode1 = h("div", [
h("div", "Foo"),
thunk("span", "num", numberInSpan, [1]),
h("div", "Foo"),
]);
const vnode2 = h("div", [h("div", "Foo"), h("div", "Foo")]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(called, 1);
});
});

Loading…
Cancel
Save