Repository: infernojs/inferno Branch: master Commit: bd1f4ee6b425 Files: 441 Total size: 3.0 MB Directory structure: gitextract_phe3_u4g/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── COMMIT_TEMPLATE.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci-template.yml │ └── codeql-analysis.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── browser/ │ └── jsfiddle-integration-babel7v3.js ├── demo/ │ └── inferno-router-demo/ │ ├── .babelrc │ ├── .gitignore │ ├── .proxyrc │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── App.tsx │ │ ├── index.html │ │ ├── index.tsx │ │ ├── indexServer.tsx │ │ ├── pages/ │ │ │ ├── AboutPage.scss │ │ │ ├── AboutPage.tsx │ │ │ ├── ContentPage.scss │ │ │ ├── ContentPage.tsx │ │ │ ├── PageTemplate.scss │ │ │ ├── PageTemplate.tsx │ │ │ ├── StartPage.scss │ │ │ └── StartPage.tsx │ │ └── server.ts │ └── tsconfig.json ├── docs/ │ ├── 1kcomponents/ │ │ ├── app.js │ │ ├── index.html │ │ └── style.css │ ├── animations/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── animations-demo/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── animations-demo-inner/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── animations-global-demo/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── async-render/ │ │ ├── app.js │ │ └── index.html │ ├── build.js │ ├── compat/ │ │ ├── app.js │ │ └── index.html │ ├── dbmonster/ │ │ ├── ENV.js │ │ ├── app.js │ │ ├── index.html │ │ └── style.css │ ├── dbmonster-mobx/ │ │ ├── app.js │ │ ├── index.html │ │ └── style.css │ ├── event-test/ │ │ ├── app.js │ │ ├── index.html │ │ └── styles.css │ ├── form/ │ │ ├── app.js │ │ └── index.html │ ├── index.html │ ├── math-elements/ │ │ ├── app.js │ │ ├── index.html │ │ └── style.css │ ├── styles.css │ ├── svg/ │ │ ├── app.js │ │ └── tiger.html │ ├── uibench/ │ │ ├── app.js │ │ ├── custom-uibench.js │ │ └── index.html │ ├── uibench-inferno-compat/ │ │ ├── app.js │ │ └── index.html │ ├── uibench-lifecycle/ │ │ ├── app.js │ │ └── index.html │ ├── uibench-normalization/ │ │ ├── app.js │ │ └── index.html │ └── uibench-reactlike/ │ ├── app.js │ └── index.html ├── documentation/ │ ├── v4-migration.md │ ├── v6-migration.md │ ├── v6-release.md │ └── v9-migration.md ├── eslint.config.js ├── fixtures/ │ └── browser/ │ ├── gzip/ │ │ └── gzippreprocessor.js │ ├── karma.babel.conf.js │ ├── karma.sauce.conf.js │ ├── karma.swc.conf.js │ ├── karma.ts.conf.js │ ├── package.json │ ├── test.index.js │ └── test.no-compat.index.js ├── jest.config-nodom.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages/ │ ├── inferno/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── animationHooks.spec.tsx │ │ │ ├── animationHooksFunc.spec.tsx │ │ │ ├── async-setstate.spec.tsx │ │ │ ├── blueprints.spec.tsx │ │ │ ├── children.spec.tsx │ │ │ ├── clonenode.spec.tsx │ │ │ ├── columnrender.spec.tsx │ │ │ ├── componentlifecycle.spec.tsx │ │ │ ├── components2.spec.tsx │ │ │ ├── createref.spec.tsx │ │ │ ├── data/ │ │ │ │ └── common-render/ │ │ │ │ ├── child.tsx │ │ │ │ ├── parentbase.tsx │ │ │ │ ├── parentfirstcommon.tsx │ │ │ │ └── parentsecondcommon.tsx │ │ │ ├── defaultprops-typings.spec.tsx │ │ │ ├── error.spec.tsx │ │ │ ├── forceUpdate.spec.tsx │ │ │ ├── formelements.spec.tsx │ │ │ ├── forward-ref.spec.tsx │ │ │ ├── fragments.spec.tsx │ │ │ ├── hooks.spec.tsx │ │ │ ├── input.spec.tsx │ │ │ ├── instancenull.spec.tsx │ │ │ ├── issue-1369.spec.tsx │ │ │ ├── lifecycle.spec.tsx │ │ │ ├── link.spec.tsx │ │ │ ├── linkEvent.spec.tsx │ │ │ ├── mixedFormElements.spec.tsx │ │ │ ├── newlifecycle.spec.tsx │ │ │ ├── patching-jsx.spec.tsx │ │ │ ├── patching.spec.tsx │ │ │ ├── portal.spec.tsx │ │ │ ├── rendering.spec.tsx │ │ │ ├── select.spec.tsx │ │ │ ├── select2.spec.tsx │ │ │ ├── setState.spec.tsx │ │ │ ├── singlepatches.spec.tsx │ │ │ ├── state.spec.tsx │ │ │ ├── styles.spec.tsx │ │ │ ├── svgXlink.spec.tsx │ │ │ ├── topcontext.spec.tsx │ │ │ ├── transition.spec.tsx │ │ │ ├── types.children.spec.tsx │ │ │ ├── types.spec.tsx │ │ │ └── validations.spec.tsx │ │ ├── index.cjs │ │ ├── index.mjs │ │ ├── package.json │ │ └── src/ │ │ ├── DOM/ │ │ │ ├── constants.ts │ │ │ ├── events/ │ │ │ │ ├── attachEvent.ts │ │ │ │ ├── delegation.ts │ │ │ │ └── linkEvent.ts │ │ │ ├── mounting.ts │ │ │ ├── patching.ts │ │ │ ├── props.ts │ │ │ ├── rendering.ts │ │ │ ├── unmounting.ts │ │ │ ├── utils/ │ │ │ │ ├── common.ts │ │ │ │ ├── componentUtil.ts │ │ │ │ └── innerHTML.ts │ │ │ └── wrappers/ │ │ │ ├── InputWrapper.ts │ │ │ ├── SelectWrapper.ts │ │ │ ├── TextareaWrapper.ts │ │ │ ├── processElement.ts │ │ │ └── wrapper.ts │ │ ├── core/ │ │ │ ├── component.ts │ │ │ ├── implementation.ts │ │ │ ├── nativetypes.ts │ │ │ ├── refs.ts │ │ │ ├── types.ts │ │ │ └── validate.ts │ │ └── index.ts │ ├── inferno-animation/ │ │ ├── __tests__/ │ │ │ ├── animatedAllComponent.spec.tsx │ │ │ ├── animatedComponent.spec.tsx │ │ │ ├── animatedComponentTypings.tsx │ │ │ ├── animatedMoveComponent.spec.tsx │ │ │ ├── index.spec.tsx │ │ │ └── utils.spec.tsx │ │ ├── index.cjs │ │ ├── index.css │ │ ├── package.json │ │ ├── readme.md │ │ └── src/ │ │ ├── AnimatedAllComponent.ts │ │ ├── AnimatedComponent.ts │ │ ├── AnimatedMoveComponent.ts │ │ ├── animationCoordinator.ts │ │ ├── animations.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── inferno-clone-vnode/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── cloneVNode.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ ├── inferno-compat/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── ReactChildren.spec.jsx │ │ │ ├── ReactComponent.spec.jsx │ │ │ ├── ReactComponentLifeCycle.spec.jsx │ │ │ ├── ReactCompositeComponentState.spec.jsx │ │ │ ├── ReactDOM.spec.jsx │ │ │ ├── ReactDOMComponent.spec.jsx │ │ │ ├── ReactES6Class.spec.jsx │ │ │ ├── ReactElement.spec.jsx │ │ │ ├── ReactElementClone.spec.jsx │ │ │ ├── ReactJSXElement.spec.jsx │ │ │ ├── ReactMount.spec.jsx │ │ │ ├── ReactMountDestruction.spec.jsx │ │ │ ├── ReactMultiChild.spec.jsx │ │ │ ├── ReactPureComponent.spec.jsx │ │ │ ├── ReactStatelessComponent.spec.jsx │ │ │ ├── SelectValueElement.spec.jsx │ │ │ ├── clonevnode.spec.tsx │ │ │ ├── compat_children.spec.tsx │ │ │ ├── findDOMNodes.spec.jsx │ │ │ ├── isValidElement.spec.jsx │ │ │ ├── lifecycle.spec.jsx │ │ │ ├── misc.spec.jsx │ │ │ ├── onlyChild.spec.jsx │ │ │ ├── styles.spec.jsx │ │ │ ├── svg.spec.jsx │ │ │ ├── testutils.spec.jsx │ │ │ └── warnings.spec.jsx │ │ ├── index.cjs │ │ ├── lib/ │ │ │ ├── EventConstants.js │ │ │ ├── EventPluginHub.js │ │ │ ├── EventPluginUtils.js │ │ │ ├── EventPropagators.js │ │ │ ├── ReactCSSTransitionGroup.js │ │ │ ├── ReactFragment.js │ │ │ ├── ReactMount.js │ │ │ ├── ReactTransitionEvents.js │ │ │ ├── ReactTransitionGroup.js │ │ │ ├── SyntheticUIEvent.js │ │ │ ├── ViewportMetrics.js │ │ │ └── shallowCompare.js │ │ ├── package.json │ │ └── src/ │ │ ├── InfernoCompatPropertyMap.ts │ │ ├── PropTypes.ts │ │ ├── index.ts │ │ └── reactstyles.ts │ ├── inferno-create-element/ │ │ ├── __tests__/ │ │ │ ├── callback.in.ctr.spec.tsx │ │ │ ├── children.spec.ts │ │ │ ├── components.spec.ts │ │ │ ├── components1.spec.tsx │ │ │ ├── components2b.spec.tsx │ │ │ ├── components3.spec.tsx │ │ │ ├── createElement.fragment.spec.ts │ │ │ ├── createElement.spec.ts │ │ │ ├── createElementTyped.spec.tsx │ │ │ ├── creation.spec.ts │ │ │ ├── elements.spec.jsx │ │ │ ├── events.spec.js │ │ │ ├── hooks.spec.js │ │ │ ├── patchKeyedChildren.spec.js │ │ │ ├── patchMixedKeyed.spec.js │ │ │ ├── patchNonKeyedChildren.spec.js │ │ │ ├── patching.spec.js │ │ │ ├── select.spec.js │ │ │ ├── svg.ext.spec.js │ │ │ ├── svg.spec.jsx │ │ │ ├── text.spec.js │ │ │ ├── update.ext.spec.js │ │ │ └── update.spec.jsx │ │ ├── index.cjs │ │ ├── package.json │ │ ├── readme.md │ │ └── src/ │ │ └── index.ts │ ├── inferno-extras/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── extras.spec.jsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ ├── findDOMNode.ts │ │ ├── index.ts │ │ └── isDOMInsideVDOM.ts │ ├── inferno-hydrate/ │ │ ├── __tests__/ │ │ │ ├── hydrate-forward-ref.spec.tsx │ │ │ └── hydrate.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ ├── readme.md │ │ └── src/ │ │ └── index.ts │ ├── inferno-hyperscript/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── hyperscript.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ ├── inferno-mobx/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── context.spec.jsx │ │ │ ├── extra/ │ │ │ │ └── eventemitter.spec.js │ │ │ ├── generic.spec.jsx │ │ │ ├── inject.spec.jsx │ │ │ ├── misc.spec.jsx │ │ │ ├── observer.spec.jsx │ │ │ ├── observerPatch.spec.jsx │ │ │ ├── observerWrap.spec.jsx │ │ │ ├── stateless.spec.jsx │ │ │ ├── transactions.spec.jsx │ │ │ └── types.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ ├── Provider.ts │ │ ├── index.ts │ │ ├── observer.ts │ │ ├── observerPatch.ts │ │ ├── observerWrap.ts │ │ └── utils/ │ │ ├── EventEmitter.ts │ │ └── utils.ts │ ├── inferno-redux/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── components/ │ │ │ │ ├── Provider.spec.js │ │ │ │ ├── Provider.typings.tsx │ │ │ │ └── connect.spec.js │ │ │ ├── functional.spec.jsx │ │ │ └── utils/ │ │ │ ├── shallowEqual.spec.js │ │ │ └── wrapActionCreators.spec.js │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ ├── components/ │ │ │ ├── Provider.ts │ │ │ └── connectAdvanced.ts │ │ ├── connect/ │ │ │ ├── connect.ts │ │ │ ├── mapDispatchToProps.ts │ │ │ ├── mapStateToProps.ts │ │ │ ├── mergeProps.ts │ │ │ ├── selectorFactory.ts │ │ │ ├── verifySubselectors.ts │ │ │ └── wrapMapToProps.ts │ │ ├── index.ts │ │ └── utils/ │ │ ├── Subscription.ts │ │ ├── shallowEqual.ts │ │ ├── verifyPlainObject.ts │ │ ├── warning.ts │ │ └── wrapActionCreators.ts │ ├── inferno-router/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── BrowserRouter.spec.tsx │ │ │ ├── HashRouter.spec.tsx │ │ │ ├── Link.ext.spec.tsx │ │ │ ├── Link.spec.tsx │ │ │ ├── MemoryRouter.spec.tsx │ │ │ ├── NavLink.spec.tsx │ │ │ ├── Prompt.spec.tsx │ │ │ ├── Route.spec.tsx │ │ │ ├── Route.typings.spec.tsx │ │ │ ├── Router.spec.tsx │ │ │ ├── Switch.spec.tsx │ │ │ ├── SwitchMount.spec.tsx │ │ │ ├── github1176.spec.tsx │ │ │ ├── integration.spec.tsx │ │ │ ├── issue1322.spec.tsx │ │ │ ├── loaderOnRoute.spec.tsx │ │ │ ├── loaderWithSwitch.spec.tsx │ │ │ ├── matchPath.spec.ts │ │ │ ├── mobx-router.spec.tsx │ │ │ ├── testUtils.ts │ │ │ └── withRouter.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ ├── BrowserRouter.ts │ │ ├── HashRouter.ts │ │ ├── Link.ts │ │ ├── MemoryRouter.ts │ │ ├── NavLink.ts │ │ ├── Prompt.ts │ │ ├── Redirect.ts │ │ ├── Route.ts │ │ ├── Router.ts │ │ ├── StaticRouter.ts │ │ ├── Switch.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── locationUtils.ts │ │ ├── matchPath.ts │ │ ├── resolveLoaders.ts │ │ ├── utils.ts │ │ └── withRouter.ts │ ├── inferno-server/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── StaticRouter.spec.server-nodom.tsx │ │ │ ├── animationHooks.spec.server.tsx │ │ │ ├── creation-queuestream.spec.server.tsx │ │ │ ├── creation-stream.spec.server.ts │ │ │ ├── creation-stream.spec.server.tsx │ │ │ ├── creation.spec.server.ts │ │ │ ├── creation.spec.server.tsx │ │ │ ├── hydration-ext.spec.server.tsx │ │ │ ├── hydration.spec.server.ts │ │ │ ├── hydration.spec.server.tsx │ │ │ ├── loaderOnRoute.spec.server.tsx │ │ │ ├── observer.spec.server.tsx │ │ │ ├── props-context.spec.server.tsx │ │ │ ├── security.spec.server.tsx │ │ │ ├── ssr-forwardref.spec.tsx │ │ │ └── utils.spec.server.tsx │ │ ├── index.cjs │ │ ├── index.mjs │ │ ├── package.json │ │ └── src/ │ │ ├── index.ts │ │ ├── prop-renderers.ts │ │ ├── renderToString.queuestream.ts │ │ ├── renderToString.stream.ts │ │ ├── renderToString.ts │ │ ├── stream/ │ │ │ └── streamUtils.ts │ │ └── utils.ts │ ├── inferno-shared/ │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ ├── inferno-test-utils/ │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ ├── snapshots.spec.tsx.snap │ │ │ │ └── testUtils.jest.spec.tsx.snap │ │ │ ├── snapshots.spec.tsx │ │ │ ├── testUtils.jest.spec.tsx │ │ │ └── testUtils.spec.tsx │ │ ├── index.cjs │ │ ├── package.json │ │ └── src/ │ │ ├── index.ts │ │ ├── jest.ts │ │ └── utils.ts │ ├── inferno-utils/ │ │ ├── __tests__/ │ │ │ └── utils.spec.tsx │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ └── inferno-vnode-flags/ │ ├── README.md │ ├── index.cjs │ ├── package.json │ └── src/ │ └── index.ts ├── scripts/ │ ├── bundle/ │ │ ├── bundle-size.js │ │ ├── move-compiled.js │ │ ├── move-typedefs.js │ │ └── read-files-in-dir.js │ ├── fakedom/ │ │ ├── build.js │ │ ├── libs/ │ │ │ ├── setup.js │ │ │ └── uibench.js │ │ ├── results/ │ │ │ └── inferno_base_line.json │ │ ├── uibench-reactlike/ │ │ │ ├── app.js │ │ │ └── start.js │ │ ├── viewer.html │ │ └── viewer.js │ ├── rollup/ │ │ ├── build.js │ │ └── plugins/ │ │ ├── alias.js │ │ └── index.js │ └── test/ │ ├── globals.js │ ├── jasmine-polyfill.js │ └── requestAnimationFrame.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # top-most EditorConfig file root = true [*] end_of_line = lf insert_final_newline = true quote_type = single # Tab indentation [*.{js,ts,jsx,tsx,json}] indent_style = space indent_size = 2 # Space indentation [package.json] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto *.css text eol=lf *.js text eol=lf *.jsx text eol=lf *.ts text eol=lf linguist-language=JavaScript *.tsx text eol=lf linguist-language=JavaScript *.json text eol=lf *.html text eol=lf *.md text eol=lf .gitattributes text eol=lf ================================================ FILE: .github/COMMIT_TEMPLATE.md ================================================ # : (If applied, this commit will...) (Max 50 char) # |<---- Using a Maximum Of 50 Characters ---->| # Explain why this change is being made # |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: Github issue #23 # --- COMMIT END --- # Type can be # feat (new feature) # fix (bug fix) # refactor (refactoring production code) # style (formatting, missing semi colons, etc; no code change) # docs (changes to documentation) # test (adding or refactoring tests; no production code change) # chore (updating grunt tasks etc; no production code change) # -------------------- # Remember to # Capitalize the subject line # Use the imperative mood in the subject line # Do not end the subject line with a period # Separate subject from body with a blank line # Use the body to explain what and why vs. how # Can use multiple lines with "-" for bullet points in body # -------------------- ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: inferno ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ *Before* submitting an issue please: - Check that you are using the latest version of Inferno. Either using our [CDN @ Master](http://cdn.infernojs.org/latest/inferno.js) or by checking the tags on [NPM](http://www.npmjs.com/package/inferno). - Check that the bug has not been fixed in the latest development version. Use our [CDN @ Edge](http://cdn.infernojs.org/edge/inferno.js). - Check that the issue has not been brought up before on [Github issues](http://www.github.com/infernojs/inferno/issues). **If you can, please distill your problem down and include a JSFiddle example for illustration. Also when requesting bug fix please include at least one test to avoid regression.** --- ## Issue Template **Observed Behaviour** Inferno is... **Expected Current Behaviour** Inferno should... **Inferno Metadata** macOS / Windowx / Linux Safari / Chrome / Firefox / ... ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ *Before* submitting a PR please: - Include tests for the functionality you are adding! See CONTRIBUTING.md for details how to run tests. - Run `npm run build` and check that the build succeeds. - Ensure that the PR hasn't been submitted before. --- ## PR Template **Objective** This PR... **Closes Issue** It closes Issue #... ================================================ FILE: .github/workflows/ci-template.yml ================================================ name: CI on: push: branches: [ master ] pull_request: branches: [ '**' ] jobs: tests: runs-on: ubuntu-latest name: Tests steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: latest - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Build run: npm run build - name: Test with coverage run: npm run test:coverage - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 - name: Run browser tests on SauceLabs run: npm run test:browser:sauce env: SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] schedule: - cron: '19 8 * * 0' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'javascript' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .gitignore ================================================ .idea .vscode .DS_Store .rpt2_cache* .changelog *.log build dist coverage node_modules fixtures/browser/*.cmd packages/*/dist packages/*/tmpDist packages/*/package-lock.json .rpt2_cache_development .rpt2_cache_production .swc ================================================ FILE: .npmrc ================================================ legacy-peer-deps=true ================================================ FILE: CHANGELOG.md ================================================ See GitHub for changes https://github.com/infernojs/inferno/releases ================================================ FILE: CONTRIBUTING.md ================================================ Contributing to Inferno ========================== Many thanks for using Inferno and contributing to its development. The following is a quick set of guidelines designed to maximise your contribution's effectiveness. Got a question or need help? ---------------------------- If you're having trouble getting Inferno to do what you want, there are a couple of places to get help before submitting an issue: * [Stack Overflow questions tagged infernojs](http://stackoverflow.com/questions/tagged/infernojs) Of course, if you've encountered a bug, then the best course of action is to raise an issue (if no-one else has!). Reporting security vulnerabilities ---------------------------------- If you think you've found a security vulnerability, please email [Dominic Gannaway](mailto:dg@domgan.com) with details, and he will respond to you if he isn't at work by that time. Repository Layout ----------------- The repository structures as a monorepo utilizing [lerna](https://github.com/lerna/lerna) as a management tool of choice. Lerna setup and linking are part of the `postinstall` task so it should be automatically run after `npm install`. `lerna` executes command based on a topological-sorted order of packages based on their dependencies. For example, if you want to see the order of packages being processed, you can do: ``` $ lerna exec -- node -e "console.log(require('./package.json').name)" inferno-shared inferno-vnode-flags inferno inferno-hyperscript inferno-create-element inferno-extras inferno-router inferno-compat inferno-server inferno-redux inferno-mobx inferno-test-utils ``` Source files are written in TypeScript and tests are written in JS/JSX consuming the dist files. Running tests ------------- Always include tests for the functionality you want to add into Inferno. This way we can avoid regression in future. Make sure you have lerna tool installed globally. ``` npm i -g lerna ``` - Clone the repository, and clean it. `lerna clean` - Install development dependencies `npm i` - build typescript files `npm run build` - run tests `npm run test` Pull requests ------------- All pull requests are welcome. *Caveat for what follows: If in doubt, submit the request - a PR that needs tweaking is infinitely more valuable than a request that wasn't made because you were worrying about meeting these requirements.* Before submitting, run `npm run build` (which will concatenate, lint and test the code) to ensure the build passes - but don't include files from outside the `src` and `test` folders in the PR. And make sure the PR haven't been published before! There isn't (yet) a formal style guide for Inferno, so please take care to adhere to existing conventions: * 2-space indentation, not tabs! * Semi-colons * Single-quotes for strings Above all, code should be clean and readable, and commented where necessary. If you add a new feature, make sure you add a test to go along with it! Before you commit your changes, please run `npm run prettier` to format code correctly Small print ----------- There's no contributor license agreement - contributions are made on a common sense basis. Inferno is distributed under the MIT license, which means your contributions will be too. Debugging Browser ----------------- Just run `npm run test:browser:debug` Open localhost:9876 and click debug! Debugging NodeJS ---------------- Its possible to debug inferno tests by running following command `npm run debug` and open chrome web address: chrome://inspect/#devices Pro tip: You can filter down number of tests by editing `debug` -task: `node --inspect-brk ./node_modules/.bin/jest {*edit this*} --runInBand --no-cache --no-watchman` Change parameter to jest to match only files you want to run. Happy debugging! ## Credits ### Contributors Thank you to all the people who have already contributed to inferno! ### Backers Thank you to all our backers! [[Become a backer](https://opencollective.com/inferno#backer)] ### Sponsors Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/inferno#sponsor)) ================================================ FILE: LICENSE.md ================================================ # MIT License Copyright (c) 2015-2022 Dominic Gannaway Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Inferno

[![Build Status](https://github.com/infernojs/inferno/workflows/CI/badge.svg)](https://github.com/infernojs/inferno/actions) [![Coverage Status](https://img.shields.io/coveralls/infernojs/inferno/master.svg?style=flat-square)](https://coveralls.io/github/infernojs/inferno?branch=master) [![MIT](https://img.shields.io/npm/l/inferno.svg?style=flat-square)](https://github.com/infernojs/inferno/blob/master/LICENSE.md) [![NPM](https://img.shields.io/npm/v/inferno.svg?style=flat-square)](https://www.npmjs.com/package/inferno) [![npm downloads](https://img.shields.io/npm/dm/inferno.svg?style=flat-square)](https://www.npmjs.org/package/inferno) [![Discord](https://img.shields.io/discord/825669396823015496.svg?style=flat-square&label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/SUKuhgaBpF) [![gzip size](http://img.badgesize.io/https://unpkg.com/inferno/dist/inferno.min.js?compression=gzip)](https://unpkg.com/inferno/dist/inferno.min.js) [![Backers on Open Collective](https://opencollective.com/inferno/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/inferno/sponsors/badge.svg)](#sponsors) Inferno is an insanely fast, React-like library for building high-performance user interfaces on both the client and server. ## Description The main objective of the InfernoJS project is to provide the fastest possible **runtime** performance for web applications. Inferno excels at rendering real time data views or large DOM trees. The performance is achieved through multiple optimizations, for example: - Inferno's own JSX compilers creates monomorphic `createVNode` calls, instead of `createElement` calls. Optimizing runtime performance of the application. - [SWC plugin inferno](https://github.com/infernojs/swc-plugin-inferno) is a plugin for [SWC](https://swc.rs/). It can compile TSX and JSX - [Babel plugin inferno](https://github.com/infernojs/babel-plugin-inferno) is a plugin for [BabelJs](https://babeljs.io/). It can compile JSX. - [TS plugin inferno](https://github.com/infernojs/ts-plugin-inferno) is a plugin for [TSC](https://www.typescriptlang.org/). It can compile TSX. - Inferno's diff process uses bitwise flags to memoize the shape of objects - Child nodes are normalized only when needed - Special JSX flags can be used during compile time to optimize runtime performance at application level - Many micro optimizations ## Features - Component driven + one-way data flow architecture - React-like API, concepts and component lifecycle events - Partial synthetic event system, normalizing events for better cross browser support - Inferno's [`linkEvent`](https://github.com/infernojs/inferno/blob/master/README.md#linkevent-package-inferno) feature removes the need to use arrow functions or binding event callbacks - Isomorphic rendering on both client and server with `inferno-server` - Unlike React and Preact, Inferno has lifecycle events on functional components - Unlike Preact and other React-like libraries, Inferno has controlled components for input/select/textarea elements - Components can be rendered outside their current html hierarchy using `createPortal` - API - Support for [older browsers](https://github.com/infernojs/inferno#browser-support) without any polyfills - defaultHooks for Functional components, this way re-defining lifecycle events per usage can be avoided - Inferno supports setting styles using string `
` or using object literal syntax `
`. For camelCase syntax support see [`inferno-compat`](https://github.com/infernojs/inferno/tree/master/packages/inferno-compat). - Fragments (v6) - createRef and forwardRef APIs (v6) - componentDidAppear, componentWillDisappear and componentWillMove (v8) - class and function component callbacks to ease animation work, see [inferno-animation](https://github.com/infernojs/inferno/tree/master/packages/inferno-animation) package ## Runtime requirements Inferno v9 requires following features to be present in the executing runtime: - `Promise` - `String.prototype.includes()` - `String.prototype.startsWith()` - `Array.prototype.includes()` - `Object.spread()` - `for ... of` ## Browser support Since version 4 we have started running our test suite **without** any polyfills. Inferno is now part of [Saucelabs](https://saucelabs.com/) open source program and we use their service for executing the tests. InfernoJS is actively tested with browsers listed below, however it may run well on older browsers as well. This is due to limited support of browser versions in recent testing frameworks. https://github.com/jasmine/jasmine/blob/main/release_notes/5.0.0.md [![Browser Test Status](https://app.saucelabs.com/browser-matrix/Havunen.svg)](https://app.saucelabs.com/open_sauce/user/Havunen/tests/vdc) ## Migration guides - [Inferno v4](https://github.com/infernojs/inferno/blob/master/documentation/v4-migration.md) - [Inferno v6](https://github.com/infernojs/inferno/blob/master/documentation/v6-migration.md) ## Benchmarks Live examples at [https://infernojs.github.io/inferno](https://infernojs.github.io/inferno) - [UI Bench](https://localvoid.github.io/uibench/) - [dbmonster](https://infernojs.github.io/inferno/dbmonster/) - [JS Web Frameworks Benchmark (current)](https://krausest.github.io/js-framework-benchmark/current.html) - [Isomorphic-UI-Benchmark](https://github.com/marko-js/isomorphic-ui-benchmarks) - [1k Components](https://infernojs.github.io/inferno/1kcomponents/) ## Code Example Let's start with some code. As you can see, Inferno intentionally keeps the same design ideas as React regarding components: one-way data flow and separation of concerns. In these examples, JSX is used via the [Inferno JSX Babel Plugin](https://github.com/infernojs/babel-plugin-inferno) to provide a simple way to express Inferno virtual DOM. You do not need to use JSX, it's completely **optional**, you can use [hyperscript](https://github.com/infernojs/inferno/tree/master/packages/inferno-hyperscript) or [createElement](https://github.com/infernojs/inferno/tree/master/packages/inferno-create-element) (like React does). Keep in mind that compile time optimizations are available only for JSX. ```jsx import { render } from 'inferno'; const message = "Hello world"; render( , document.getElementById("app") ); ``` Furthermore, Inferno also uses ES6 components like React: ```jsx import { render, Component } from 'inferno'; class MyComponent extends Component { constructor(props) { super(props); this.state = { counter: 0 }; } render() { return (

Header!

Counter is at: { this.state.counter }
); } } render( , document.getElementById("app") ); ``` Because performance is an important aspect of this library, we want to show you how to optimize your application even further. In the example below we optimize diffing process by using JSX **$HasVNodeChildren** and **$HasTextChildren** to predefine children shape compile time. In the MyComponent render method there is a div that contains JSX expression `node` as its content. Due to dynamic nature of Javascript that variable `node` could be anything and Inferno needs to go through the normalization process to make sure there are no nested arrays or other invalid data. Inferno offers a feature called ChildFlags for application developers to pre-define the shape of vNode's child node. In this example case it is using `$HasVNodeChildren` to tell the JSX compiler, that this vNode contains only single element or component vNode. Now inferno will not go into the normalization process runtime, but trusts the developer decision about the shape of the object and correctness of data. If this contract is not kept and `node` variable contains invalid value for the pre-defined shape (fe. `null`), then application would crash runtime. There is also span-element in the same render method, which content is set dynamically through `_getText()` method. There `$HasTextChildren` child-flag fits nicely, because the content of that given "span" is never anything else than text. All the available child flags are documented [here](https://infernojs.org/docs/guides/optimizations). ```jsx import { createTextVNode, render, Component } from 'inferno'; class MyComponent extends Component { constructor(props) { super(props); this.state = { counter: 0 }; } _getText() { return 'Hello!'; } render() { const node = this.state.counter > 0 ?
0
: {this._getText()}; return (

Header!

{node}
); } } render( , document.getElementById("app") ); ``` ### Tear down To tear down inferno application you need to render null on root element. Rendering `null` will trigger unmount lifecycle hooks for whole vDOM tree and remove global event listeners. It is important to unmount unused vNode trees to free browser memory. ```jsx import { createTextVNode, render, Component } from 'inferno'; const rootElement = document.getElementById("app"); // Start the application render( , rootElement ); // Tear down render( null, rootElement ); ``` ### More Examples If you have built something using Inferno you can add them here: - [**Simple Clock** (@JSFiddle)](https://jsfiddle.net/4bha7kcg/) - [**Simple JS Counter** (@github/scorsi)](https://github.com/scorsi/simple-counter-inferno-cerebral-fusebox): SSR Inferno (view) + Cerebral (state manager) + FuseBox (build system/bundler) - [**Online interface to TMDb movie database** (@codesandbox.io)](https://codesandbox.io/s/9zjo5yx8po): Inferno + [Inferno hyperscript](https://github.com/infernojs/inferno) (view) + [Superagent](https://github.com/visionmedia/superagent) (network requests) + Web component ([custom elements v1](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)) + [state-transducer](https://github.com/brucou/state-transducer) (state machine library) - [**Lemmy - a self-hostable reddit alternative** (front end in Inferno)](https://github.com/dessalines/lemmy) ## Getting Started The easiest way to get started with Inferno is by using [Create Inferno App](https://github.com/infernojs/create-inferno-app). Alternatively, you can try any of the following: * the [Inferno Boilerplate](https://github.com/infernojs/inferno-boilerplate) for a very simple setup. * for a more advanced example demonstrating how Inferno might be used, we recommend trying out [Inferno Starter Project](https://github.com/nightwolfz/inferno-starter) by [nightwolfz](https://github.com/nightwolfz/). * for using Inferno to build a mobile app, try [Inferno Mobile Starter Project](https://github.com/Rudy-Zidan/inferno-mobile) by [Rudy-Zidan](https://github.com/Rudy-Zidan). * for [TypeScript](https://www.typescriptlang.org/) support and bundling, check out [ts-plugin-inferno](https://github.com/infernojs/ts-plugin-inferno), or [inferno-typescript-example](https://github.com/infernojs/inferno-typescript-example). * for an example of how to use Inferno in [codesandbox](https://codesandbox.io/): https://codesandbox.io/s/znmyj24w4p * for using [parcel and typescript](https://github.com/jayy-lmao/inferno-parcel-ts) Core package: ```sh npm install --save inferno ``` Addons: ```sh # server-side rendering npm install --save inferno-server # routing npm install --save inferno-router ``` Pre-bundled files for browser consumption can be found on [our cdnjs](https://cdnjs.com/libraries/inferno): Or on jsDelivr: ``` https://cdn.jsdelivr.net/npm/inferno@latest/dist/inferno.min.js ``` Or on unpkg.com: ``` https://unpkg.com/inferno@latest/dist/inferno.min.js ``` ### Creating Virtual DOM #### JSX: ```sh npm install --save-dev babel-plugin-inferno ``` #### Hyperscript: ```sh npm install --save inferno-hyperscript ``` #### createElement: ```sh npm install --save inferno-create-element ``` ### Compatibility with existing React apps ```sh npm install --save-dev inferno-compat ``` Note: Make sure you read more about [`inferno-compat`](https://github.com/infernojs/inferno/tree/master/packages/inferno-compat) before using it. ## Third-party state libraries Inferno now has bindings available for some of the major state management libraries out there: - Redux via [`inferno-redux`](https://github.com/infernojs/inferno/tree/dev/packages/inferno-redux) - MobX via [`inferno-mobx`](https://github.com/infernojs/inferno/tree/dev/packages/inferno-mobx) - Cerebral via [`@cerebral/inferno`](https://github.com/cerebral/cerebral/tree/master/packages/node_modules/@cerebral/inferno) ## JSX Inferno has its own [JSX Babel plugin](https://github.com/trueadm/babel-plugin-inferno). ## Differences from React - Inferno doesn't have a fully synthetic event system like React does. Inferno has a partially synthetic event system, instead opting to only delegate certain events (such as `onClick`). - Inferno doesn't support React Native. Inferno was only designed for the browser/server with the DOM in mind. - Inferno doesn't support legacy string refs, use `createRef` or callback `ref` API - Inferno provides lifecycle events on functional components. This is a major win for people who prefer lightweight components rather than ES2015 classes. ## Differences from Preact - Inferno has a partial synthetic event system, resulting in better performance via delegation of certain events. - Inferno is *much* faster than Preact in rendering, updating and removing elements from the DOM. Inferno diffs against virtual DOM, rather than the real DOM (except when loading from server-side rendered content), which means it can make drastic improvements. Unfortunately, diffing against the real DOM has a 30-40% overhead cost in operations. - Inferno fully supports controlled components for `input`/`select`/`textarea` elements. This prevents lots of edgecases where the virtual DOM is not the source of truth (it should always be). Preact pushes the source of truth to the DOM itself. - Inferno provides lifecycle events on functional components. This is a major win for people who prefer lightweight components rather than ES2015 classes. ## Event System Like React, Inferno also uses a light-weight synthetic event system in certain places (although both event systems differ massively). Inferno's event system provides highly efficient delegation and an event helper called [`linkEvent`](https://github.com/infernojs/inferno/blob/master/README.md#linkevent-package-inferno). One major difference between Inferno and React is that Inferno does not rename events or change how they work by default. Inferno only specifies that events should be camel cased, rather than lower case. Lower case events will bypass Inferno's event system in favour of using the native event system supplied by the browser. For example, when detecting changes on an `` element, in React you'd use `onChange`, with Inferno you'd use `onInput` instead (the native DOM event is `oninput`). Available synthetic events are: - `onClick` - `onDblClick` - `onFocusIn` - `onFocusOut` - `onKeyDown` - `onKeyPress` - `onKeyUp` - `onMouseDown` - `onMouseMove` - `onMouseUp` - `onTouchEnd` - `onTouchMove` - `onTouchStart` ### `linkEvent` (package: `inferno`) `linkEvent()` is a helper function that allows attachment of `props`/`state`/`context` or other data to events without needing to `bind()` them or use arrow functions/closures. This is extremely useful when dealing with events in functional components. Below is an example: ```jsx import { linkEvent } from 'inferno'; function handleClick(props, event) { props.validateValue(event.target.value); } function MyComponent(props) { return
; } ``` This is an example of using it with ES2015 classes: ```jsx import { linkEvent, Component } from 'inferno'; function handleClick(instance, event) { instance.setState({ data: event.target.value }); } class MyComponent extends Component { render () { return
; } } ``` `linkEvent()` offers better performance than binding an event in a class constructor and using arrow functions, so use it where possible. ## Controlled Components In HTML, form elements such as ``, `, container); }).toThrow( constructInfernoError("textarea elements can't have children."), ); }); it('Media elements cannot have children', () => { expect(() => { render(foobar, container); }).toThrow(constructInfernoError("media elements can't have children.")); }); it('< BR > elements cannot have children', () => { expect(() => { render(
foobar
, container); }).toThrow(constructInfernoError("br elements can't have children.")); }); it('< img > elements cannot have children', () => { expect(() => { render(foobar, container); }).toThrow(constructInfernoError("img elements can't have children.")); }); }); }); ================================================ FILE: packages/inferno/index.cjs ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./dist/index.min.cjs'); } else { module.exports = require('./dist/index.cjs'); } ================================================ FILE: packages/inferno/index.mjs ================================================ export * from './dist/index.mjs'; if (process.env.NODE_ENV !== 'production') { console.warn( 'You are running production build of Inferno in development mode. Use dev:module entry point.', ); } ================================================ FILE: packages/inferno/package.json ================================================ { "name": "inferno", "version": "9.0.11", "license": "MIT", "type": "module", "description": "An extremely fast, React-like JavaScript library for building modern user interfaces", "author": { "name": "Dominic Gannaway", "email": "dg@domgan.com" }, "bugs": { "url": "https://github.com/infernojs/inferno/issues" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/inferno" }, "homepage": "https://github.com/infernojs/inferno#readme", "keywords": [ "inferno", "performance", "framework", "interfaces", "user interfaces", "html", "renderToString", "server", "dom", "browser", "rollup", "vdom" ], "collective": { "type": "opencollective", "url": "https://opencollective.com/inferno", "logo": "https://opencollective.com/inferno/logo.txt" }, "scripts": { "postinstall": "opencollective-postinstall" }, "rollup": { "bundledDependencies": [ "inferno-shared", "inferno-vnode-flags" ], "moduleName": "Inferno" }, "files": [ "index.cjs", "index.mjs", "dist/", "README.md", "package.json" ], "exports": { ".": { "import": "./index.mjs", "require": "./index.cjs", "types": "./dist/index.d.ts" }, "./dist/index.dev.mjs": "./dist/index.dev.mjs", "./dist/index.mjs": "./dist/index.mjs" }, "module": "index.mjs", "dev:module": "dist/index.dev.mjs", "typings": "dist/index.d.ts", "repository": { "type": "git", "url": "https://github.com/infernojs/inferno.git", "directory": "packages/inferno" }, "devDependencies": { "inferno-shared": "9.0.11", "inferno-utils": "9.0.11" }, "dependencies": { "csstype": "^3.2.3", "inferno-vnode-flags": "9.0.11", "opencollective-postinstall": "^2.0.3" } } ================================================ FILE: packages/inferno/src/DOM/constants.ts ================================================ export const xlinkNS = 'http://www.w3.org/1999/xlink'; export const xmlNS = 'http://www.w3.org/XML/1998/namespace'; export const namespaces: Record = { 'xlink:actuate': xlinkNS, 'xlink:arcrole': xlinkNS, 'xlink:href': xlinkNS, 'xlink:role': xlinkNS, 'xlink:show': xlinkNS, 'xlink:title': xlinkNS, 'xlink:type': xlinkNS, 'xml:base': xmlNS, 'xml:lang': xmlNS, 'xml:space': xmlNS, }; ================================================ FILE: packages/inferno/src/DOM/events/attachEvent.ts ================================================ import { isFunction } from 'inferno-shared'; export function attachEvent(dom, eventName, handler): void { const previousKey = `$${eventName}`; const previousArgs = dom[previousKey]; if (previousArgs) { if (previousArgs[1].wrapped) { return; } dom.removeEventListener(previousArgs[0], previousArgs[1]); dom[previousKey] = null; } if (isFunction(handler)) { dom.addEventListener(eventName, handler); dom[previousKey] = [eventName, handler]; } } ================================================ FILE: packages/inferno/src/DOM/events/delegation.ts ================================================ import type { LinkedEvent, SemiSyntheticEvent } from './../../core/types'; import { isFunction, isNull, isNullOrUndef } from 'inferno-shared'; import { isLastValueSameLinkEvent, normalizeEventName, } from './../utils/common'; import { isLinkEventObject } from './linkEvent'; interface IEventData { dom: Element; } export interface DelegateEventTypes { onClick: unknown; onDblClick: unknown; onFocusIn: unknown; onFocusOut: unknown; onKeyDown: unknown; onKeyPress: unknown; onKeyUp: unknown; onMouseDown: unknown; onMouseMove: unknown; onMouseUp: unknown; onTouchEnd: unknown; onTouchMove: unknown; onTouchStart: unknown; } function getDelegatedEventObject(v: unknown): DelegateEventTypes { return { onClick: v, onDblClick: v, onFocusIn: v, onFocusOut: v, onKeyDown: v, onKeyPress: v, onKeyUp: v, onMouseDown: v, onMouseMove: v, onMouseUp: v, onTouchEnd: v, onTouchMove: v, onTouchStart: v, }; } const attachedEventCounts = getDelegatedEventObject(0); const attachedEvents = getDelegatedEventObject(null); export const syntheticEvents = getDelegatedEventObject(true); function updateOrAddSyntheticEvent(name: string, dom): DelegateEventTypes { let eventsObject = dom.$EV; if (!eventsObject) { eventsObject = dom.$EV = getDelegatedEventObject(null); } if (!eventsObject[name]) { if (++attachedEventCounts[name] === 1) { attachedEvents[name] = attachEventToDocument(name); } } return eventsObject; } export function unmountSyntheticEvent(name: string, dom): void { const eventsObject = dom.$EV; if (eventsObject?.[name]) { if (--attachedEventCounts[name] === 0) { document.removeEventListener( normalizeEventName(name), attachedEvents[name], ); attachedEvents[name] = null; } eventsObject[name] = null; } } export function handleSyntheticEvent( name: string, lastEvent: (() => void) | LinkedEvent | null | false | true, nextEvent: (() => void) | LinkedEvent | null | false | true, dom, ): void { if (isFunction(nextEvent)) { updateOrAddSyntheticEvent(name, dom)[name] = nextEvent; } else if (isLinkEventObject(nextEvent)) { if (isLastValueSameLinkEvent(lastEvent, nextEvent)) { return; } updateOrAddSyntheticEvent(name, dom)[name] = nextEvent; } else { unmountSyntheticEvent(name, dom); } } // TODO: When browsers fully support event.composedPath we could loop it through instead of using parentNode property function getTargetNode(event): any { return isFunction(event.composedPath) ? event.composedPath()[0] : event.target; } function dispatchEvents( event: SemiSyntheticEvent, isClick: boolean, name: string, eventData: IEventData, ): void { let dom = getTargetNode(event); do { // Html Nodes can be nested fe: span inside button in that scenario browser does not handle disabled attribute on parent, // because the event listener is on document.body // Don't process clicks on disabled elements if (isClick && dom.disabled) { return; } const eventsObject = dom.$EV; if (!isNullOrUndef(eventsObject)) { const currentEvent = eventsObject[name]; if (currentEvent) { // linkEvent object eventData.dom = dom; if (currentEvent.event) { currentEvent.event(currentEvent.data, event); } else { currentEvent(event); } if (event.cancelBubble) { return; } } } dom = dom.parentNode; } while (!isNull(dom)); } function stopPropagation(): void { this.cancelBubble = true; if (!this.immediatePropagationStopped) { this.stopImmediatePropagation(); } } function isDefaultPrevented(): boolean { return this.defaultPrevented; } function isPropagationStopped(): boolean { return this.cancelBubble; } function extendEventProperties(event): IEventData { // Event data needs to be an object to save reference to currentTarget getter const eventData: IEventData = { dom: document as any, }; event.isDefaultPrevented = isDefaultPrevented; event.isPropagationStopped = isPropagationStopped; event.stopPropagation = stopPropagation; Object.defineProperty(event, 'currentTarget', { configurable: true, get: function get() { return eventData.dom; }, }); return eventData; } function rootEvent(name: string): (event: SemiSyntheticEvent) => void { const isClick = name === 'onClick' || name === 'onDblClick'; return function (event: SemiSyntheticEvent) { dispatchEvents(event, isClick, name, extendEventProperties(event)); }; } function attachEventToDocument( name: string, ): (event: SemiSyntheticEvent) => void { const attachedEvent = rootEvent(name); document.addEventListener(normalizeEventName(name), attachedEvent); return attachedEvent; } ================================================ FILE: packages/inferno/src/DOM/events/linkEvent.ts ================================================ import type { LinkedEvent } from '../../core/types'; import { isFunction, isNull } from 'inferno-shared'; /** * Links given data to event as first parameter * @param {*} data data to be linked, it will be available in function as first parameter * @param {Function} callback Function to be called when event occurs * @returns {{data: *, event: Function}} */ export function linkEvent( data: T, callback: (data: T, event: E) => void, ): LinkedEvent | null { if (isFunction(callback)) { return { data, event: callback }; } return null; // Return null when event is invalid, to avoid creating unnecessary event handlers } // object.event should always be function, otherwise its badly created object. export function isLinkEventObject(o): o is LinkedEvent { return !isNull(o) && typeof o === 'object'; } ================================================ FILE: packages/inferno/src/DOM/mounting.ts ================================================ import type { VNode, ContextObject } from '../core/types'; import { isFunction, isNull, isNullOrUndef, isString, isStringOrNumber, throwError, } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { createVoidVNode, directClone, normalizeRoot, } from '../core/implementation'; import { AnimationQueues, documentCreateElement, EMPTY_OBJ, findDOMFromVNode, insertOrAppend, safeCall1, setTextContent, } from './utils/common'; import { mountProps } from './props'; import { createClassComponentInstance, renderFunctionalComponent, } from './utils/componentUtil'; import { validateKeys } from '../core/validate'; import { mountRef } from '../core/refs'; export function mount( vNode: VNode, parentDOM: Element | null, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const flags = (vNode.flags |= VNodeFlags.InUse); if ((flags & VNodeFlags.Element) !== 0) { mountElement( vNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else if ((flags & VNodeFlags.ComponentClass) !== 0) { mountClassComponent( vNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else if (flags & VNodeFlags.ComponentFunction) { mountFunctionalComponent( vNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else if (flags & VNodeFlags.Text) { mountText(vNode, parentDOM, nextNode); } else if (flags & VNodeFlags.Fragment) { mountFragment( vNode, context, parentDOM, isSVG, nextNode, lifecycle, animations, ); } else if (flags & VNodeFlags.Portal) { mountPortal(vNode, context, parentDOM, nextNode, lifecycle, animations); } else if (process.env.NODE_ENV !== 'production') { // Development validation, in production we don't need to throw because it crashes anyway if (typeof vNode === 'object') { throwError( `mount() received an object that's not a valid VNode, you should stringify it first, fix createVNode flags or call normalizeChildren. Object: "${JSON.stringify( vNode, )}".`, ); } else { throwError( `mount() expects a valid VNode, instead it received an object with the type "${typeof vNode}".`, ); } } } function mountPortal( vNode, context, parentDOM: Element | null, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { mount( vNode.children as VNode, vNode.ref, context, false, null, lifecycle, animations, ); const placeHolderVNode = createVoidVNode(); mountText(placeHolderVNode, parentDOM, nextNode); vNode.dom = placeHolderVNode.dom; } function mountFragment( vNode, context, parentDOM: Element | null, isSVG, nextNode, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { let children = vNode.children; let childFlags = vNode.childFlags; // When fragment is optimized for multiple children, check if there is no children and change flag to invalid // This is the only normalization always done, to keep optimization flags API same for fragments and regular elements if (childFlags & ChildFlags.MultipleChildren && children.length === 0) { childFlags = vNode.childFlags = ChildFlags.HasVNodeChildren; children = vNode.children = createVoidVNode(); } if (childFlags === ChildFlags.HasVNodeChildren) { mount( children as VNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else { mountArrayChildren( children, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } } export function mountText( vNode: VNode, parentDOM: Element | null, nextNode: Element | null, ): void { const dom = (vNode.dom = document.createTextNode( vNode.children as string, ) as any); if (!isNull(parentDOM)) { insertOrAppend(parentDOM, dom, nextNode); } } export function mountElement( vNode: VNode, parentDOM: Element | null, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const flags = vNode.flags; const props = vNode.props; const className = vNode.className; const childFlags = vNode.childFlags; const dom = (vNode.dom = documentCreateElement( vNode.type, (isSVG = isSVG || (flags & VNodeFlags.SvgElement) > 0), )); let children = vNode.children; if (!isNullOrUndef(className) && className !== '') { if (isSVG) { dom.setAttribute('class', className); } else { dom.className = className; } } if (process.env.NODE_ENV !== 'production') { validateKeys(vNode); } if (childFlags === ChildFlags.HasTextChildren) { setTextContent(dom, children as string); } else if (childFlags !== ChildFlags.HasInvalidChildren) { const childrenIsSVG = isSVG && vNode.type !== 'foreignObject'; if (childFlags === ChildFlags.HasVNodeChildren) { if ((children as VNode).flags & VNodeFlags.InUse) { vNode.children = children = directClone(children as VNode); } mount( children as VNode, dom, context, childrenIsSVG, null, lifecycle, animations, ); } else if ( childFlags === ChildFlags.HasKeyedChildren || childFlags === ChildFlags.HasNonKeyedChildren ) { mountArrayChildren( children, dom, context, childrenIsSVG, null, lifecycle, animations, ); } } if (!isNull(parentDOM)) { insertOrAppend(parentDOM, dom, nextNode); } if (!isNull(props)) { mountProps(vNode, flags, props, dom, isSVG, animations); } if (process.env.NODE_ENV !== 'production') { if (isString(vNode.ref)) { throwError( 'string "refs" are not supported in Inferno 1.0. Use callback ref or Inferno.createRef() API instead.', ); } } mountRef(vNode.ref, dom, lifecycle); } export function mountArrayChildren( children, dom: Element | null, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { for (let i = 0; i < children.length; ++i) { let child = children[i]; if (child.flags & VNodeFlags.InUse) { children[i] = child = directClone(child); } mount(child, dom, context, isSVG, nextNode, lifecycle, animations); } } export function mountClassComponent( vNode: VNode, parentDOM: Element | null, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const instance = createClassComponentInstance( vNode, vNode.type, vNode.props || EMPTY_OBJ, context, isSVG, lifecycle, ); // If we have a componentDidAppear on this component, we shouldn't allow children to animate so we're passing an dummy animations queue let childAnimations = animations; if (isFunction(instance.componentDidAppear)) { childAnimations = new AnimationQueues(); } mount( instance.$LI, parentDOM, instance.$CX, isSVG, nextNode, lifecycle, childAnimations, ); mountClassComponentCallbacks(vNode.ref, instance, lifecycle, animations); } export function mountFunctionalComponent( vNode: VNode, parentDOM: Element | null, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const ref = vNode.ref; // If we have a componentDidAppear on this component, we shouldn't allow children to animate so we're passing an dummy animations queue let childAnimations = animations; if (!isNullOrUndef(ref) && isFunction(ref.onComponentDidAppear)) { childAnimations = new AnimationQueues(); } mount( (vNode.children = normalizeRoot(renderFunctionalComponent(vNode, context))), parentDOM, context, isSVG, nextNode, lifecycle, childAnimations, ); mountFunctionalComponentCallbacks(vNode, lifecycle, animations); } function createClassMountCallback(instance) { return () => { instance.componentDidMount(); }; } function addAppearAnimationHookClass( animations: AnimationQueues, instance, dom: Element, ): void { animations.componentDidAppear.push(() => { instance.componentDidAppear(dom); }); } function addAppearAnimationHookFunctional( animations: AnimationQueues, ref, dom: Element, props, ): void { animations.componentDidAppear.push(() => { ref.onComponentDidAppear(dom, props); }); } export function mountClassComponentCallbacks( ref, instance, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { mountRef(ref, instance, lifecycle); if (process.env.NODE_ENV !== 'production') { if (isStringOrNumber(ref)) { throwError( 'string "refs" are not supported in Inferno 1.0. Use callback ref or Inferno.createRef() API instead.', ); } else if ( !isNullOrUndef(ref) && typeof ref === 'object' && ref.current === void 0 ) { throwError( 'functional component lifecycle events are not supported on ES2015 class components.', ); } } if (isFunction(instance.componentDidMount)) { lifecycle.push(createClassMountCallback(instance)); } if (isFunction(instance.componentDidAppear)) { addAppearAnimationHookClass(animations, instance, instance.$LI.dom); } } function createOnMountCallback(ref, vNode) { return () => { ref.onComponentDidMount( findDOMFromVNode(vNode, true), vNode.props || EMPTY_OBJ, ); }; } export function mountFunctionalComponentCallbacks( vNode: VNode, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const ref = vNode.ref; if (!isNullOrUndef(ref)) { safeCall1(ref.onComponentWillMount, vNode.props || EMPTY_OBJ); if (isFunction(ref.onComponentDidMount)) { lifecycle.push(createOnMountCallback(ref, vNode)); } if (isFunction(ref.onComponentDidAppear)) { addAppearAnimationHookFunctional( animations, ref, findDOMFromVNode(vNode, true) as Element, vNode.props, ); } } } ================================================ FILE: packages/inferno/src/DOM/patching.ts ================================================ import type { ContextObject, VNode } from '../core/types'; import { isFunction, isInvalid, isNull, isNullOrUndef } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { createVoidVNode, directClone, normalizeRoot, } from '../core/implementation'; import type { Component } from './../core/component'; import { mount, mountArrayChildren } from './mounting'; import { clearDOM, remove, removeAllChildren, unmount, unmountAllChildren, } from './unmounting'; import { type AnimationQueues, appendChild, callAllMoveAnimationHooks, createDerivedState, EMPTY_OBJ, findDOMFromVNode, moveVNodeDOM, removeChild, removeVNodeDOM, replaceChild, setTextContent, } from './utils/common'; import { isControlledFormElement, processElement, } from './wrappers/processElement'; import { patchProp } from './props'; import { renderFunctionalComponent, renderNewInput, } from './utils/componentUtil'; import { validateKeys } from '../core/validate'; import { mountRef, unmountRef } from '../core/refs'; function replaceWithNewNode( lastVNode, nextVNode, parentDOM: Element, context: ContextObject, isSVG: boolean, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { unmount(lastVNode, animations); if ((nextVNode.flags & lastVNode.flags & VNodeFlags.DOMRef) !== 0) { mount(nextVNode, null, context, isSVG, null, lifecycle, animations); // Single DOM operation, when we have dom references available replaceChild(parentDOM, nextVNode.dom, lastVNode.dom); } else { mount( nextVNode, parentDOM, context, isSVG, findDOMFromVNode(lastVNode, true), lifecycle, animations, ); removeVNodeDOM(lastVNode, parentDOM, animations); } } export function patch( lastVNode: VNode, nextVNode: VNode, parentDOM: Element, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const nextFlags = (nextVNode.flags |= VNodeFlags.InUse); if ( lastVNode.flags !== nextFlags || lastVNode.type !== nextVNode.type || lastVNode.key !== nextVNode.key || nextFlags & VNodeFlags.ReCreate ) { if (lastVNode.flags & VNodeFlags.InUse) { replaceWithNewNode( lastVNode, nextVNode, parentDOM, context, isSVG, lifecycle, animations, ); } else { // Last vNode is not in use, it has crashed at application level. Just mount nextVNode and ignore last one mount( nextVNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } } else if (nextFlags & VNodeFlags.Element) { patchElement(lastVNode, nextVNode, context, isSVG, lifecycle, animations); } else if (nextFlags & VNodeFlags.ComponentClass) { patchClassComponent( lastVNode, nextVNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else if (nextFlags & VNodeFlags.ComponentFunction) { patchFunctionalComponent( lastVNode, nextVNode, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } else if (nextFlags & VNodeFlags.Text) { patchText(lastVNode, nextVNode); } else if (nextFlags & VNodeFlags.Fragment) { patchFragment( lastVNode, nextVNode, parentDOM, context, isSVG, lifecycle, animations, ); } else { patchPortal(lastVNode, nextVNode, context, lifecycle, animations); } } export function patchSingleTextChild( lastChildren, nextChildren, parentDOM: Element, ): void { if (lastChildren !== nextChildren) { if (lastChildren !== '') { (parentDOM.firstChild as Node).nodeValue = nextChildren; } else { setTextContent(parentDOM, nextChildren); } } } function patchContentEditableChildren(dom, nextChildren): void { if (dom.textContent !== nextChildren) { dom.textContent = nextChildren; } } function patchFragment( lastVNode: VNode, nextVNode: VNode, parentDOM: Element, context: ContextObject, isSVG: boolean, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const lastChildren = lastVNode.children as VNode[]; let nextChildren = nextVNode.children as any; const lastChildFlags = lastVNode.childFlags; let nextChildFlags = nextVNode.childFlags; let nextNode: Element | null = null; // When fragment is optimized for multiple children, check if there is no children and change flag to invalid // This is the only normalization always done, to keep optimization flags API same for fragments and regular elements if ( nextChildFlags & ChildFlags.MultipleChildren && nextChildren.length === 0 ) { nextChildFlags = nextVNode.childFlags = ChildFlags.HasVNodeChildren; nextChildren = nextVNode.children = createVoidVNode(); } const nextIsSingle: boolean = (nextChildFlags & ChildFlags.HasVNodeChildren) !== 0; if (lastChildFlags & ChildFlags.MultipleChildren) { const lastLen = lastChildren.length; // We need to know Fragment's edge node when if ( // It uses keyed algorithm (lastChildFlags & ChildFlags.HasKeyedChildren && nextChildFlags & ChildFlags.HasKeyedChildren) || // It transforms from many to single nextIsSingle || // It will append more nodes (!nextIsSingle && (nextChildren as VNode[]).length > lastLen) ) { // When fragment has multiple children there is always at least one vNode nextNode = (findDOMFromVNode(lastChildren[lastLen - 1], false) as Element) .nextSibling as Element | null; } } patchChildren( lastChildFlags, nextChildFlags, lastChildren, nextChildren, parentDOM, context, isSVG, nextNode, lastVNode, lifecycle, animations, ); } function patchPortal( lastVNode: VNode, nextVNode: VNode, context, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const lastContainer = lastVNode.ref as Element; const nextContainer = nextVNode.ref as Element; const nextChildren = nextVNode.children as VNode; patchChildren( lastVNode.childFlags, nextVNode.childFlags, lastVNode.children as VNode, nextChildren, lastContainer, context, false, null, lastVNode, lifecycle, animations, ); nextVNode.dom = lastVNode.dom; if (lastContainer !== nextContainer && !isInvalid(nextChildren)) { const node = nextChildren.dom as Element; removeChild(lastContainer, node); appendChild(nextContainer, node); } } export function patchElement( lastVNode: VNode, nextVNode: VNode, context: ContextObject, isSVG: boolean, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const dom = (nextVNode.dom = lastVNode.dom as Element); const lastProps = lastVNode.props; const nextProps = nextVNode.props; const nextFlags = nextVNode.flags; let isFormElement = false; let hasControlledValue = false; let nextPropsOrEmpty; isSVG = isSVG || (nextFlags & VNodeFlags.SvgElement) > 0; // inlined patchProps -- starts -- if (lastProps !== nextProps) { const lastPropsOrEmpty = lastProps || EMPTY_OBJ; nextPropsOrEmpty = nextProps || EMPTY_OBJ; if (nextPropsOrEmpty !== EMPTY_OBJ) { isFormElement = (nextFlags & VNodeFlags.FormElement) > 0; if (isFormElement) { hasControlledValue = isControlledFormElement(nextPropsOrEmpty); } for (const prop in nextPropsOrEmpty) { const lastValue = lastPropsOrEmpty[prop]; const nextValue = nextPropsOrEmpty[prop]; if (lastValue !== nextValue) { patchProp( prop, lastValue, nextValue, dom, isSVG, hasControlledValue, lastVNode, animations, ); } } } if (lastPropsOrEmpty !== EMPTY_OBJ) { for (const prop in lastPropsOrEmpty) { if ( isNullOrUndef(nextPropsOrEmpty[prop]) && !isNullOrUndef(lastPropsOrEmpty[prop]) ) { patchProp( prop, lastPropsOrEmpty[prop], null, dom, isSVG, hasControlledValue, lastVNode, animations, ); } } } } const nextChildren = nextVNode.children; const nextClassName = nextVNode.className; // inlined patchProps -- ends -- if (lastVNode.className !== nextClassName) { if (isNullOrUndef(nextClassName)) { dom.removeAttribute('class'); } else if (isSVG) { dom.setAttribute('class', nextClassName); } else { dom.className = nextClassName; } } if (process.env.NODE_ENV !== 'production') { validateKeys(nextVNode); } if (nextFlags & VNodeFlags.ContentEditable) { patchContentEditableChildren(dom, nextChildren); } else { patchChildren( lastVNode.childFlags, nextVNode.childFlags, lastVNode.children, nextChildren, dom, context, isSVG && nextVNode.type !== 'foreignObject', null, lastVNode, lifecycle, animations, ); } if (isFormElement) { processElement( nextFlags, nextVNode, dom, nextPropsOrEmpty, false, hasControlledValue, ); } const nextRef = nextVNode.ref; const lastRef = lastVNode.ref; if (lastRef !== nextRef) { unmountRef(lastRef); mountRef(nextRef, dom, lifecycle); } } function replaceOneVNodeWithMultipleVNodes( lastChildren, nextChildren, parentDOM, context, isSVG: boolean, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { unmount(lastChildren, animations); mountArrayChildren( nextChildren, parentDOM, context, isSVG, findDOMFromVNode(lastChildren, true), lifecycle, animations, ); removeVNodeDOM(lastChildren, parentDOM, animations); } function commonChildrenSwitch( lastChildren, nextChildren, parentDOM: Element, context: ContextObject, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, parentVNode: VNode, nextChildFlags: | ChildFlags.UnknownChildren | ChildFlags.HasNonKeyedChildren | ChildFlags.HasKeyedChildren | ChildFlags.MultipleChildren, lastChildFlags: | ChildFlags.UnknownChildren | ChildFlags.HasNonKeyedChildren | ChildFlags.HasKeyedChildren | ChildFlags.MultipleChildren, ): void { const lastLength = lastChildren.length | 0; const nextLength = nextChildren.length | 0; // Fast path's for both algorithms if (lastLength === 0) { if (nextLength > 0) { mountArrayChildren( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); } } else if (nextLength === 0) { removeAllChildren(parentDOM, parentVNode, lastChildren, animations); } else if ( nextChildFlags === ChildFlags.HasKeyedChildren && lastChildFlags === ChildFlags.HasKeyedChildren ) { patchKeyedChildren( lastChildren, nextChildren, parentDOM, context, isSVG, lastLength, nextLength, nextNode, parentVNode, lifecycle, animations, ); } else { patchNonKeyedChildren( lastChildren, nextChildren, parentDOM, context, isSVG, lastLength, nextLength, nextNode, lifecycle, animations, ); } } function patchChildren( lastChildFlags: ChildFlags, nextChildFlags: ChildFlags, lastChildren, nextChildren, parentDOM: Element, context: ContextObject, isSVG: boolean, nextNode: Element | null, parentVNode: VNode, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { switch (lastChildFlags) { case ChildFlags.HasVNodeChildren: switch (nextChildFlags) { case ChildFlags.HasVNodeChildren: patch( lastChildren, nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; case ChildFlags.HasInvalidChildren: remove(lastChildren, parentDOM, animations); break; case ChildFlags.HasTextChildren: unmount(lastChildren, animations); setTextContent(parentDOM, nextChildren); break; default: replaceOneVNodeWithMultipleVNodes( lastChildren, nextChildren, parentDOM, context, isSVG, lifecycle, animations, ); break; } break; case ChildFlags.HasInvalidChildren: switch (nextChildFlags) { case ChildFlags.HasVNodeChildren: mount( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; case ChildFlags.HasInvalidChildren: break; case ChildFlags.HasTextChildren: setTextContent(parentDOM, nextChildren); break; default: mountArrayChildren( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; } break; case ChildFlags.HasTextChildren: switch (nextChildFlags) { case ChildFlags.HasTextChildren: patchSingleTextChild(lastChildren, nextChildren, parentDOM); break; case ChildFlags.HasVNodeChildren: clearDOM(parentDOM, lastChildren, animations); mount( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; case ChildFlags.HasInvalidChildren: clearDOM(parentDOM, lastChildren, animations); break; default: clearDOM(parentDOM, lastChildren, animations); mountArrayChildren( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; } break; default: switch (nextChildFlags) { case ChildFlags.HasTextChildren: unmountAllChildren(lastChildren, animations); setTextContent(parentDOM, nextChildren); break; case ChildFlags.HasVNodeChildren: removeAllChildren(parentDOM, parentVNode, lastChildren, animations); mount( nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); break; case ChildFlags.HasInvalidChildren: removeAllChildren(parentDOM, parentVNode, lastChildren, animations); break; default: commonChildrenSwitch( lastChildren, nextChildren, parentDOM, context, isSVG, nextNode, lifecycle, animations, parentVNode, nextChildFlags, lastChildFlags, ); break; } break; } } function createDidUpdate( instance: Component, lastProps, lastState, snapshot, lifecycle: Array<() => void>, ): void { lifecycle.push(() => { instance.componentDidUpdate!(lastProps, lastState, snapshot); }); } export function updateClassComponent( instance, nextState, nextProps, parentDOM: Element, context, isSVG: boolean, force: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const lastState = instance.state; const lastProps = instance.props; const usesNewAPI = Boolean(instance.$N); const hasSCU = isFunction(instance.shouldComponentUpdate); if (usesNewAPI) { nextState = createDerivedState( instance, nextProps, nextState !== lastState ? { ...lastState, ...nextState } : nextState, ); } if ( force || !hasSCU || (hasSCU && instance.shouldComponentUpdate(nextProps, nextState, context)) ) { if (!usesNewAPI && isFunction(instance.componentWillUpdate)) { instance.componentWillUpdate(nextProps, nextState, context); } instance.props = nextProps; instance.state = nextState; instance.context = context; let snapshot = null; const nextInput = renderNewInput(instance, nextProps, context); if (usesNewAPI && isFunction(instance.getSnapshotBeforeUpdate)) { snapshot = instance.getSnapshotBeforeUpdate(lastProps, lastState); } patch( instance.$LI, nextInput, parentDOM, instance.$CX, isSVG, nextNode, lifecycle, animations, ); // Don't update Last input, until patch has been successfully executed instance.$LI = nextInput; if (isFunction(instance.componentDidUpdate)) { createDidUpdate(instance, lastProps, lastState, snapshot, lifecycle); } } else { instance.props = nextProps; instance.state = nextState; instance.context = context; } } function patchClassComponent( lastVNode, nextVNode, parentDOM, context, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const instance = (nextVNode.children = lastVNode.children); // If Component has crashed, ignore it to stay functional if (isNull(instance)) { return; } instance.$L = lifecycle; const nextProps = nextVNode.props || EMPTY_OBJ; const nextRef = nextVNode.ref; const lastRef = lastVNode.ref; let nextState = instance.state; if (!instance.$N) { if (isFunction(instance.componentWillReceiveProps)) { instance.$BR = true; instance.componentWillReceiveProps(nextProps, context); // If instance component was removed during its own update do nothing. if (instance.$UN) { return; } instance.$BR = false; } if (!isNull(instance.$PS)) { nextState = { ...nextState, ...instance.$PS }; instance.$PS = null; } } updateClassComponent( instance, nextState, nextProps, parentDOM, context, isSVG, false, nextNode, lifecycle, animations, ); if (lastRef !== nextRef) { unmountRef(lastRef); mountRef(nextRef, instance, lifecycle); } } function patchFunctionalComponent( lastVNode, nextVNode, parentDOM, context, isSVG: boolean, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { let shouldUpdate: boolean = true; const nextProps = nextVNode.props || EMPTY_OBJ; const nextRef = nextVNode.ref; const lastProps = lastVNode.props; const nextHooksDefined = !isNullOrUndef(nextRef); const lastInput = lastVNode.children; if (nextHooksDefined && isFunction(nextRef.onComponentShouldUpdate)) { shouldUpdate = nextRef.onComponentShouldUpdate(lastProps, nextProps); } if (shouldUpdate) { if (nextHooksDefined && isFunction(nextRef.onComponentWillUpdate)) { nextRef.onComponentWillUpdate(lastProps, nextProps); } const nextInput = normalizeRoot( renderFunctionalComponent(nextVNode, context), ); patch( lastInput, nextInput, parentDOM, context, isSVG, nextNode, lifecycle, animations, ); nextVNode.children = nextInput; if (nextHooksDefined && isFunction(nextRef.onComponentDidUpdate)) { nextRef.onComponentDidUpdate(lastProps, nextProps); } } else { nextVNode.children = lastInput; } } function patchText(lastVNode: VNode, nextVNode: VNode): void { const nextText = nextVNode.children as string; const dom = (nextVNode.dom = lastVNode.dom); if (nextText !== lastVNode.children) { (dom as Element).nodeValue = nextText; } } function patchNonKeyedChildren( lastChildren, nextChildren, dom, context: ContextObject, isSVG: boolean, lastChildrenLength: number, nextChildrenLength: number, nextNode: Element | null, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { const commonLength = lastChildrenLength > nextChildrenLength ? nextChildrenLength : lastChildrenLength; let i = 0; let nextChild; let lastChild; for (; i < commonLength; ++i) { nextChild = nextChildren[i]; lastChild = lastChildren[i]; if (nextChild.flags & VNodeFlags.InUse) { nextChild = nextChildren[i] = directClone(nextChild); } patch( lastChild, nextChild, dom, context, isSVG, nextNode, lifecycle, animations, ); lastChildren[i] = nextChild; } if (lastChildrenLength < nextChildrenLength) { for (i = commonLength; i < nextChildrenLength; ++i) { nextChild = nextChildren[i]; if (nextChild.flags & VNodeFlags.InUse) { nextChild = nextChildren[i] = directClone(nextChild); } mount(nextChild, dom, context, isSVG, nextNode, lifecycle, animations); } } else if (lastChildrenLength > nextChildrenLength) { for (i = commonLength; i < lastChildrenLength; ++i) { remove(lastChildren[i], dom, animations); } } } function patchKeyedChildren( a: VNode[], b: VNode[], dom, context, isSVG: boolean, aLength: number, bLength: number, outerEdge: Element | null, parentVNode: VNode, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { let aEnd = aLength - 1; let bEnd = bLength - 1; let j: number = 0; let aNode: VNode = a[j]; let bNode: VNode = b[j]; let nextPos: number; let nextNode; // Step 1 outer: { // Sync nodes with the same key at the beginning. while (aNode.key === bNode.key) { if (bNode.flags & VNodeFlags.InUse) { b[j] = bNode = directClone(bNode); } patch( aNode, bNode, dom, context, isSVG, outerEdge, lifecycle, animations, ); a[j] = bNode; ++j; if (j > aEnd || j > bEnd) { break outer; } aNode = a[j]; bNode = b[j]; } aNode = a[aEnd]; bNode = b[bEnd]; // Sync nodes with the same key at the end. while (aNode.key === bNode.key) { if (bNode.flags & VNodeFlags.InUse) { b[bEnd] = bNode = directClone(bNode); } patch( aNode, bNode, dom, context, isSVG, outerEdge, lifecycle, animations, ); a[aEnd] = bNode; aEnd--; bEnd--; if (j > aEnd || j > bEnd) { break outer; } aNode = a[aEnd]; bNode = b[bEnd]; } } if (j > aEnd) { if (j <= bEnd) { nextPos = bEnd + 1; nextNode = nextPos < bLength ? findDOMFromVNode(b[nextPos], true) : outerEdge; while (j <= bEnd) { bNode = b[j]; if (bNode.flags & VNodeFlags.InUse) { b[j] = bNode = directClone(bNode); } ++j; mount(bNode, dom, context, isSVG, nextNode, lifecycle, animations); } } } else if (j > bEnd) { while (j <= aEnd) { remove(a[j++], dom, animations); } } else { patchKeyedChildrenComplex( a, b, context, aLength, bLength, aEnd, bEnd, j, dom, isSVG, outerEdge, parentVNode, lifecycle, animations, ); } } function patchKeyedChildrenComplex( a: VNode[], b: VNode[], context, aLength: number, bLength: number, aEnd: number, bEnd: number, j: number, dom: Element, isSVG: boolean, outerEdge: Element | null, parentVNode: VNode, lifecycle: Array<() => void>, animations: AnimationQueues, ): void { let aNode: VNode; let bNode: VNode; // eslint-disable-next-line no-useless-assignment let nextPos: number = 0; // eslint-disable-next-line no-useless-assignment let i: number = 0; let aStart: number = j; const bStart: number = j; const aLeft: number = aEnd - j + 1; const bLeft: number = bEnd - j + 1; const sources = new Int32Array(bLeft + 1); // Keep track if it is possible to remove whole DOM using textContent = ''; let canRemoveWholeContent: boolean = aLeft === aLength; let moved: boolean = false; let pos: number = 0; let patched: number = 0; // When sizes are small, just loop them through if (bLength < 4 || (aLeft | bLeft) < 32) { for (i = aStart; i <= aEnd; ++i) { aNode = a[i]; if (patched < bLeft) { for (j = bStart; j <= bEnd; j++) { bNode = b[j]; if (aNode.key === bNode.key) { sources[j - bStart] = i + 1; if (canRemoveWholeContent) { canRemoveWholeContent = false; while (aStart < i) { remove(a[aStart++], dom, animations); } } if (pos > j) { moved = true; } else { pos = j; } if (bNode.flags & VNodeFlags.InUse) { b[j] = bNode = directClone(bNode); } patch( aNode, bNode, dom, context, isSVG, outerEdge, lifecycle, animations, ); ++patched; break; } } if (!canRemoveWholeContent && j > bEnd) { remove(aNode, dom, animations); } } else if (!canRemoveWholeContent) { remove(aNode, dom, animations); } } } else { const keyIndex: Record = {}; // Map keys by their index for (i = bStart; i <= bEnd; ++i) { keyIndex[b[i].key as string | number] = i; } // Try to patch same keys for (i = aStart; i <= aEnd; ++i) { aNode = a[i]; if (patched < bLeft) { j = keyIndex[aNode.key as string | number]; if (j !== void 0) { if (canRemoveWholeContent) { canRemoveWholeContent = false; while (i > aStart) { remove(a[aStart++], dom, animations); } } sources[j - bStart] = i + 1; if (pos > j) { moved = true; } else { pos = j; } bNode = b[j]; if (bNode.flags & VNodeFlags.InUse) { b[j] = bNode = directClone(bNode); } patch( aNode, bNode, dom, context, isSVG, outerEdge, lifecycle, animations, ); ++patched; } else if (!canRemoveWholeContent) { remove(aNode, dom, animations); } } else if (!canRemoveWholeContent) { remove(aNode, dom, animations); } } } // fast-path: if nothing patched remove all old and add all new if (canRemoveWholeContent) { removeAllChildren(dom, parentVNode, a, animations); mountArrayChildren( b, dom, context, isSVG, outerEdge, lifecycle, animations, ); } else if (moved) { const seq = lisAlgorithm(sources); j = seq.length - 1; for (i = bLeft - 1; i >= 0; i--) { if (sources[i] === 0) { pos = i + bStart; bNode = b[pos]; if (bNode.flags & VNodeFlags.InUse) { b[pos] = bNode = directClone(bNode); } nextPos = pos + 1; mount( bNode, dom, context, isSVG, nextPos < bLength ? findDOMFromVNode(b[nextPos], true) : outerEdge, lifecycle, animations, ); } else if (j < 0 || i !== seq[j]) { pos = i + bStart; bNode = b[pos]; nextPos = pos + 1; // --- the DOM-node is moved by a call to insertAppend moveVNodeDOM( parentVNode, bNode, dom, nextPos < bLength ? findDOMFromVNode(b[nextPos], true) : outerEdge, animations, ); } else { j--; } } // Invoke move animations when all moves have been calculated if (animations.componentWillMove.length > 0) { callAllMoveAnimationHooks(animations.componentWillMove); } } else if (patched !== bLeft) { // when patched count doesn't match b length we need to insert those new ones // loop backwards so we can use insertBefore for (i = bLeft - 1; i >= 0; i--) { if (sources[i] === 0) { pos = i + bStart; bNode = b[pos]; if (bNode.flags & VNodeFlags.InUse) { b[pos] = bNode = directClone(bNode); } nextPos = pos + 1; mount( bNode, dom, context, isSVG, nextPos < bLength ? findDOMFromVNode(b[nextPos], true) : outerEdge, lifecycle, animations, ); } } } } let result: Int32Array; let p: Int32Array; let maxLen = 0; // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function lisAlgorithm(arr: Int32Array): Int32Array { // Assigning number here tells JIT that these variables are numbers /* eslint-disable no-useless-assignment */ let arrI = 0; let i = 0; let j = 0; let k = 0; let u = 0; let v = 0; let c = 0; const len = arr.length; /* eslint-enable no-useless-assignment */ if (len > maxLen) { maxLen = len; result = new Int32Array(len); p = new Int32Array(len); } for (; i < len; ++i) { arrI = arr[i]; if (arrI !== 0) { j = result[k]; if (arr[j] < arrI) { p[i] = j; result[++k] = i; continue; } u = 0; v = k; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = k + 1; const seq = new Int32Array(u); v = result[u - 1]; while (u-- > 0) { seq[u] = v; v = p[v]; result[u] = 0; } return seq; } ================================================ FILE: packages/inferno/src/DOM/props.ts ================================================ import type { VNode } from '../core/types'; import { namespaces } from './constants'; import { isNull, isNullOrUndef, isString, warning } from 'inferno-shared'; import { handleSyntheticEvent, syntheticEvents } from './events/delegation'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { isSameInnerHTML } from './utils/innerHTML'; import { type AnimationQueues, isLastValueSameLinkEvent, normalizeEventName, } from './utils/common'; import { addFormElementEventHandlers, isControlledFormElement, processElement, } from './wrappers/processElement'; import { unmount, unmountAllChildren } from './unmounting'; import { attachEvent } from './events/attachEvent'; import { isLinkEventObject } from './events/linkEvent'; function wrapLinkEvent(nextValue): (e) => void { // This variable makes sure there is no "this" context in callback const ev = nextValue.event; return function (e): void { ev(nextValue.data, e); }; } export function patchEvent(name: string, lastValue, nextValue, dom): void { if (isLinkEventObject(nextValue)) { if (isLastValueSameLinkEvent(lastValue, nextValue)) { return; } nextValue = wrapLinkEvent(nextValue); } attachEvent(dom, normalizeEventName(name), nextValue); } // We are assuming here that we come from patchProp routine // -nextAttrValue cannot be null or undefined function patchStyle(lastAttrValue, nextAttrValue, dom): void { if (isNullOrUndef(nextAttrValue)) { dom.removeAttribute('style'); return; } const domStyle = dom.style; let style; let value; if (isString(nextAttrValue)) { domStyle.cssText = nextAttrValue; return; } if (!isNullOrUndef(lastAttrValue) && !isString(lastAttrValue)) { for (style in nextAttrValue) { // do not add a hasOwnProperty check here, it affects performance value = nextAttrValue[style]; if (value !== lastAttrValue[style]) { domStyle.setProperty(style, value); } } for (style in lastAttrValue) { if (isNullOrUndef(nextAttrValue[style])) { domStyle.removeProperty(style); } } } else { for (style in nextAttrValue) { value = nextAttrValue[style]; domStyle.setProperty(style, value); } } } function patchDangerInnerHTML( lastValue, nextValue, lastVNode, dom, animations: AnimationQueues, ): void { const lastHtml = lastValue?.__html || ''; const nextHtml = nextValue?.__html || ''; if (lastHtml !== nextHtml) { if (!isNullOrUndef(nextHtml) && !isSameInnerHTML(dom, nextHtml)) { if (!isNull(lastVNode)) { if (lastVNode.childFlags & ChildFlags.MultipleChildren) { unmountAllChildren(lastVNode.children as VNode[], animations); } else if (lastVNode.childFlags === ChildFlags.HasVNodeChildren) { unmount(lastVNode.children, animations); } lastVNode.children = null; lastVNode.childFlags = ChildFlags.HasInvalidChildren; } dom.innerHTML = nextHtml; } } } function patchDomProp(nextValue: unknown, dom: Element, prop: string): void { const value = isNullOrUndef(nextValue) ? '' : nextValue; if (dom[prop] !== value) { dom[prop] = value; } } export function patchProp( prop: string, lastValue: any, nextValue: any, dom: Element, isSVG: boolean, hasControlledValue: boolean, lastVNode: VNode | null, animations: AnimationQueues, ): void { switch (prop) { case 'children': case 'childrenType': case 'className': case 'defaultValue': case 'key': case 'multiple': case 'ref': case 'selectedIndex': break; case 'autoFocus': (dom as any).autofocus = !!nextValue; break; case 'allowfullscreen': case 'autoplay': case 'capture': case 'checked': case 'controls': case 'default': case 'disabled': case 'hidden': case 'indeterminate': case 'loop': case 'muted': case 'novalidate': case 'open': case 'readOnly': case 'required': case 'reversed': case 'scoped': case 'seamless': case 'selected': dom[prop] = !!nextValue; break; case 'defaultChecked': case 'value': case 'volume': if (hasControlledValue && prop === 'value') { break; } patchDomProp(nextValue, dom, prop); break; case 'style': patchStyle(lastValue, nextValue, dom); break; case 'dangerouslySetInnerHTML': patchDangerInnerHTML(lastValue, nextValue, lastVNode, dom, animations); break; default: if (syntheticEvents[prop]) { handleSyntheticEvent(prop, lastValue, nextValue, dom); } else if (prop.charCodeAt(0) === 111 && prop.charCodeAt(1) === 110) { patchEvent(prop, lastValue, nextValue, dom); } else if (isNullOrUndef(nextValue)) { dom.removeAttribute(prop); } else if (isSVG && namespaces[prop]) { // We optimize for isSVG being false // If we end up in this path we can read property again dom.setAttributeNS(namespaces[prop], prop, nextValue); } else { if (process.env.NODE_ENV !== 'production') { if ( prop === 'href' && isString(nextValue) && nextValue.startsWith('javascript:') ) { warning( 'Rendering links with javascript: URLs is not recommended. Use event handlers instead if you can. Inferno was passed "' + nextValue + '".', ); } } dom.setAttribute(prop, nextValue); } break; } } export function mountProps( vNode, flags, props, dom, isSVG, animations: AnimationQueues, ): void { let hasControlledValue: boolean = false; const isFormElement = (flags & VNodeFlags.FormElement) > 0; if (isFormElement) { hasControlledValue = isControlledFormElement(props); if (hasControlledValue) { addFormElementEventHandlers(flags, dom, props); } } for (const prop in props) { // do not add a hasOwnProperty check here, it affects performance patchProp( prop, null, props[prop], dom, isSVG, hasControlledValue, null, animations, ); } if (isFormElement) { processElement(flags, vNode, dom, props, true, hasControlledValue); } } ================================================ FILE: packages/inferno/src/DOM/rendering.ts ================================================ import type { InfernoNode, VNode, ContextObject, ParentDOM, } from '../core/types'; import { isFunction, isInvalid, isNullOrUndef, throwError, warning, } from 'inferno-shared'; import { VNodeFlags } from 'inferno-vnode-flags'; import { directClone } from '../core/implementation'; import { mount } from './mounting'; import { patch } from './patching'; import { remove } from './unmounting'; import { AnimationQueues, callAll, callAllAnimationHooks, EMPTY_OBJ, renderCheck, } from './utils/common'; import { type DelegateEventTypes } from './events/delegation'; const hasDocumentAvailable: boolean = typeof document !== 'undefined'; if (process.env.NODE_ENV !== 'production') { if (hasDocumentAvailable && !document.body) { warning( 'Inferno warning: you cannot initialize inferno without "document.body". Wait on "DOMContentLoaded" event, add script to bottom of body, or use async/defer attributes on script tag.', ); } } let documentBody: HTMLElement | null = null; if (hasDocumentAvailable) { documentBody = document.body; /* * Defining $EV and $V properties on Node.prototype * fixes v8 "wrong map" de-optimization */ if (window.Node) { (Node.prototype as any).$EV = null as DelegateEventTypes | null; (Node.prototype as any).$V = null as DelegateEventTypes | null; } } // noinspection JSUnusedAssignment export function renderInternal( input: VNode | InfernoNode, parentDOM: ParentDOM, callback: (() => void) | null, context: ContextObject, ): void { // Development warning if (process.env.NODE_ENV !== 'production') { if (documentBody === parentDOM) { throwError( 'you cannot render() to the "document.body". Use an empty element as a container instead.', ); } if (isInvalid(parentDOM)) { throwError( `render target ( DOM ) is mandatory, received ${ parentDOM === null ? 'null' : typeof parentDOM }`, ); } } const lifecycle: Array<() => void> = []; const animations: AnimationQueues = new AnimationQueues(); const rootInput = (parentDOM as any).$V as VNode | null; renderCheck.v = true; if (isNullOrUndef(rootInput)) { if (!isNullOrUndef(input)) { if (((input as VNode).flags & VNodeFlags.InUse) !== 0) { input = directClone(input as VNode); } mount( input as VNode, parentDOM as Element, context, false, null, lifecycle, animations, ); (parentDOM as any).$V = input; } } else { if (isNullOrUndef(input)) { remove(rootInput, parentDOM as Element, animations); (parentDOM as any).$V = null; } else { if ((input as VNode).flags & VNodeFlags.InUse) { input = directClone(input as VNode); } patch( rootInput, input as VNode, parentDOM as Element, context, false, null, lifecycle, animations, ); (parentDOM as any).$V = input as VNode; } } callAll(lifecycle); callAllAnimationHooks(animations.componentDidAppear); renderCheck.v = false; if (isFunction(callback)) { callback(); } } export function render( input: VNode | InfernoNode, parentDOM: ParentDOM, callback: (() => void) | null = null, context: ContextObject = EMPTY_OBJ, ): void { renderInternal(input, parentDOM, callback, context); } export function createRenderer(parentDOM?: ParentDOM) { return function renderer(lastInput, nextInput, callback, context) { if (!parentDOM) { parentDOM = lastInput; } render(nextInput, parentDOM as ParentDOM, callback, context); }; } ================================================ FILE: packages/inferno/src/DOM/unmounting.ts ================================================ import type { VNode } from '../core/types'; import { isFunction, isNull, isNullOrUndef } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { syntheticEvents, unmountSyntheticEvent } from './events/delegation'; import { AnimationQueues, callAllAnimationHooks, clearVNodeDOM, EMPTY_OBJ, findDOMFromVNode, removeVNodeDOM, } from './utils/common'; import { unmountRef } from '../core/refs'; export function remove( vNode: VNode, parentDOM: Element, animations: AnimationQueues, ): void { unmount(vNode, animations); removeVNodeDOM(vNode, parentDOM, animations); } export function unmount(vNode, animations: AnimationQueues): void { const flags = vNode.flags; const children = vNode.children; let ref; if ((flags & VNodeFlags.Element) !== 0) { ref = vNode.ref; const props = vNode.props; unmountRef(ref); const childFlags = vNode.childFlags; if (!isNull(props)) { const keys = Object.keys(props); for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; if (syntheticEvents[key]) { unmountSyntheticEvent(key, vNode.dom); } } } if (childFlags & ChildFlags.MultipleChildren) { unmountAllChildren(children, animations); } else if (childFlags === ChildFlags.HasVNodeChildren) { unmount(children as VNode, animations); } } else if (children) { if (flags & VNodeFlags.ComponentClass) { if (isFunction(children.componentWillUnmount)) { // TODO: Possible entrypoint children.componentWillUnmount(); } // If we have a componentWillDisappear on this component, block children from animating let childAnimations = animations; if (isFunction(children.componentWillDisappear)) { childAnimations = new AnimationQueues(); addDisappearAnimationHook( animations, children, children.$LI.dom, flags, undefined, ); } unmountRef(vNode.ref); children.$UN = true; unmount(children.$LI, childAnimations); } else if (flags & VNodeFlags.ComponentFunction) { // If we have a onComponentWillDisappear on this component, block children from animating let childAnimations = animations; ref = vNode.ref; if (!isNullOrUndef(ref)) { let domEl: Element | null = null; if (isFunction(ref.onComponentWillUnmount)) { domEl = findDOMFromVNode(vNode, true); ref.onComponentWillUnmount(domEl, vNode.props || EMPTY_OBJ); } if (isFunction(ref.onComponentWillDisappear)) { childAnimations = new AnimationQueues(); domEl = domEl || findDOMFromVNode(vNode, true); addDisappearAnimationHook( animations, ref, domEl as Element, flags, vNode.props, ); } } unmount(children, childAnimations); } else if (flags & VNodeFlags.Portal) { remove(children as VNode, vNode.ref, animations); } else if (flags & VNodeFlags.Fragment) { if (vNode.childFlags & ChildFlags.MultipleChildren) { unmountAllChildren(children, animations); } } } } export function unmountAllChildren( children: VNode[], animations: AnimationQueues, ): void { for (let i = 0, len = children.length; i < len; ++i) { unmount(children[i], animations); } } function createClearAllCallback(children, parentDOM) { return function () { // We need to remove children one by one because elements can be added during animation if (parentDOM) { for (let i = 0; i < children.length; i++) { const vNode = children[i]; clearVNodeDOM(vNode, parentDOM, false); } } }; } export function clearDOM( parentDOM, children: VNode[], animations: AnimationQueues, ): void { if (animations.componentWillDisappear.length > 0) { // Wait until animations are finished before removing actual dom nodes // Be aware that the element could be removed by a later operation callAllAnimationHooks( animations.componentWillDisappear, createClearAllCallback(children, parentDOM), ); } else { // Optimization for clearing dom parentDOM.textContent = ''; } } export function removeAllChildren( dom: Element, vNode: VNode, children, animations: AnimationQueues, ): void { unmountAllChildren(children, animations); if (vNode.flags & VNodeFlags.Fragment) { removeVNodeDOM(vNode, dom, animations); } else { clearDOM(dom, children, animations); } } // Only add animations to queue in browser function addDisappearAnimationHook( animations: AnimationQueues, instanceOrRef, dom: Element, flags: VNodeFlags, props, ): void { // @ts-expect-error TODO: Here is something weird check this behavior animations.componentWillDisappear.push((callback) => { if (flags & VNodeFlags.ComponentClass) { instanceOrRef.componentWillDisappear(dom, callback); } else if (flags & VNodeFlags.ComponentFunction) { instanceOrRef.onComponentWillDisappear(dom, props, callback); } }); } ================================================ FILE: packages/inferno/src/DOM/utils/common.ts ================================================ import type { Inferno, InfernoNode, LinkedEvent, VNode, } from './../../core/types'; import { isFunction, isNull, isNullOrUndef, isUndefined } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { isLinkEventObject } from '../events/linkEvent'; // We need EMPTY_OBJ defined in one place. // It's used for comparison, so we can't inline it into shared export const EMPTY_OBJ = {}; // @ts-expect-error hack for fragment type export const Fragment: Inferno.ExoticComponent<{ children?: InfernoNode }> = '$F'; export interface MoveQueueItem { parent: Element; dom: Element; next: Element; fn: () => void; } export class AnimationQueues { public componentDidAppear: Array<() => void> = []; public componentWillDisappear: Array<() => void> = []; public componentWillMove: MoveQueueItem[] = []; } if (process.env.NODE_ENV !== 'production') { Object.freeze(EMPTY_OBJ); } export function normalizeEventName(name): keyof DocumentEventMap { return name.substring(2).toLowerCase(); } export function appendChild(parentDOM, dom): void { parentDOM.appendChild(dom); } export function insertOrAppend(parentDOM: Element, newNode, nextNode): void { if (isNull(nextNode)) { appendChild(parentDOM, newNode); } else { parentDOM.insertBefore(newNode, nextNode); } } export function documentCreateElement(tag, isSVG: boolean): Element { if (isSVG) { return document.createElementNS('http://www.w3.org/2000/svg', tag); } return document.createElement(tag); } export function replaceChild(parentDOM: Element, newDom, lastDom): void { parentDOM.replaceChild(newDom, lastDom); } export function removeChild(parentDOM: Element, childNode: Element): void { parentDOM.removeChild(childNode); } export function callAll(arrayFn: Array<() => void>): void { for (let i = 0; i < arrayFn.length; i++) { arrayFn[i](); } } function findChildVNode( vNode: VNode, startEdge: boolean, flags: VNodeFlags, ): InfernoNode { const children = vNode.children; if ((flags & VNodeFlags.ComponentClass) !== 0) { return (children as any).$LI; } if ((flags & VNodeFlags.Fragment) !== 0) { return vNode.childFlags === ChildFlags.HasVNodeChildren ? (children as VNode) : (children as VNode[])[startEdge ? 0 : (children as VNode[]).length - 1]; } return children; } export function findDOMFromVNode( vNode: VNode, startEdge: boolean, ): Element | null { let flags: VNodeFlags; let v: VNode | null = vNode; while (!isNullOrUndef(v)) { flags = v.flags; if ((flags & VNodeFlags.DOMRef) !== 0) { return v.dom; } v = findChildVNode(v, startEdge, flags) as VNode | null; } return null; } export function callAllAnimationHooks( animationQueue: Array<() => void>, callback?: () => void, ): void { let animationsLeft: number = animationQueue.length; // Picking from the top because it is faster, invocation order should be irrelevant // since all animations are to be run, and we can't predict the order in which they complete. let fn; while ((fn = animationQueue.pop()) !== undefined) { fn(() => { if (--animationsLeft <= 0 && isFunction(callback)) { callback(); } }); } } export function callAllMoveAnimationHooks( animationQueue: MoveQueueItem[], ): void { // Start the animations. for (let i = 0; i < animationQueue.length; i++) { animationQueue[i].fn(); } // Perform the actual DOM moves when all measurements of initial // position have been performed. The rest of the animations are done // async. for (let i = 0; i < animationQueue.length; i++) { const tmp = animationQueue[i]; insertOrAppend(tmp.parent, tmp.dom, tmp.next); } animationQueue.splice(0, animationQueue.length); } export function clearVNodeDOM( vNode: VNode | null, parentDOM: Element, deferredRemoval: boolean, ): void { while (!isNullOrUndef(vNode)) { const flags = vNode.flags; if ((flags & VNodeFlags.DOMRef) !== 0) { // On deferred removals the node might disappear because of later operations if (!deferredRemoval || (vNode.dom as Element).parentNode === parentDOM) { removeChild(parentDOM, vNode.dom as Element); } return; } const children = vNode.children as any; if ((flags & VNodeFlags.ComponentClass) !== 0) { vNode = children.$LI; } if ((flags & VNodeFlags.ComponentFunction) !== 0) { vNode = children; } if ((flags & VNodeFlags.Fragment) !== 0) { if ((vNode as VNode).childFlags === ChildFlags.HasVNodeChildren) { vNode = children; } else { for (let i = 0, len = children.length; i < len; ++i) { clearVNodeDOM(children[i], parentDOM, false); } return; } } } } function createDeferComponentClassRemovalCallback(vNode, parentDOM) { return function () { // Mark removal as deferred to trigger check that node still exists clearVNodeDOM(vNode, parentDOM, true); }; } export function removeVNodeDOM( vNode: VNode, parentDOM: Element, animations: AnimationQueues, ): void { if (animations.componentWillDisappear.length > 0) { // Wait until animations are finished before removing actual dom nodes callAllAnimationHooks( animations.componentWillDisappear, createDeferComponentClassRemovalCallback(vNode, parentDOM), ); } else { clearVNodeDOM(vNode, parentDOM, false); } } function addMoveAnimationHook( animations: AnimationQueues, parentVNode, refOrInstance, dom: Element, parentDOM: Element, nextNode: Element, flags, props?, ): void { animations.componentWillMove.push({ dom, fn: () => { if ((flags & VNodeFlags.ComponentClass) !== 0) { refOrInstance.componentWillMove(parentVNode, parentDOM, dom); } else if ((flags & VNodeFlags.ComponentFunction) !== 0) { refOrInstance.onComponentWillMove(parentVNode, parentDOM, dom, props); } }, next: nextNode, parent: parentDOM, }); } export function moveVNodeDOM( parentVNode, vNode, parentDOM, nextNode, animations: AnimationQueues, ): void { let refOrInstance; let instanceProps; const instanceFlags = vNode.flags; while (!isNullOrUndef(vNode)) { const flags = vNode.flags; if ((flags & VNodeFlags.DOMRef) !== 0) { if ( !isNullOrUndef(refOrInstance) && (isFunction(refOrInstance.componentWillMove) || isFunction(refOrInstance.onComponentWillMove)) ) { addMoveAnimationHook( animations, parentVNode, refOrInstance, vNode.dom, parentDOM, nextNode, instanceFlags, instanceProps, ); } else { // TODO: Should we delay this too to support mixing animated moves with regular? insertOrAppend(parentDOM, vNode.dom, nextNode); } return; } const children = vNode.children; if ((flags & VNodeFlags.ComponentClass) !== 0) { refOrInstance = vNode.children; // TODO: We should probably deprecate this in V9 since it is inconsitent with other class component hooks instanceProps = vNode.props; vNode = children.$LI; } else if ((flags & VNodeFlags.ComponentFunction) !== 0) { refOrInstance = vNode.ref; instanceProps = vNode.props; vNode = children; } else if ((flags & VNodeFlags.Fragment) !== 0) { if (vNode.childFlags === ChildFlags.HasVNodeChildren) { vNode = children; } else { for (let i = 0, len = children.length; i < len; ++i) { moveVNodeDOM( parentVNode, children[i], parentDOM, nextNode, animations, ); } return; } } } } export function getComponentName(instance: any): string { // TODO: Fallback for IE return ( instance.name ?? instance.displayName ?? instance.constructor.name ?? ((instance as any).toString().match(/^function\s*([^\s(]+)/) || [])[1] ); } export function createDerivedState( instance, nextProps, state: TState, ): TState { if (isFunction(instance.constructor.getDerivedStateFromProps)) { return { ...state, ...instance.constructor.getDerivedStateFromProps(nextProps, state), }; } return state; } export const renderCheck = { v: false, }; export const options: { createVNode: ((vNode: VNode) => void) | null; reactStyles?: boolean; } = { createVNode: null, }; export function setTextContent(dom: Element, children): void { dom.textContent = children; } // Calling this function assumes, nextValue is linkEvent export function isLastValueSameLinkEvent(lastValue, nextValue): boolean { return ( isLinkEventObject(lastValue) && lastValue.event === (nextValue as LinkedEvent).event && lastValue.data === (nextValue as LinkedEvent).data ); } export function mergeUnsetProperties( to: TTo, from: TFrom, ): TTo & TFrom { for (const propName in from) { // @ts-expect-error merge objects if (isUndefined(to[propName])) { // @ts-expect-error merge objects to[propName] = from[propName]; } } // @ts-expect-error merge objects return to; } export function safeCall1( method: Function | null | undefined, arg1: any, ): boolean { return isFunction(method) && (method(arg1), true); } ================================================ FILE: packages/inferno/src/DOM/utils/componentUtil.ts ================================================ import type { ContextObject, InfernoNode, VNode } from './../../core/types'; import type { Component } from './../../core/component'; import { isFunction, isNull, warning } from 'inferno-shared'; import { createDerivedState, EMPTY_OBJ, getComponentName } from './common'; import { VNodeFlags } from 'inferno-vnode-flags'; import { normalizeRoot } from '../../core/implementation'; function warnAboutOldLifecycles(component: any): void { const oldLifecycles: string[] = []; // Don't warn about react polyfilled components. if ( component.componentWillMount && component.componentWillMount.__suppressDeprecationWarning !== true ) { oldLifecycles.push('componentWillMount'); } if ( component.componentWillReceiveProps && component.componentWillReceiveProps.__suppressDeprecationWarning !== true ) { oldLifecycles.push('componentWillReceiveProps'); } if ( component.componentWillUpdate && component.componentWillUpdate.__suppressDeprecationWarning !== true ) { oldLifecycles.push('componentWillUpdate'); } if (oldLifecycles.length > 0) { warning(` Warning: Unsafe legacy lifecycles will not be called for components using new component APIs. ${getComponentName(component)} contains the following legacy lifecycles: ${oldLifecycles.join('\n')} The above lifecycles should be removed. `); } } export function renderNewInput(instance, props, context): VNode { const nextInput = normalizeRoot( instance.render(props, instance.state, context), ); let childContext = context; if (isFunction(instance.getChildContext)) { childContext = { ...context, ...instance.getChildContext() }; } instance.$CX = childContext; return nextInput; } export function createClassComponentInstance( vNode: VNode, ComponentCtr, props, context: ContextObject, isSVG: boolean, lifecycle: Array<() => void>, ): Component { const instance = new ComponentCtr(props, context); const usesNewAPI = (instance.$N = Boolean( ComponentCtr.getDerivedStateFromProps || instance.getSnapshotBeforeUpdate, )); instance.$SVG = isSVG; instance.$L = lifecycle; if (process.env.NODE_ENV !== 'production') { if (instance.getDerivedStateFromProps) { warning( `${getComponentName( instance, )} getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.`, ); } if (usesNewAPI) { warnAboutOldLifecycles(instance); } } vNode.children = instance; instance.$BS = false; instance.context = context; if (instance.props === EMPTY_OBJ) { instance.props = props; } if (!usesNewAPI) { if (isFunction(instance.componentWillMount)) { instance.$BR = true; instance.componentWillMount(); const pending = instance.$PS; if (!isNull(pending)) { const state = instance.state; if (isNull(state)) { instance.state = pending; } else { for (const key in pending) { state[key] = pending[key]; } } instance.$PS = null; } instance.$BR = false; } } else { instance.state = createDerivedState(instance, props, instance.state); } instance.$LI = renderNewInput(instance, props, context); return instance; } export function renderFunctionalComponent( vNode: VNode, context: ContextObject, ): InfernoNode { const props = vNode.props || EMPTY_OBJ; return vNode.flags & VNodeFlags.ForwardRef ? vNode.type.render(props, vNode.ref, context) : vNode.type(props, context); } ================================================ FILE: packages/inferno/src/DOM/utils/innerHTML.ts ================================================ export function isSameInnerHTML(dom: Element, innerHTML: string): boolean { const temp = document.createElement('i'); temp.innerHTML = innerHTML; return temp.innerHTML === dom.innerHTML; } ================================================ FILE: packages/inferno/src/DOM/wrappers/InputWrapper.ts ================================================ import { isNullOrUndef } from 'inferno-shared'; import { createWrappedFunction } from './wrapper'; import { attachEvent } from '../events/attachEvent'; export function isCheckedType(type): boolean { return type === 'checkbox' || type === 'radio'; } const onTextInputChange = createWrappedFunction('onInput', applyValueInput); const wrappedOnChange = createWrappedFunction( ['onClick', 'onChange'], applyValueInput, ); function stopPropagationWrapper(event) { event.stopPropagation(); } (stopPropagationWrapper as any).wrapped = true; export function inputEvents(dom, nextPropsOrEmpty): void { if (isCheckedType(nextPropsOrEmpty.type)) { attachEvent(dom, 'change', wrappedOnChange); attachEvent(dom, 'click', stopPropagationWrapper); } else { attachEvent(dom, 'input', onTextInputChange); } } export function applyValueInput(nextPropsOrEmpty, dom): void { const type = nextPropsOrEmpty.type; const value = nextPropsOrEmpty.value; const checked = nextPropsOrEmpty.checked; const multiple = nextPropsOrEmpty.multiple; const defaultValue = nextPropsOrEmpty.defaultValue; const hasValue = !isNullOrUndef(value); if (type != null && type !== dom.type) { dom.setAttribute('type', type); } if (!isNullOrUndef(multiple) && multiple !== dom.multiple) { dom.multiple = multiple; } if (!isNullOrUndef(defaultValue) && !hasValue) { dom.defaultValue = defaultValue + ''; } if (isCheckedType(type)) { if (hasValue) { dom.value = value; } if (!isNullOrUndef(checked)) { dom.checked = checked; } } else { if (hasValue && dom.value !== value) { dom.defaultValue = value; dom.value = value; } else if (!isNullOrUndef(checked)) { dom.checked = checked; } } } ================================================ FILE: packages/inferno/src/DOM/wrappers/SelectWrapper.ts ================================================ import { isArray, isNullOrUndef, isNumber } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { EMPTY_OBJ } from '../utils/common'; import { createWrappedFunction } from './wrapper'; import { attachEvent } from '../events/attachEvent'; import { type VNode } from '../../core/types'; import { type Component } from '../../core/component'; function updateChildOptions(vNode: VNode, value): void { if (vNode.type === 'option') { updateChildOption(vNode, value); } else { const children = vNode.children; const flags = vNode.flags; if ((flags & VNodeFlags.ComponentClass) !== 0) { updateChildOptions((children as Component).$LI, value); } else if ((flags & VNodeFlags.ComponentFunction) !== 0) { updateChildOptions(children as VNode, value); } else if (vNode.childFlags === ChildFlags.HasVNodeChildren) { updateChildOptions(children as VNode, value); } else if ((vNode.childFlags & ChildFlags.MultipleChildren) !== 0) { for (let i = 0, len = (children as VNode[]).length; i < len; ++i) { updateChildOptions((children as VNode[])[i], value); } } } } function updateChildOption(vNode: VNode, value: unknown): void { const props: any = vNode.props ?? EMPTY_OBJ; const propsValue = props.value; const dom = vNode.dom as any; // we do this as multiple prop may have changed dom.value = propsValue; if (propsValue === value || (isArray(value) && value.includes(propsValue))) { dom.selected = true; } else if (!isNullOrUndef(value) || !isNullOrUndef(props.selected)) { dom.selected = Boolean(props.selected); } } const onSelectChange = createWrappedFunction('onChange', applyValueSelect); export function selectEvents(dom): void { attachEvent(dom, 'change', onSelectChange); } export function applyValueSelect( nextPropsOrEmpty, dom: any, mounting: boolean, vNode, ): void { const multiplePropInBoolean = Boolean(nextPropsOrEmpty.multiple); if ( !isNullOrUndef(nextPropsOrEmpty.multiple) && multiplePropInBoolean !== dom.multiple ) { dom.multiple = multiplePropInBoolean; } const index = nextPropsOrEmpty.selectedIndex; if (index === -1) { dom.selectedIndex = -1; } const childFlags = vNode.childFlags; if (childFlags !== ChildFlags.HasInvalidChildren) { let value = nextPropsOrEmpty.value; if (isNumber(index) && index > -1 && !isNullOrUndef(dom.options[index])) { value = dom.options[index].value; } if (mounting && isNullOrUndef(value)) { value = nextPropsOrEmpty.defaultValue; } updateChildOptions(vNode, value); } } ================================================ FILE: packages/inferno/src/DOM/wrappers/TextareaWrapper.ts ================================================ import { isFunction, isNullOrUndef } from 'inferno-shared'; import { createWrappedFunction } from './wrapper'; import { attachEvent } from '../events/attachEvent'; import { type NonEmptyProps } from '../../core/types'; const onTextareaInputChange = createWrappedFunction( 'onInput', applyValueTextArea, ); const wrappedOnChange = createWrappedFunction('onChange'); export function textAreaEvents(dom, nextPropsOrEmpty): void { attachEvent(dom, 'input', onTextareaInputChange); if (isFunction(nextPropsOrEmpty.onChange)) { attachEvent(dom, 'change', wrappedOnChange); } } export function applyValueTextArea( nextPropsOrEmpty: NonEmptyProps, dom, mounting: boolean, ): void { const value = nextPropsOrEmpty.value; const domValue = dom.value; if (isNullOrUndef(value)) { if (mounting) { const defaultValue = nextPropsOrEmpty.defaultValue; if (!isNullOrUndef(defaultValue) && defaultValue !== domValue) { dom.defaultValue = defaultValue; dom.value = defaultValue; } } } else if (domValue !== value) { /* There is value so keep it controlled */ dom.defaultValue = value; dom.value = value; } } ================================================ FILE: packages/inferno/src/DOM/wrappers/processElement.ts ================================================ import type { VNode } from '../../core/types'; import { isNullOrUndef } from 'inferno-shared'; import { VNodeFlags } from 'inferno-vnode-flags'; import { applyValueInput, inputEvents, isCheckedType } from './InputWrapper'; import { applyValueSelect, selectEvents } from './SelectWrapper'; import { applyValueTextArea, textAreaEvents } from './TextareaWrapper'; export function processElement( flags: VNodeFlags, vNode: VNode, dom: Element, nextPropsOrEmpty, mounting: boolean, isControlled: boolean, ): void { if ((flags & VNodeFlags.InputElement) !== 0) { applyValueInput(nextPropsOrEmpty, dom); } else if ((flags & VNodeFlags.SelectElement) !== 0) { applyValueSelect(nextPropsOrEmpty, dom, mounting, vNode); } else if ((flags & VNodeFlags.TextareaElement) !== 0) { applyValueTextArea(nextPropsOrEmpty, dom, mounting); } if (isControlled) { (dom as any).$V = vNode; } } export function addFormElementEventHandlers( flags: VNodeFlags, dom: Element, nextPropsOrEmpty, ): void { if ((flags & VNodeFlags.InputElement) !== 0) { inputEvents(dom, nextPropsOrEmpty); } else if ((flags & VNodeFlags.SelectElement) !== 0) { selectEvents(dom); } else if ((flags & VNodeFlags.TextareaElement) !== 0) { textAreaEvents(dom, nextPropsOrEmpty); } } export function isControlledFormElement(nextPropsOrEmpty): boolean { return isCheckedType(nextPropsOrEmpty.type) ? !isNullOrUndef(nextPropsOrEmpty.checked) : !isNullOrUndef(nextPropsOrEmpty.value); } ================================================ FILE: packages/inferno/src/DOM/wrappers/wrapper.ts ================================================ import { isFunction, isNullOrUndef, isString } from 'inferno-shared'; import { EMPTY_OBJ } from '../utils/common'; import { type NonEmptyProps, type VNode } from '../../core/types'; function triggerEventListener( props: NonEmptyProps, methodName: string, e: any, ): void { const listener = props[methodName] as any; if (listener) { if (listener.event) { listener.event(listener.data, e); } else { listener(e); } } else { const nativeListenerName = methodName.toLowerCase(); if (isFunction(props[nativeListenerName])) { (props[nativeListenerName] as (e) => void)(e); } } } export function createWrappedFunction( methodName: string | string[], applyValue?: ( newProps: NonEmptyProps, dom: any, isMounting: boolean, newVNode: VNode, ) => void, ): (e: Event) => void { const fnWrapper = function fnWrapper(e: Event): void { const vNode = this.$V as VNode; // If vNode is gone by the time event fires, no-op if (isNullOrUndef(vNode)) { return; } const props = vNode.props ?? EMPTY_OBJ; const dom = vNode.dom; if (isString(methodName)) { triggerEventListener(props, methodName, e); } else { for (let i = 0; i < methodName.length; ++i) { triggerEventListener(props, methodName[i], e); } } if (isFunction(applyValue)) { const newVNode = this.$V; const newProps = newVNode.props ?? EMPTY_OBJ; applyValue(newProps, dom, false, newVNode); } }; Object.defineProperty(fnWrapper, 'wrapped', { configurable: false, enumerable: false, value: true, writable: false, }); return fnWrapper; } ================================================ FILE: packages/inferno/src/core/component.ts ================================================ import type { Inferno, InfernoNode, IComponent, VNode } from './types'; import { isFunction, isNullOrUndef, throwError } from 'inferno-shared'; import { updateClassComponent } from '../DOM/patching'; import { AnimationQueues, callAll, callAllAnimationHooks, EMPTY_OBJ, findDOMFromVNode, renderCheck, } from '../DOM/utils/common'; const COMPONENTS_QUEUE: Array> = []; const nextTick = Promise.resolve().then.bind(Promise.resolve()); let microTaskPending = false; function queueStateChanges( component: Component, newState: any, callback: (() => void) | undefined, force: boolean, ): void { const pending = component.$PS; if (isFunction(newState)) { newState = newState( pending ? { ...component.state, ...pending } : component.state, component.props, component.context, ); } if (isNullOrUndef(pending)) { component.$PS = newState; } else { for (const stateKey in newState) { pending[stateKey] = newState[stateKey]; } } if (!component.$BR) { if (!renderCheck.v) { if (COMPONENTS_QUEUE.length === 0) { applyState(component, force); if (isFunction(callback)) { callback.call(component); } return; } } if (!COMPONENTS_QUEUE.includes(component)) { COMPONENTS_QUEUE.push(component); } if (force) { component.$F = true; } if (!microTaskPending) { microTaskPending = true; nextTick(rerender); } if (isFunction(callback)) { let QU = component.$QU; if (!QU) { QU = component.$QU = [] as Array<() => void>; } QU.push(callback); } } else if (isFunction(callback)) { (component.$L as Array<() => void>).push(callback.bind(component)); } } function callSetStateCallbacks(component): void { const queue = component.$QU; for (let i = 0; i < queue.length; ++i) { queue[i].call(component); } component.$QU = null; } export function rerender(): void { let component: Component | undefined; microTaskPending = false; while ((component = COMPONENTS_QUEUE.shift())) { if (!component.$UN) { const force = component.$F; component.$F = false; applyState(component, force); if (component.$QU) { callSetStateCallbacks(component); } } } } function applyState(component: Component, force: boolean): void { if (force || !component.$BR) { const pendingState = component.$PS; component.$PS = null; const lifecycle: Array<() => void> = []; const animations: AnimationQueues = new AnimationQueues(); renderCheck.v = true; updateClassComponent( component, { ...component.state, ...pendingState }, component.props, (findDOMFromVNode(component.$LI, true) as Element).parentNode as Element, component.context, component.$SVG, force, null, lifecycle, animations, ); callAll(lifecycle); callAllAnimationHooks(animations.componentDidAppear); renderCheck.v = false; } else { component.state = component.$PS as any; component.$PS = null; } } export type ComponentType

> = | typeof Component

| Inferno.StatelessComponent

; export abstract class Component< P = Record, S = Record, > implements IComponent { // Public public state: Readonly = null; public props: Readonly<{ children?: InfernoNode }> & Readonly

; public context: any; public displayName?: string; // Internal properties public $BR: boolean = false; // BLOCK RENDER public $BS: boolean = true; // BLOCK STATE public $PS: Partial | null = null; // PENDING STATE (PARTIAL or FULL) public $LI: any = null; // LAST INPUT public $UN: boolean = false; // UNMOUNTED public $CX: any = null; // CHILDCONTEXT public $QU: Array<() => void> | null = null; // QUEUE public $N: boolean = false; // Uses new lifecycle API Flag public $SSR?: boolean; // Server side rendering flag, true when rendering on server, non existent on client public $L: Array<() => void> | null = null; // Current lifecycle of this component public $SVG: boolean = false; // Flag to keep track if component is inside SVG tree public $F: boolean = false; // Force update flag constructor(props?: P, context?: any) { this.props = (props || EMPTY_OBJ) as Readonly<{ children?: InfernoNode }> & Readonly

; this.context = context || EMPTY_OBJ; // context should not be mutable } public forceUpdate(callback?: (() => void) | undefined): void { if (this.$UN) { return; } // Do not allow double render during force update queueStateChanges(this, {} as any, callback, true); } public setState( newState: | (( prevState: Readonly, props: Readonly<{ children?: InfernoNode } & P>, ) => Pick | S | null) | (Pick | S | null), callback?: () => void, ): void { if (this.$UN) { return; } if (!this.$BS) { queueStateChanges(this, newState, callback, false); } else { // Development warning if (process.env.NODE_ENV !== 'production') { throwError( 'cannot update state via setState() in constructor. Instead, assign to `this.state` directly or define a `state = {};`', ); } } } public componentDidMount?(): void; public componentWillMount?(): void; public componentWillReceiveProps?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextContext: any, ): void; public shouldComponentUpdate?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextState: Readonly, context: any, ): boolean; public componentWillUpdate?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextState: Readonly, context: any, ): void; public componentDidUpdate?( prevProps: Readonly<{ children?: InfernoNode } & P>, prevState: Readonly, snapshot: any, ): void; public componentWillUnmount?(): void; public componentDidAppear?(domNode: Element): void; public componentWillDisappear?(domNode: Element, callback: () => void): void; public componentWillMove?( parentVNode: VNode, parentDOM: Element, dom: Element, ): void; public getChildContext?(): void; public getSnapshotBeforeUpdate?( prevProps: Readonly<{ children?: InfernoNode } & P>, prevState: Readonly, ): any; public static defaultProps?: Record | null = null; public static getDerivedStateFromProps?(nextProps: any, state: any): any; /* eslint-disable */ // @ts-ignore public render(props: Readonly<{ children?: InfernoNode } & P>, state: Readonly, context: any): InfernoNode { return null; } } ================================================ FILE: packages/inferno/src/core/implementation.ts ================================================ import type { ForwardRef, InfernoNode, ParentDOM, Ref, Refs, VNode, } from './types'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { isArray, isFunction, isInvalid, isNull, isNullOrUndef, isString, isStringOrNumber, throwError, } from 'inferno-shared'; import { throwIfObjectIsNotVNode, validateChildFlags, validateVNodeElementChildren, } from './validate'; import { Fragment, mergeUnsetProperties, options } from './../DOM/utils/common'; import { type Component, type ComponentType } from './component'; const keyPrefix = '$'; function V( childFlags: ChildFlags, children, className: string | null | undefined, flags: VNodeFlags, key, props, ref, type, ): void { if (process.env.NODE_ENV !== 'production') { this.isValidated = false; } this.childFlags = childFlags; this.children = children; this.className = className; this.dom = null; this.flags = flags; this.key = key === void 0 ? null : key; this.props = props === void 0 ? null : props; this.ref = ref === void 0 ? null : ref; this.type = type; } export function createVNode

( flags: VNodeFlags, type: string, className?: string | null, children?: InfernoNode, childFlags?: ChildFlags, props?: Readonly

| null, key?: string | number | null, ref?: Ref | Refs

| null, ): VNode { if (process.env.NODE_ENV !== 'production') { if (flags & VNodeFlags.Component) { throwError( 'Creating Component vNodes using createVNode is not allowed. Use Inferno.createComponentVNode method.', ); } } const childFlag: ChildFlags = childFlags === void 0 ? ChildFlags.HasInvalidChildren : childFlags; const vNode = new V( childFlag, children, className, flags, key, props, ref, type, ) as VNode; if (options.createVNode) { options.createVNode(vNode); } if (childFlag === ChildFlags.UnknownChildren) { normalizeChildren(vNode, vNode.children); } if (process.env.NODE_ENV !== 'production') { if (childFlag !== ChildFlags.UnknownChildren) { validateChildFlags(vNode); } validateVNodeElementChildren(vNode); } return vNode; } function mergeDefaultHooks(flags, type, ref) { if (flags & VNodeFlags.ComponentClass) { return ref; } const defaultHooks = (flags & VNodeFlags.ForwardRef ? type.render : type) .defaultHooks; if (isNullOrUndef(defaultHooks)) { return ref; } if (isNullOrUndef(ref)) { return defaultHooks; } return mergeUnsetProperties(ref, defaultHooks); } function mergeDefaultProps(flags, type, props) { // set default props const defaultProps = (flags & VNodeFlags.ForwardRef ? type.render : type) .defaultProps; if (isNullOrUndef(defaultProps)) { return props; } if (isNullOrUndef(props)) { return { ...defaultProps }; } return mergeUnsetProperties(props, defaultProps); } function resolveComponentFlags(flags: VNodeFlags, type): VNodeFlags { if (flags & VNodeFlags.ComponentKnown) { return flags; } if (type.prototype?.render) { return VNodeFlags.ComponentClass; } if (type.render) { return VNodeFlags.ForwardRefComponent; } return VNodeFlags.ComponentFunction; } export function createComponentVNode

( flags: VNodeFlags, type: | Function | ComponentType

| Component | ForwardRef, props?: Readonly

| null, key?: null | string | number, ref?: Ref | Refs

| null, ): VNode { if (process.env.NODE_ENV !== 'production') { if ((flags & VNodeFlags.HtmlElement) !== 0) { throwError( 'Creating element vNodes using createComponentVNode is not allowed. Use Inferno.createVNode method.', ); } } flags = resolveComponentFlags(flags, type); const vNode = new V( ChildFlags.HasInvalidChildren, null, null, flags, key, mergeDefaultProps(flags, type, props), mergeDefaultHooks(flags, type, ref), type, ) as VNode; if (isFunction(options.createVNode)) { options.createVNode(vNode); } return vNode; } export function createTextVNode( text?: string | boolean | null | number, key?: string | number | null, ): VNode { return new V( ChildFlags.HasInvalidChildren, isNullOrUndef(text) || text === true || text === false ? '' : text, null, VNodeFlags.Text, key, null, null, null, ) as VNode; } export function createFragment( children: any, childFlags: ChildFlags, key?: string | number | null, ): VNode { const fragment = createVNode( VNodeFlags.Fragment, VNodeFlags.Fragment as any, null, children, childFlags, null, key, null, ); switch (fragment.childFlags) { case ChildFlags.HasInvalidChildren: fragment.children = createVoidVNode(); fragment.childFlags = ChildFlags.HasVNodeChildren; break; case ChildFlags.HasTextChildren: fragment.children = [createTextVNode(children)]; fragment.childFlags = ChildFlags.HasNonKeyedChildren; break; default: break; } return fragment; } export function normalizeProps(vNode: VNode): VNode { const props = vNode.props; if (props) { const flags = vNode.flags; if (flags & VNodeFlags.Element) { if (props.children !== void 0 && isNullOrUndef(vNode.children)) { normalizeChildren(vNode, props.children); } if (props.className !== void 0) { if (isNullOrUndef(vNode.className)) { vNode.className = props.className || null; } props.className = undefined; } } if (props.key !== void 0) { vNode.key = props.key; props.key = undefined; } if (props.ref !== void 0) { if (flags & VNodeFlags.ComponentFunction) { vNode.ref = { ...vNode.ref, ...props.ref }; } else { vNode.ref = props.ref; } props.ref = undefined; } } return vNode; } /* * Fragment is different from normal vNode, * because when it needs to be cloned we need to clone its children too * But not normalize, because otherwise those possibly get KEY and re-mount */ function cloneFragment(vNodeToClone: VNode): VNode { const oldChildren = vNodeToClone.children; const childFlags = vNodeToClone.childFlags; return createFragment( childFlags === ChildFlags.HasVNodeChildren ? directClone(oldChildren as VNode) : (oldChildren as VNode[]).map(directClone), childFlags, vNodeToClone.key, ); } export function directClone(vNodeToClone: VNode): VNode { const flags = vNodeToClone.flags & VNodeFlags.ClearInUse; let props = vNodeToClone.props; if (flags & VNodeFlags.Component) { if (!isNull(props)) { const propsToClone = props; props = {}; for (const key in propsToClone) { props[key] = propsToClone[key]; } } } if ((flags & VNodeFlags.Fragment) === 0) { return new V( vNodeToClone.childFlags, vNodeToClone.children, vNodeToClone.className, flags, vNodeToClone.key, props, vNodeToClone.ref, vNodeToClone.type, ) as VNode; } return cloneFragment(vNodeToClone); } export function createVoidVNode(): VNode { return createTextVNode('', null); } export function createPortal(children, container: ParentDOM): VNode { const normalizedRoot = normalizeRoot(children); return createVNode( VNodeFlags.Portal, VNodeFlags.Portal as any, null, normalizedRoot, ChildFlags.UnknownChildren, null, normalizedRoot.key, container as any, // Should there be own prop for this? ); } export function _normalizeVNodes( nodes: any[], result: VNode[], index: number, currentKey: string, ): void { for (const len = nodes.length; index < len; index++) { let n = nodes[index]; if (!isInvalid(n)) { const newKey: string = currentKey + keyPrefix + index; if (isArray(n)) { _normalizeVNodes(n, result, 0, newKey); } else { if (isStringOrNumber(n)) { n = createTextVNode(n, newKey); } else { if (process.env.NODE_ENV !== 'production') { throwIfObjectIsNotVNode(n); } const oldKey = n.key; const isPrefixedKey = isString(oldKey) && oldKey[0] === keyPrefix; if (n.flags & VNodeFlags.InUseOrNormalized || isPrefixedKey) { n = directClone(n); } n.flags |= VNodeFlags.Normalized; if (!isPrefixedKey) { if (isNull(oldKey)) { n.key = newKey; } else { n.key = currentKey + oldKey; } } else if (oldKey.substring(0, currentKey.length) !== currentKey) { n.key = currentKey + oldKey; } } result.push(n); } } } } export function getFlagsForElementVnode(type: string): VNodeFlags { switch (type) { case 'svg': return VNodeFlags.SvgElement; case 'input': return VNodeFlags.InputElement; case 'select': return VNodeFlags.SelectElement; case 'textarea': return VNodeFlags.TextareaElement; // @ts-expect-error Fragment is special case case Fragment: return VNodeFlags.Fragment; default: return VNodeFlags.HtmlElement; } } export function normalizeChildren(vNode: VNode, children): VNode { let newChildren; let newChildFlags: ChildFlags = ChildFlags.HasInvalidChildren; // Don't change children to match strict equal (===) true in patching if (isInvalid(children)) { newChildren = children; } else if (isStringOrNumber(children)) { newChildFlags = ChildFlags.HasTextChildren; newChildren = children; } else if (isArray(children)) { const len = children.length; for (let i = 0; i < len; ++i) { let n = children[i]; if (isInvalid(n) || isArray(n)) { newChildren = newChildren || children.slice(0, i); _normalizeVNodes(children, newChildren, i, ''); break; } else if (isStringOrNumber(n)) { newChildren = newChildren || children.slice(0, i); newChildren.push(createTextVNode(n, keyPrefix + i)); } else { if (process.env.NODE_ENV !== 'production') { throwIfObjectIsNotVNode(n); } const key = n.key; const needsCloning: boolean = (n.flags & VNodeFlags.InUseOrNormalized) > 0; const isNullKey: boolean = isNull(key); const isPrefixed: boolean = isString(key) && key[0] === keyPrefix; if (needsCloning || isNullKey || isPrefixed) { newChildren = newChildren || children.slice(0, i); if (needsCloning || isPrefixed) { n = directClone(n); } if (isNullKey || isPrefixed) { n.key = keyPrefix + i; } newChildren.push(n); } else if (newChildren) { newChildren.push(n); } n.flags |= VNodeFlags.Normalized; } } newChildren = newChildren || children; if (newChildren.length === 0) { newChildFlags = ChildFlags.HasInvalidChildren; } else { newChildFlags = ChildFlags.HasKeyedChildren; } } else { newChildren = children; newChildren.flags |= VNodeFlags.Normalized; if (children.flags & VNodeFlags.InUseOrNormalized) { newChildren = directClone(children as VNode); } newChildFlags = ChildFlags.HasVNodeChildren; } vNode.children = newChildren; vNode.childFlags = newChildFlags; return vNode; } export function normalizeRoot(input): VNode { if (isInvalid(input) || isStringOrNumber(input)) { return createTextVNode(input, null); } if (isArray(input)) { return createFragment(input, ChildFlags.UnknownChildren, null); } return input.flags & VNodeFlags.InUse ? directClone(input) : input; } ================================================ FILE: packages/inferno/src/core/nativetypes.ts ================================================ // These types need to be in another file to not conflict with Inferno event type names export type NativeClipboardEvent = ClipboardEvent; export type NativeCompositionEvent = CompositionEvent; export type NativeDragEvent = DragEvent; export type NativeFocusEvent = FocusEvent; ================================================ FILE: packages/inferno/src/core/refs.ts ================================================ import { isFunction, isNullOrUndef, warning } from 'inferno-shared'; import { safeCall1 } from '../DOM/utils/common'; import type { InfernoNode, Props, RefObject } from './types'; export function createRef(): RefObject { return { current: null, }; } // TODO: Make this return value typed export function forwardRef>( render: ( props: Readonly<{ children?: InfernoNode }> & Readonly

, ref: RefObject, ) => InfernoNode, ): any { if (process.env.NODE_ENV !== 'production') { if (!isFunction(render)) { warning( `forwardRef requires a render function but was given ${ render === null ? 'null' : typeof render }.`, ); return; } } return { render, }; } export function unmountRef(ref): void { if (!isNullOrUndef(ref)) { if (!safeCall1(ref, null) && (ref as RefObject).current) { ref.current = null; } } } export function mountRef(ref, value, lifecycle: Array<() => void>): void { if (!isNullOrUndef(ref) && (isFunction(ref) || ref.current !== void 0)) { lifecycle.push(() => { if (!safeCall1(ref, value) && ref.current !== void 0) { ref.current = value; } }); } } ================================================ FILE: packages/inferno/src/core/types.ts ================================================ /* eslint-disable */ import type { NativeClipboardEvent, NativeCompositionEvent, NativeDragEvent, NativeFocusEvent, } from './nativetypes'; import type { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import type { PropertiesHyphen } from 'csstype'; export interface LinkedEvent { data: T; event: (data: T, event: E) => void; } export type InfernoText = string | number; export type InfernoChild = Inferno.InfernoElement | InfernoText; interface InfernoNodeArray extends Array {} export type InfernoFragment = {} | InfernoNodeArray; export type InfernoSingleNode = InfernoChild | boolean | null | undefined; export type InfernoNode = InfernoSingleNode | InfernoFragment; export type InfernoKeyedNode = Inferno.InfernoElement & { key: string | number }; export type NonEmptyProps = Record; export type ContextObject = Record; export type ParentDOM = | Element | SVGAElement | ShadowRoot | DocumentFragment | HTMLElement | Node | null; // IComponent is defined here, instead of Component to de-couple implementation from interface export interface IComponent { // Public state: S | null; props: Readonly< { children?: InfernoNode; } & P >; context?: any; displayName?: string; refs?: any; forceUpdate(callback?: Function); setState( newState: | (( prevState: Readonly, props: Readonly<{ children?: InfernoNode } & P>, ) => Pick | S | null) | (Pick | S | null), callback?: () => void, ): void; componentDidMount?(): void; componentWillMount?(): void; componentWillReceiveProps?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextContext: any, ): void; shouldComponentUpdate?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextState: Readonly, context: any, ): boolean; componentWillUpdate?( nextProps: Readonly<{ children?: InfernoNode } & P>, nextState: Readonly, context: any, ): void; componentDidUpdate?( prevProps: Readonly<{ children?: InfernoNode } & P>, prevState: Readonly, snapshot: any, ): void; componentWillUnmount?(): void; componentDidAppear?(domNode: Element): void; componentWillDisappear?(domNode: Element, callback: Function): void; componentWillMove?( parentVNode: VNode, parentDOM: Element, dom: Element, ): void; getChildContext?(): void; getSnapshotBeforeUpdate?( prevProps: Readonly<{ children?: InfernoNode } & P>, prevState: Readonly, ): any; render( nextProps: Readonly<{ children?: InfernoNode } & P>, nextState: Readonly, nextContext: any, ): InfernoNode; } export interface SemiSyntheticEvent extends Event { /** * A reference to the element on which the event listener is registered. */ currentTarget: EventTarget & T; isDefaultPrevented?: () => boolean; isPropagationStopped?: () => boolean; } export type ClipboardEvent = SemiSyntheticEvent & NativeClipboardEvent; export type CompositionEvent = SemiSyntheticEvent & NativeCompositionEvent; export type DragEvent = InfernoMouseEvent & NativeDragEvent; export type FocusEvent = SemiSyntheticEvent & NativeFocusEvent; export interface FormEvent extends SemiSyntheticEvent { target: EventTarget & T; } export interface ChangeEvent extends SemiSyntheticEvent { target: EventTarget & T; } export type InfernoKeyboardEvent = SemiSyntheticEvent & KeyboardEvent; export type InfernoMouseEvent = SemiSyntheticEvent & MouseEvent & { target: EventTarget & T; }; export type InfernoTouchEvent = SemiSyntheticEvent & TouchEvent; export type InfernoPointerEvent = SemiSyntheticEvent & PointerEvent; export type InfernoUIEvent = SemiSyntheticEvent & UIEvent; export type InfernoWheelEvent = InfernoMouseEvent & WheelEvent; export type InfernoAnimationEvent = SemiSyntheticEvent & AnimationEvent; export type InfernoTransitionEvent = SemiSyntheticEvent & TransitionEvent; type Booleanish = boolean | 'true' | 'false'; // // Event Handler Types // ---------------------------------------------------------------------- export type EventHandler> = | { bivarianceHack(event: E): void }['bivarianceHack'] | LinkedEvent | null; export type InfernoEventHandler = EventHandler< SemiSyntheticEvent >; export type ClipboardEventHandler = EventHandler< ClipboardEvent >; export type CompositionEventHandler = EventHandler< CompositionEvent >; export type DragEventHandler = EventHandler>; export type FocusEventHandler = EventHandler>; export type FormEventHandler = EventHandler>; export type ChangeEventHandler = EventHandler>; export type KeyboardEventHandler = EventHandler< InfernoKeyboardEvent >; export type MouseEventHandler = EventHandler>; export type TouchEventHandler = EventHandler>; export type PointerEventHandler = EventHandler< InfernoPointerEvent >; export type UIEventHandler = EventHandler>; export type WheelEventHandler = EventHandler>; export type AnimationEventHandler = EventHandler< InfernoAnimationEvent >; export type TransitionEventHandler = EventHandler< InfernoTransitionEvent >; export type Key = string | number | undefined | null; type CrossOrigin = 'anonymous' | 'use-credentials' | '' | null | undefined; export interface VNode { children: InfernoNode; childFlags: ChildFlags; dom: Element | null; className: string | null | undefined; flags: VNodeFlags; isValidated?: boolean; key: Key; props: any; ref: any; type: any; } export interface RefObject { readonly current: T | null; } export type Ref = { bivarianceHack(instance: T | null): any; }['bivarianceHack']; export interface ForwardRef extends Inferno.StatelessComponent

{ ref: Ref; } export interface Refs

{ onComponentDidMount?: ( domNode: Element | null, nextProps: Readonly<{ children?: InfernoNode } & P>, ) => void; onComponentWillMount?(props: Readonly<{ children?: InfernoNode } & P>): void; onComponentShouldUpdate?( lastProps: Readonly<{ children?: InfernoNode } & P>, nextProps: Readonly<{ children?: InfernoNode } & P>, ): boolean; onComponentWillUpdate?( lastProps: Readonly<{ children?: InfernoNode } & P>, nextProps: Readonly<{ children?: InfernoNode } & P>, ): void; onComponentDidUpdate?( lastProps: Readonly<{ children?: InfernoNode } & P>, nextProps: Readonly<{ children?: InfernoNode } & P>, ): void; onComponentWillUnmount?( domNode: Element, nextProps: Readonly<{ children?: InfernoNode } & P>, ): void; onComponentDidAppear?( domNode: Element, props: Readonly<{ children?: InfernoNode } & P>, ): void; onComponentWillDisappear?( domNode: Element, props: Readonly<{ children?: InfernoNode } & P>, callback: Function, ): void; onComponentWillMove?( parentVNode: VNode, parentDOM: Element, dom: Element, props: Readonly<{ children?: InfernoNode } & P>, ): void; } export interface Props { children?: InfernoNode; key?: Key; ref?: Ref | undefined; } export declare namespace Inferno { // // Inferno Elements // ---------------------------------------------------------------------- // tslint:disable-next-line:interface-over-type-literal type ComponentState = {}; type ExoticComponent

= (props: P) => InfernoElement; interface Attributes { key?: Key; $ReCreate?: boolean; $HasVNodeChildren?: boolean; $HasNonKeyedChildren?: boolean; $HasKeyedChildren?: boolean; $HasTextChildren?: boolean; $ChildFlag?: number; } interface ClassAttributes extends Attributes { ref?: Ref | RefObject | null | undefined; } interface InfernoElement

{ type: string | ComponentClass

| SFC

; props: P; key?: Key; } interface SFCElement

extends InfernoElement

{ type: SFC

; } type CElement> = ComponentElement< P, T >; interface ComponentElement> extends InfernoElement

{ type: ComponentClass

; ref?: Ref | undefined; } // string fallback for custom web-components interface DOMElement< P extends HTMLAttributes | SVGAttributes, T extends Element, > extends InfernoElement

{ type: string; ref: Ref; } // InfernoHTML for InfernoHTMLElement interface InfernoHTMLElement extends DetailedInfernoHTMLElement, T> {} interface DetailedInfernoHTMLElement< P extends HTMLAttributes, T extends HTMLElement, > extends DOMElement { type: keyof InfernoHTML; } // InfernoSVG for InfernoSVGElement interface InfernoSVGElement extends DOMElement, SVGElement> { type: keyof InfernoSVG; } // // Factories // ---------------------------------------------------------------------- type Factory

= ( props?: Attributes & P, ...children: InfernoNode[] ) => InfernoElement

; type SFCFactory

= ( props?: Attributes & P, ...children: InfernoNode[] ) => SFCElement

; type ComponentFactory> = ( props?: ClassAttributes & P, ...children: InfernoNode[] ) => CElement; type CFactory> = ComponentFactory< P, T >; type DOMFactory

, T extends Element> = ( props?: (ClassAttributes & P) | null, ...children: InfernoNode[] ) => DOMElement; interface HTMLFactory extends DetailedHTMLFactory, T> {} interface DetailedHTMLFactory< P extends HTMLAttributes, T extends HTMLElement, > extends DOMFactory { ( props?: (ClassAttributes & P) | null, ...children: InfernoNode[] ): DetailedInfernoHTMLElement; } interface SVGFactory extends DOMFactory, SVGElement> { ( props?: (ClassAttributes & SVGAttributes) | null, ...children: InfernoNode[] ): InfernoSVGElement; } // // Inferno Nodes // ---------------------------------------------------------------------- const version: string; // // Component API // ---------------------------------------------------------------------- interface ChildContextProvider { getChildContext(): CC; } // // Class Interfaces // ---------------------------------------------------------------------- type SFC

= StatelessComponent

; interface StatelessComponent

{ ( props: { children?: InfernoNode; } & P & Refs

, context?: any, ): InfernoElement | null; defaultProps?: Partial

| undefined | null; defaultHooks?: Refs

| undefined | null; } interface ComponentClass

{ new ( props?: { children?: InfernoNode; } & P, context?: any, ): IComponent; defaultProps?: Partial

| undefined | null; } // // Props / DOM Attributes // ---------------------------------------------------------------------- interface HTMLProps extends AllHTMLAttributes, ClassAttributes {} type DetailedHTMLProps, T> = ClassAttributes & E; interface SVGProps extends SVGAttributes, ClassAttributes {} interface DOMAttributes { children?: InfernoNode; dangerouslySetInnerHTML?: | { __html: string; } | null | undefined; // Clipboard Events onCopy?: ClipboardEventHandler | undefined; onCut?: ClipboardEventHandler | undefined; onPaste?: ClipboardEventHandler | undefined; // Composition Events onCompositionEnd?: CompositionEventHandler | undefined; onCompositionStart?: CompositionEventHandler | undefined; onCompositionUpdate?: CompositionEventHandler | undefined; // Focus Events onFocus?: FocusEventHandler | undefined; onBlur?: FocusEventHandler | undefined; // Form Events onChange?: FormEventHandler | undefined | null; onBeforeInput?: FormEventHandler | undefined; onInput?: FormEventHandler | undefined; onReset?: FormEventHandler | undefined; onSubmit?: FormEventHandler | undefined; onInvalid?: FormEventHandler | undefined; // Image Events onLoad?: InfernoEventHandler | undefined; onError?: InfernoEventHandler | undefined; // also a Media Event // Keyboard Events onKeyDown?: KeyboardEventHandler | undefined; onKeyPress?: KeyboardEventHandler | undefined; onKeyUp?: KeyboardEventHandler | undefined; // Media Events onAbort?: InfernoEventHandler | undefined; onCanPlay?: InfernoEventHandler | undefined; onCanPlayThrough?: InfernoEventHandler | undefined; onDurationChange?: InfernoEventHandler | undefined; onEmptied?: InfernoEventHandler | undefined; onEncrypted?: InfernoEventHandler | undefined; onEnded?: InfernoEventHandler | undefined; onLoadedData?: InfernoEventHandler | undefined; onLoadedMetadata?: InfernoEventHandler | undefined; onLoadStart?: InfernoEventHandler | undefined; onPause?: InfernoEventHandler | undefined; onPlay?: InfernoEventHandler | undefined; onPlaying?: InfernoEventHandler | undefined; onProgress?: InfernoEventHandler | undefined; onRateChange?: InfernoEventHandler | undefined; onSeeked?: InfernoEventHandler | undefined; onSeeking?: InfernoEventHandler | undefined; onStalled?: InfernoEventHandler | undefined; onSuspend?: InfernoEventHandler | undefined; onTimeUpdate?: InfernoEventHandler | undefined; onVolumeChange?: InfernoEventHandler | undefined; onWaiting?: InfernoEventHandler | undefined; // MouseEvents onAuxClick?: MouseEventHandler | undefined; onClick?: MouseEventHandler | undefined; onContextMenu?: MouseEventHandler | undefined; onDblClick?: MouseEventHandler | undefined; onDrag?: DragEventHandler | undefined; onDragEnd?: DragEventHandler | undefined; onDragEnter?: DragEventHandler | undefined; onDragExit?: DragEventHandler | undefined; onDragLeave?: DragEventHandler | undefined; onDragOver?: DragEventHandler | undefined; onDragStart?: DragEventHandler | undefined; onDrop?: DragEventHandler | undefined; onMouseDown?: MouseEventHandler | undefined; onMouseEnter?: MouseEventHandler | undefined; onMouseLeave?: MouseEventHandler | undefined; onMouseMove?: MouseEventHandler | undefined; onMouseOut?: MouseEventHandler | undefined; onMouseOver?: MouseEventHandler | undefined; onMouseUp?: MouseEventHandler | undefined; // Selection Events onSelect?: InfernoEventHandler | undefined; // Touch Events onTouchCancel?: TouchEventHandler | undefined; onTouchEnd?: TouchEventHandler | undefined; onTouchMove?: TouchEventHandler | undefined; onTouchStart?: TouchEventHandler | undefined; // Pointer Events onPointerDown?: PointerEventHandler | undefined; onPointerMove?: PointerEventHandler | undefined; onPointerUp?: PointerEventHandler | undefined; onPointerCancel?: PointerEventHandler | undefined; onPointerEnter?: PointerEventHandler | undefined; onPointerLeave?: PointerEventHandler | undefined; onPointerOver?: PointerEventHandler | undefined; onPointerOut?: PointerEventHandler | undefined; // UI Events onScroll?: UIEventHandler | undefined; // Wheel Events onWheel?: WheelEventHandler | undefined; // Animation Events onAnimationStart?: AnimationEventHandler | undefined; onAnimationEnd?: AnimationEventHandler | undefined; onAnimationIteration?: AnimationEventHandler | undefined; // Transition Events onTransitionEnd?: TransitionEventHandler | undefined; // NATIVE EVENTS // Clipboard Events oncopy?: ClipboardEventHandler | undefined; oncut?: ClipboardEventHandler | undefined; onpaste?: ClipboardEventHandler | undefined; // Composition Events oncompositionend?: CompositionEventHandler | undefined; oncompositionstart?: CompositionEventHandler | undefined; oncompositionupdate?: CompositionEventHandler | undefined; // Focus Events onfocus?: FocusEventHandler | undefined; onblur?: FocusEventHandler | undefined; // Form Events onchange?: FormEventHandler | undefined | null; onbeforeinput?: FormEventHandler | undefined; oninput?: FormEventHandler | undefined; onreset?: FormEventHandler | undefined; onsubmit?: FormEventHandler | undefined; oninvalid?: FormEventHandler | undefined; // Image Events onload?: InfernoEventHandler | undefined; onerror?: InfernoEventHandler | undefined; // also a Media Event // Keyboard Events onkeydown?: KeyboardEventHandler | undefined; onkeypress?: KeyboardEventHandler | undefined; onkeyup?: KeyboardEventHandler | undefined; // Media Events onabort?: InfernoEventHandler | undefined; oncanplay?: InfernoEventHandler | undefined; oncanplaythrough?: InfernoEventHandler | undefined; ondurationchange?: InfernoEventHandler | undefined; onemptied?: InfernoEventHandler | undefined; onencrypted?: InfernoEventHandler | undefined; onended?: InfernoEventHandler | undefined; onloadeddata?: InfernoEventHandler | undefined; onloadedmetadata?: InfernoEventHandler | undefined; onloadstart?: InfernoEventHandler | undefined; onpause?: InfernoEventHandler | undefined; onplay?: InfernoEventHandler | undefined; onplaying?: InfernoEventHandler | undefined; onprogress?: InfernoEventHandler | undefined; onratechange?: InfernoEventHandler | undefined; onseeked?: InfernoEventHandler | undefined; onseeking?: InfernoEventHandler | undefined; onstalled?: InfernoEventHandler | undefined; onsuspend?: InfernoEventHandler | undefined; ontimeupdate?: InfernoEventHandler | undefined; onvolumechange?: InfernoEventHandler | undefined; onwaiting?: InfernoEventHandler | undefined; // MouseEvents onauxclick?: MouseEventHandler | undefined; onclick?: MouseEventHandler | undefined; oncontextmenu?: MouseEventHandler | undefined; ondblclick?: MouseEventHandler | undefined; ondrag?: DragEventHandler | undefined; ondragend?: DragEventHandler | undefined; ondragenter?: DragEventHandler | undefined; ondragexit?: DragEventHandler | undefined; ondragLeave?: DragEventHandler | undefined; ondragover?: DragEventHandler | undefined; ondragstart?: DragEventHandler | undefined; ondrop?: DragEventHandler | undefined; onmousedown?: MouseEventHandler | undefined; onmouseenter?: MouseEventHandler | undefined; onmouseleave?: MouseEventHandler | undefined; onmousemove?: MouseEventHandler | undefined; onmouseout?: MouseEventHandler | undefined; onmouseover?: MouseEventHandler | undefined; onmouseup?: MouseEventHandler | undefined; // Selection Events onselect?: InfernoEventHandler | undefined; // Touch Events ontouchcancel?: TouchEventHandler | undefined; ontouchend?: TouchEventHandler | undefined; ontouchmove?: TouchEventHandler | undefined; ontouchstart?: TouchEventHandler | undefined; // Pointer Events onpointerdown?: PointerEventHandler | undefined; onpointermove?: PointerEventHandler | undefined; onpointerup?: PointerEventHandler | undefined; onpointercancel?: PointerEventHandler | undefined; onpointerenter?: PointerEventHandler | undefined; onpointerleave?: PointerEventHandler | undefined; onpointerover?: PointerEventHandler | undefined; onpointerout?: PointerEventHandler | undefined; // UI Events onscroll?: UIEventHandler | undefined; // Wheel Events onwheel?: WheelEventHandler | undefined; // Animation Events onanimationstart?: AnimationEventHandler | undefined; onanimationend?: AnimationEventHandler | undefined; onanimationiteration?: AnimationEventHandler | undefined; // Transition Events ontransitionend?: TransitionEventHandler | undefined; } // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ interface AriaAttributes { /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */ 'aria-activedescendant'?: string | null | undefined; /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ 'aria-atomic'?: Booleanish | null | undefined; /** * Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be * presented if they are made. */ 'aria-autocomplete'?: | 'none' | 'inline' | 'list' | 'both' | null | undefined; /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ /** * Defines a string value that labels the current element, which is intended to be converted into Braille. * @see aria-label. */ 'aria-braillelabel'?: string | null | undefined; /** * Defines a human-readable, author-localized abbreviated description for the role of an element, which is intended to be converted into Braille. * @see aria-roledescription. */ 'aria-brailleroledescription'?: string | null | undefined; 'aria-busy'?: Booleanish | null | undefined; /** * Indicates the current "checked" state of checkboxes, radio buttons, and other widgets. * @see aria-pressed @see aria-selected. */ 'aria-checked'?: boolean | 'false' | 'mixed' | 'true' | null | undefined; /** * Defines the total number of columns in a table, grid, or treegrid. * @see aria-colindex. */ 'aria-colcount'?: number | null | undefined; /** * Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid. * @see aria-colcount @see aria-colspan. */ 'aria-colindex'?: number | null | undefined; /** * Defines a human readable text alternative of aria-colindex. * @see aria-rowindextext. */ 'aria-colindextext'?: string | null | undefined; /** * Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. * @see aria-colindex @see aria-rowspan. */ 'aria-colspan'?: number | null | undefined; /** * Identifies the element (or elements) whose contents or presence are controlled by the current element. * @see aria-owns. */ 'aria-controls'?: string | null | undefined; /** Indicates the element that represents the current item within a container or set of related elements. */ 'aria-current'?: | boolean | 'false' | 'true' | 'page' | 'step' | 'location' | 'date' | 'time' | null | undefined; /** * Identifies the element (or elements) that describes the object. * @see aria-labelledby */ 'aria-describedby'?: string | null | undefined; /** * Defines a string value that describes or annotates the current element. * @see related aria-describedby. */ 'aria-description'?: string | null | undefined; /** * Identifies the element that provides a detailed, extended description for the object. * @see aria-describedby. */ 'aria-details'?: string | null | undefined; /** * Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. * @see aria-hidden @see aria-readonly. */ 'aria-disabled'?: Booleanish | null | undefined; /** * Indicates what functions can be performed when a dragged object is released on the drop target. * @deprecated in ARIA 1.1 */ 'aria-dropeffect'?: | 'none' | 'copy' | 'execute' | 'link' | 'move' | 'popup' | null | undefined; /** * Identifies the element that provides an error message for the object. * @see aria-invalid @see aria-describedby. */ 'aria-errormessage'?: string | null | undefined; /** Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. */ 'aria-expanded'?: Booleanish | null | undefined; /** * Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, * allows assistive technology to override the general default of reading in document source order. */ 'aria-flowto'?: string | null | undefined; /** * Indicates an element's "grabbed" state in a drag-and-drop operation. * @deprecated in ARIA 1.1 */ 'aria-grabbed'?: Booleanish | null | undefined; /** Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. */ 'aria-haspopup'?: | boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | null | undefined; /** * Indicates whether the element is exposed to an accessibility API. * @see aria-disabled. */ 'aria-hidden'?: Booleanish | null | undefined; /** * Indicates the entered value does not conform to the format expected by the application. * @see aria-errormessage. */ 'aria-invalid'?: | boolean | 'false' | 'true' | 'grammar' | 'spelling' | null | undefined; /** Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element. */ 'aria-keyshortcuts'?: string | null | undefined; /** * Defines a string value that labels the current element. * @see aria-labelledby. */ 'aria-label'?: string | null | undefined; /** * Identifies the element (or elements) that labels the current element. * @see aria-describedby. */ 'aria-labelledby'?: string | null | undefined; /** Defines the hierarchical level of an element within a structure. */ 'aria-level'?: number | null | undefined; /** Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region. */ 'aria-live'?: 'off' | 'assertive' | 'polite' | null | undefined; /** Indicates whether an element is modal when displayed. */ 'aria-modal'?: Booleanish | null | undefined; /** Indicates whether a text box accepts multiple lines of input or only a single line. */ 'aria-multiline'?: Booleanish | null | undefined; /** Indicates that the user may select more than one item from the current selectable descendants. */ 'aria-multiselectable'?: Booleanish | null | undefined; /** Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous. */ 'aria-orientation'?: 'horizontal' | 'vertical' | null | undefined; /** * Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship * between DOM elements where the DOM hierarchy cannot be used to represent the relationship. * @see aria-controls. */ 'aria-owns'?: string | null | undefined; /** * Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. * A hint could be a sample value or a brief description of the expected format. */ 'aria-placeholder'?: string | null | undefined; /** * Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. * @see aria-setsize. */ 'aria-posinset'?: number | null | undefined; /** * Indicates the current "pressed" state of toggle buttons. * @see aria-checked @see aria-selected. */ 'aria-pressed'?: boolean | 'false' | 'mixed' | 'true' | null | undefined; /** * Indicates that the element is not editable, but is otherwise operable. * @see aria-disabled. */ 'aria-readonly'?: Booleanish | null | undefined; /** * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. * @see aria-atomic. */ 'aria-relevant'?: | 'additions' | 'additions removals' | 'additions text' | 'all' | 'removals' | 'removals additions' | 'removals text' | 'text' | 'text additions' | 'text removals' | null | undefined; /** Indicates that user input is required on the element before a form may be submitted. */ 'aria-required'?: Booleanish | null | undefined; /** Defines a human-readable, author-localized description for the role of an element. */ 'aria-roledescription'?: string | null | undefined; /** * Defines the total number of rows in a table, grid, or treegrid. * @see aria-rowindex. */ 'aria-rowcount'?: number | null | undefined; /** * Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid. * @see aria-rowcount @see aria-rowspan. */ 'aria-rowindex'?: number | null | undefined; /** * Defines a human readable text alternative of aria-rowindex. * @see aria-colindextext. */ 'aria-rowindextext'?: string | null | undefined; /** * Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. * @see aria-rowindex @see aria-colspan. */ 'aria-rowspan'?: number | null | undefined; /** * Indicates the current "selected" state of various widgets. * @see aria-checked @see aria-pressed. */ 'aria-selected'?: Booleanish | null | undefined; /** * Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. * @see aria-posinset. */ 'aria-setsize'?: number | null | undefined; /** Indicates if items in a table or grid are sorted in ascending or descending order. */ 'aria-sort'?: | 'none' | 'ascending' | 'descending' | 'other' | null | undefined; /** Defines the maximum allowed value for a range widget. */ 'aria-valuemax'?: number | null | undefined; /** Defines the minimum allowed value for a range widget. */ 'aria-valuemin'?: number | null | undefined; /** * Defines the current value for a range widget. * @see aria-valuetext. */ 'aria-valuenow'?: number | null | undefined; /** Defines the human readable text alternative of aria-valuenow for a range widget. */ 'aria-valuetext'?: string | null | undefined; } // All the WAI-ARIA 1.1 role attribute values from https://www.w3.org/TR/wai-aria-1.1/#role_definitions type AriaRole = | 'alert' | 'alertdialog' | 'application' | 'article' | 'banner' | 'button' | 'cell' | 'checkbox' | 'columnheader' | 'combobox' | 'complementary' | 'contentinfo' | 'definition' | 'dialog' | 'directory' | 'document' | 'feed' | 'figure' | 'form' | 'grid' | 'gridcell' | 'group' | 'heading' | 'img' | 'link' | 'list' | 'listbox' | 'listitem' | 'log' | 'main' | 'marquee' | 'math' | 'menu' | 'menubar' | 'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'navigation' | 'none' | 'note' | 'option' | 'presentation' | 'progressbar' | 'radio' | 'radiogroup' | 'region' | 'row' | 'rowgroup' | 'rowheader' | 'scrollbar' | 'search' | 'searchbox' | 'separator' | 'slider' | 'spinbutton' | 'status' | 'switch' | 'tab' | 'table' | 'tablist' | 'tabpanel' | 'term' | 'textbox' | 'timer' | 'toolbar' | 'tooltip' | 'tree' | 'treegrid' | 'treeitem' | (string & {}); interface CssVariables { [key: `--${string}`]: string; } interface HTMLAttributes extends AriaAttributes, DOMAttributes { // Inferno-specific Attributes class?: string | null | undefined; defaultChecked?: boolean | null | undefined; defaultValue?: string | number | readonly string[] | null | undefined; // Standard HTML Attributes accessKey?: string | null | undefined; autoCapitalize?: | 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters' | null | undefined | (string & {}); autoFocus?: boolean | null | undefined; className?: string | null | undefined; contentEditable?: | Booleanish | 'inherit' | 'plaintext-only' | null | undefined; contextMenu?: string | null | undefined; dir?: string | null | undefined; draggable?: Booleanish | null | undefined; enterKeyHint?: | 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | null | undefined; hidden?: boolean | null | undefined; id?: string | null | undefined; lang?: string | null | undefined; nonce?: string | null | undefined; slot?: string | null | undefined; spellCheck?: Booleanish | null | undefined; style?: PropertiesHyphen | string | null | undefined | CssVariables; tabIndex?: number | null | undefined; title?: string | null | undefined; translate?: 'yes' | 'no' | null | undefined; // Unknown radioGroup?: string | null | undefined; // , // WAI-ARIA role?: AriaRole | null | undefined; // RDFa Attributes about?: string | null | undefined; content?: string | null | undefined; datatype?: string | null | undefined; inlist?: any; prefix?: string | null | undefined; property?: string | null | undefined; rel?: string | null | undefined; resource?: string | null | undefined; rev?: string | null | undefined; typeof?: string | null | undefined; vocab?: string | null | undefined; // Non-standard Attributes autoCorrect?: string | null | undefined; autoSave?: string | null | undefined; color?: string | null | undefined; itemProp?: string | null | undefined; itemScope?: boolean | null | undefined; itemType?: string | null | undefined; itemID?: string | null | undefined; itemRef?: string | null | undefined; results?: number | null | undefined; security?: string | null | undefined; unselectable?: 'on' | 'off' | null | undefined; // Living Standard /** * Hints at the type of data that might be entered by the user while editing the element or its contents * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute} */ inputMode?: | 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | null | undefined; /** * Specify that a standard HTML element should behave like a defined custom built-in element * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is} */ is?: string | null | undefined; } interface AllHTMLAttributes extends HTMLAttributes { // Standard HTML Attributes accept?: string | null | undefined; acceptCharset?: string | null | undefined; action?: string | null | undefined; allowFullScreen?: boolean | null | undefined; allowTransparency?: boolean | null | undefined; alt?: string | null | undefined; as?: string | null | undefined; async?: boolean | null | undefined; autoComplete?: string | null | undefined; autoPlay?: boolean | null | undefined; capture?: boolean | 'user' | 'environment' | null | undefined; cellPadding?: number | string | null | undefined; cellSpacing?: number | string | null | undefined; charSet?: string | null | undefined; challenge?: string | null | undefined; checked?: boolean | null | undefined; cite?: string | null | undefined; classID?: string | null | undefined; cols?: number | null | undefined; colSpan?: number | null | undefined; controls?: boolean | null | undefined; coords?: string | null | undefined; crossOrigin?: CrossOrigin; data?: string | null | undefined; dateTime?: string | null | undefined; default?: boolean | null | undefined; defer?: boolean | null | undefined; disabled?: boolean | null | undefined; download?: any; encType?: string | null | undefined; form?: string | null | undefined; formAction?: string | null | undefined; formEncType?: string | null | undefined; formMethod?: string | null | undefined; formNoValidate?: boolean | null | undefined; formTarget?: string | null | undefined; frameBorder?: number | string | null | undefined; headers?: string | null | undefined; height?: number | string | null | undefined; high?: number | null | undefined; href?: string | null | undefined; hrefLang?: string | null | undefined; htmlFor?: string | null | undefined; httpEquiv?: string | null | undefined; integrity?: string | null | undefined; keyParams?: string | null | undefined; keyType?: string | null | undefined; kind?: string | null | undefined; label?: string | null | undefined; list?: string | null | undefined; loop?: boolean | null | undefined; low?: number | null | undefined; manifest?: string | null | undefined; marginHeight?: number | null | undefined; marginWidth?: number | null | undefined; max?: number | string | null | undefined; maxLength?: number | null | undefined; media?: string | null | undefined; mediaGroup?: string | null | undefined; method?: string | null | undefined; min?: number | string | null | undefined; minLength?: number | null | undefined; multiple?: boolean | null | undefined; muted?: boolean | null | undefined; name?: string | null | undefined; noValidate?: boolean | null | undefined; open?: boolean | null | undefined; optimum?: number | null | undefined; pattern?: string | null | undefined; placeholder?: string | null | undefined; playsInline?: boolean | null | undefined; poster?: string | null | undefined; preload?: string | null | undefined; readOnly?: boolean | null | undefined; required?: boolean | null | undefined; reversed?: boolean | null | undefined; rows?: number | null | undefined; rowSpan?: number | null | undefined; sandbox?: string | null | undefined; scope?: string | null | undefined; scoped?: boolean | null | undefined; scrolling?: string | null | undefined; seamless?: boolean | null | undefined; selected?: boolean | null | undefined; shape?: string | null | undefined; size?: number | null | undefined; sizes?: string | null | undefined; span?: number | null | undefined; src?: string | null | undefined; srcDoc?: string | null | undefined; srcLang?: string | null | undefined; srcSet?: string | null | undefined; start?: number | null | undefined; step?: number | string | null | undefined; summary?: string | null | undefined; target?: string | null | undefined; type?: string | null | undefined; useMap?: string | null | undefined; value?: string | readonly string[] | number | null | undefined; width?: number | string | null | undefined; wmode?: string | null | undefined; wrap?: string | null | undefined; } type HTMLAttributeReferrerPolicy = | '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'; type HTMLAttributeAnchorTarget = | '_self' | '_blank' | '_parent' | '_top' | (string & {}); interface AnchorHTMLAttributes extends HTMLAttributes { download?: any; href?: string | null | undefined; hrefLang?: string | null | undefined; media?: string | null | undefined; ping?: string | null | undefined; target?: HTMLAttributeAnchorTarget | null | undefined; type?: string | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; } interface AudioHTMLAttributes extends MediaHTMLAttributes {} interface AreaHTMLAttributes extends HTMLAttributes { alt?: string | null | undefined; coords?: string | null | undefined; download?: any; href?: string | null | undefined; hrefLang?: string | null | undefined; media?: string | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; shape?: string | null | undefined; target?: string | null | undefined; } interface BaseHTMLAttributes extends HTMLAttributes { href?: string | null | undefined; target?: string | null | undefined; } interface BlockquoteHTMLAttributes extends HTMLAttributes { cite?: string | null | undefined; } interface ButtonHTMLAttributes extends HTMLAttributes { disabled?: boolean | null | undefined; form?: string | null | undefined; formAction?: string | null | undefined; formEncType?: string | null | undefined; formMethod?: string | null | undefined; formNoValidate?: boolean | null | undefined; formTarget?: string | null | undefined; name?: string | null | undefined; type?: 'submit' | 'reset' | 'button' | null | undefined; value?: string | readonly string[] | number | null | undefined; } interface CanvasHTMLAttributes extends HTMLAttributes { height?: number | string | null | undefined; width?: number | string | null | undefined; } interface ColHTMLAttributes extends HTMLAttributes { span?: number | null | undefined; width?: number | string | null | undefined; } interface ColgroupHTMLAttributes extends HTMLAttributes { span?: number | null | undefined; } interface DataHTMLAttributes extends HTMLAttributes { value?: string | readonly string[] | number | null | undefined; } interface DetailsHTMLAttributes extends HTMLAttributes { open?: boolean | null | undefined; onToggle?: InfernoEventHandler | null | undefined; name?: string | null | undefined; } interface DelHTMLAttributes extends HTMLAttributes { cite?: string | null | undefined; dateTime?: string | null | undefined; } interface DialogHTMLAttributes extends HTMLAttributes { onCancel?: InfernoEventHandler | null | undefined; onClose?: InfernoEventHandler | null | undefined; open?: boolean | null | undefined; } interface EmbedHTMLAttributes extends HTMLAttributes { height?: number | string | null | undefined; src?: string | null | undefined; type?: string | null | undefined; width?: number | string | null | undefined; } interface FieldsetHTMLAttributes extends HTMLAttributes { disabled?: boolean | null | undefined; form?: string | null | undefined; name?: string | null | undefined; } interface FormHTMLAttributes extends HTMLAttributes { acceptCharset?: string | null | undefined; action?: string | null | undefined; autoComplete?: string | null | undefined; encType?: string | null | undefined; method?: string | null | undefined; name?: string | null | undefined; noValidate?: boolean | null | undefined; target?: string | null | undefined; } interface HtmlHTMLAttributes extends HTMLAttributes { manifest?: string | null | undefined; } interface IframeHTMLAttributes extends HTMLAttributes { allow?: string | null | undefined; allowFullScreen?: boolean | null | undefined; allowTransparency?: boolean | null | undefined; /** @deprecated */ frameBorder?: number | string | null | undefined; height?: number | string | null | undefined; loading?: 'eager' | 'lazy' | null | undefined; /** @deprecated */ marginHeight?: number | null | undefined; /** @deprecated */ marginWidth?: number | null | undefined; name?: string | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; sandbox?: string | null | undefined; /** @deprecated */ scrolling?: string | null | undefined; seamless?: boolean | null | undefined; src?: string | null | undefined; srcDoc?: string | null | undefined; width?: number | string | null | undefined; } interface ImgHTMLAttributes extends HTMLAttributes { alt?: string | null | undefined; crossOrigin?: CrossOrigin; decoding?: 'async' | 'auto' | 'sync' | null | undefined; fetchPriority?: 'high' | 'low' | 'auto'; height?: number | string | null | undefined; loading?: 'eager' | 'lazy' | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; sizes?: string | null | undefined; src?: string | null | undefined; srcSet?: string | null | undefined; useMap?: string | null | undefined; width?: number | string | null | undefined; } interface InsHTMLAttributes extends HTMLAttributes { cite?: string | null | undefined; dateTime?: string | null | undefined; } type HTMLInputTypeAttribute = | 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'reset' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week' | (string & {}); type AutoFillAddressKind = 'billing' | 'shipping'; type AutoFillBase = '' | 'off' | 'on'; type AutoFillContactField = | 'email' | 'tel' | 'tel-area-code' | 'tel-country-code' | 'tel-extension' | 'tel-local' | 'tel-local-prefix' | 'tel-local-suffix' | 'tel-national'; type AutoFillContactKind = 'home' | 'mobile' | 'work'; type AutoFillCredentialField = 'webauthn'; type AutoFillNormalField = | 'additional-name' | 'address-level1' | 'address-level2' | 'address-level3' | 'address-level4' | 'address-line1' | 'address-line2' | 'address-line3' | 'bday-day' | 'bday-month' | 'bday-year' | 'cc-csc' | 'cc-exp' | 'cc-exp-month' | 'cc-exp-year' | 'cc-family-name' | 'cc-given-name' | 'cc-name' | 'cc-number' | 'cc-type' | 'country' | 'country-name' | 'current-password' | 'family-name' | 'given-name' | 'honorific-prefix' | 'honorific-suffix' | 'name' | 'new-password' | 'one-time-code' | 'organization' | 'postal-code' | 'street-address' | 'transaction-amount' | 'transaction-currency' | 'username'; type OptionalPrefixToken = `${T} ` | ''; type OptionalPostfixToken = ` ${T}` | ''; type AutoFillField = | AutoFillNormalField | `${OptionalPrefixToken}${AutoFillContactField}`; type AutoFillSection = `section-${string}`; type AutoFill = | AutoFillBase | `${OptionalPrefixToken}${OptionalPrefixToken}${AutoFillField}${OptionalPostfixToken}`; type HTMLInputAutoCompleteAttribute = AutoFill | (string & {}); interface InputHTMLAttributes extends HTMLAttributes { accept?: string | null | undefined; alt?: string | null | undefined; autoComplete?: HTMLInputAutoCompleteAttribute | null | undefined; capture?: boolean | 'user' | 'environment' | null | undefined; // https://www.w3.org/TR/html-media-capture/#the-capture-attribute checked?: boolean | null | undefined; disabled?: boolean | null | undefined; form?: string | null | undefined; formAction?: string | null | undefined; formEncType?: string | null | undefined; formMethod?: string | null | undefined; formNoValidate?: boolean | null | undefined; formTarget?: string | null | undefined; height?: number | string | null | undefined; indeterminate?: boolean | null | undefined; list?: string | null | undefined; max?: number | string | null | undefined; maxLength?: number | null | undefined; min?: number | string | null | undefined; minLength?: number | null | undefined; multiple?: boolean | null | undefined; name?: string | null | undefined; pattern?: string | null | undefined; placeholder?: string | null | undefined; readOnly?: boolean | null | undefined; required?: boolean | null | undefined; size?: number | null | undefined; src?: string | null | undefined; step?: number | string | null | undefined; type?: HTMLInputTypeAttribute | null | undefined; value?: string | readonly string[] | number | null | undefined; width?: number | string | null | undefined; } interface KeygenHTMLAttributes extends HTMLAttributes { challenge?: string | null | undefined; disabled?: boolean | null | undefined; form?: string | null | undefined; keyType?: string | null | undefined; keyParams?: string | null | undefined; name?: string | null | undefined; } interface LabelHTMLAttributes extends HTMLAttributes { form?: string | null | undefined; htmlFor?: string | null | undefined; for?: string | null | undefined; } interface LiHTMLAttributes extends HTMLAttributes { value?: string | readonly string[] | number | null | undefined; } interface LinkHTMLAttributes extends HTMLAttributes { as?: string | null | undefined; crossOrigin?: CrossOrigin; fetchPriority?: 'high' | 'low' | 'auto'; href?: string | null | undefined; hrefLang?: string | null | undefined; integrity?: string | null | undefined; media?: string | null | undefined; imageSrcSet?: string | null | undefined; imageSizes?: string | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; sizes?: string | null | undefined; type?: string | null | undefined; charSet?: string | null | undefined; } interface MapHTMLAttributes extends HTMLAttributes { name?: string | null | undefined; } interface MenuHTMLAttributes extends HTMLAttributes { type?: string | null | undefined; } interface MediaHTMLAttributes extends HTMLAttributes { autoPlay?: boolean | null | undefined; controls?: boolean | null | undefined; controlsList?: string | null | undefined; crossOrigin?: CrossOrigin; loop?: boolean | null | undefined; mediaGroup?: string | null | undefined; muted?: boolean | null | undefined; playsInline?: boolean | null | undefined; preload?: string | null | undefined; src?: string | null | undefined; } interface MetaHTMLAttributes extends HTMLAttributes { charSet?: string | null | undefined; content?: string | null | undefined; httpEquiv?: string | null | undefined; media?: string | null | undefined; name?: string | null | undefined; } interface MeterHTMLAttributes extends HTMLAttributes { form?: string | null | undefined; high?: number | null | undefined; low?: number | null | undefined; max?: number | string | null | undefined; min?: number | string | null | undefined; optimum?: number | null | undefined; value?: string | readonly string[] | number | null | undefined; } interface QuoteHTMLAttributes extends HTMLAttributes { cite?: string | null | undefined; } interface ObjectHTMLAttributes extends HTMLAttributes { classID?: string | null | undefined; data?: string | null | undefined; form?: string | null | undefined; height?: number | string | null | undefined; name?: string | null | undefined; type?: string | null | undefined; useMap?: string | null | undefined; width?: number | string | null | undefined; wmode?: string | null | undefined; } interface OlHTMLAttributes extends HTMLAttributes { reversed?: boolean | null | undefined; start?: number | null | undefined; type?: '1' | 'a' | 'A' | 'i' | 'I' | null | undefined; } interface OptgroupHTMLAttributes extends HTMLAttributes { disabled?: boolean | null | undefined; label?: string | null | undefined; } interface OptionHTMLAttributes extends HTMLAttributes { disabled?: boolean | null | undefined; label?: string | null | undefined; selected?: boolean | null | undefined; value?: string | readonly string[] | number | null | undefined; } interface OutputHTMLAttributes extends HTMLAttributes { form?: string | null | undefined; htmlFor?: string | null | undefined; name?: string | null | undefined; } interface ParamHTMLAttributes extends HTMLAttributes { name?: string | null | undefined; value?: string | readonly string[] | number | null | undefined; } interface ProgressHTMLAttributes extends HTMLAttributes { max?: number | string | null | undefined; value?: string | readonly string[] | number | null | undefined; } interface SlotHTMLAttributes extends HTMLAttributes { name?: string | null | undefined; } interface ScriptHTMLAttributes extends HTMLAttributes { async?: boolean | null | undefined; /** @deprecated */ charSet?: string | null | undefined; crossOrigin?: CrossOrigin; defer?: boolean | null | undefined; integrity?: string | null | undefined; noModule?: boolean | null | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | null | undefined; src?: string | null | undefined; type?: string | null | undefined; } interface SelectHTMLAttributes extends HTMLAttributes { autoComplete?: string | null | undefined; disabled?: boolean | null | undefined; form?: string | null | undefined; multiple?: boolean | null | undefined; name?: string | null | undefined; required?: boolean | null | undefined; size?: number | null | undefined; value?: string | readonly string[] | number | null | undefined; selectedIndex?: number | null | undefined; } interface SourceHTMLAttributes extends HTMLAttributes { height?: number | string | null | undefined; media?: string | null | undefined; sizes?: string | null | undefined; src?: string | null | undefined; srcSet?: string | null | undefined; type?: string | null | undefined; width?: number | string | null | undefined; } interface StyleHTMLAttributes extends HTMLAttributes { media?: string | null | undefined; scoped?: boolean | null | undefined; type?: string | null | undefined; } interface TableHTMLAttributes extends HTMLAttributes { align?: 'left' | 'center' | 'right' | null | undefined; bgcolor?: string | null | undefined; border?: number | null | undefined; cellPadding?: number | string | null | undefined; cellSpacing?: number | string | null | undefined; frame?: boolean | null | undefined; rules?: 'none' | 'groups' | 'rows' | 'columns' | 'all' | null | undefined; summary?: string | null | undefined; width?: number | string | null | undefined; } interface TextareaHTMLAttributes extends HTMLAttributes { autoComplete?: string | null | undefined; autoFocus?: boolean | null | undefined; cols?: number | null | undefined; dirName?: string | null | undefined; disabled?: boolean | null | undefined; form?: string | null | undefined; maxLength?: number | null | undefined; minLength?: number | null | undefined; name?: string | null | undefined; placeholder?: string | null | undefined; readOnly?: boolean | null | undefined; required?: boolean | null | undefined; rows?: number | null | undefined; value?: string | readonly string[] | number | null | undefined; wrap?: string | null | undefined; } interface TdHTMLAttributes extends HTMLAttributes { align?: 'left' | 'center' | 'right' | 'justify' | 'char' | null | undefined; colSpan?: number | null | undefined; headers?: string | null | undefined; rowSpan?: number | null | undefined; scope?: string | null | undefined; abbr?: string | null | undefined; height?: number | string | null | undefined; width?: number | string | null | undefined; valign?: 'top' | 'middle' | 'bottom' | 'baseline' | null | undefined; } interface ThHTMLAttributes extends HTMLAttributes { align?: 'left' | 'center' | 'right' | 'justify' | 'char' | null | undefined; colSpan?: number | null | undefined; headers?: string | null | undefined; rowSpan?: number | null | undefined; scope?: string | null | undefined; abbr?: string | null | undefined; } interface TimeHTMLAttributes extends HTMLAttributes { dateTime?: string | null | undefined; } interface TrackHTMLAttributes extends HTMLAttributes { default?: boolean | null | undefined; kind?: string | null | undefined; label?: string | null | undefined; src?: string | null | undefined; srcLang?: string | null | undefined; } interface VideoHTMLAttributes extends MediaHTMLAttributes { type?: string | null | undefined; height?: number | string | null | undefined; playsInline?: boolean | null | undefined; poster?: string | null | undefined; width?: number | string | null | undefined; disablePictureInPicture?: boolean | null | undefined; disableRemotePlayback?: boolean | null | undefined; } // this list is "complete" in that it contains every SVG attribute // // The three broad type categories are (in order of restrictiveness): // - "number | string" // - "string" // - union of string literals interface SVGAttributes extends AriaAttributes, DOMAttributes { class?: string | null | undefined; // Attributes which also defined in HTMLAttributes // See comment in SVGDOMPropertyConfig.js className?: string | null | undefined; color?: string | null | undefined; height?: number | string | null | undefined; id?: string | null | undefined; lang?: string | null | undefined; max?: number | string | null | undefined; media?: string | null | undefined; method?: string | null | undefined; min?: number | string | null | undefined; name?: string | null | undefined; style?: any; target?: string | null | undefined; type?: string | null | undefined; width?: number | string | null | undefined; // Other HTML properties supported by SVG elements in browsers role?: AriaRole | null | undefined; tabIndex?: number | null | undefined; crossOrigin?: 'anonymous' | 'use-credentials' | '' | null | undefined; // SVG Specific attributes accentHeight?: number | string | null | undefined; 'accent-height'?: number | string | null | undefined; accumulate?: 'none' | 'sum' | null | undefined; additive?: 'replace' | 'sum' | null | undefined; alignmentBaseline?: | 'auto' | 'baseline' | 'before-edge' | 'text-before-edge' | 'middle' | 'central' | 'after-edge' | 'text-after-edge' | 'ideographic' | 'alphabetic' | 'hanging' | 'mathematical' | 'inherit' | null | undefined; 'alignment-baseline'?: | 'auto' | 'baseline' | 'before-edge' | 'text-before-edge' | 'middle' | 'central' | 'after-edge' | 'text-after-edge' | 'ideographic' | 'alphabetic' | 'hanging' | 'mathematical' | 'inherit' | null | undefined; allowReorder?: 'no' | 'yes' | null | undefined; alphabetic?: number | string | null | undefined; amplitude?: number | string | null | undefined; arabicForm?: | 'initial' | 'medial' | 'terminal' | 'isolated' | null | undefined; 'arabic-form'?: | 'initial' | 'medial' | 'terminal' | 'isolated' | null | undefined; ascent?: number | string | null | undefined; attributeName?: string | null | undefined; attributeType?: string | null | undefined; autoReverse?: Booleanish | null | undefined; azimuth?: number | string | null | undefined; baseFrequency?: number | string | null | undefined; baselineShift?: number | string | null | undefined; 'baseline-shift'?: number | string | null | undefined; baseProfile?: number | string | null | undefined; bbox?: number | string | null | undefined; begin?: number | string | null | undefined; bias?: number | string | null | undefined; by?: number | string | null | undefined; calcMode?: number | string | null | undefined; capHeight?: number | string | null | undefined; 'cap-height'?: number | string | null | undefined; clip?: number | string | null | undefined; clipPath?: string | null | undefined; 'clip-path'?: string | null | undefined; clipPathUnits?: number | string | null | undefined; clipRule?: number | string | null | undefined; 'clip-rule'?: number | string | null | undefined; colorInterpolation?: number | string | null | undefined; 'color-interpolation'?: number | string | null | undefined; colorInterpolationFilters?: | 'auto' | 'sRGB' | 'linearRGB' | 'inherit' | null | undefined; 'color-interpolation-filters'?: | 'auto' | 'sRGB' | 'linearRGB' | 'inherit' | null | undefined; colorProfile?: number | string | null | undefined; 'color-profile'?: number | string | null | undefined; colorRendering?: number | string | null | undefined; 'color-rendering'?: number | string | null | undefined; contentScriptType?: number | string | null | undefined; contentStyleType?: number | string | null | undefined; cursor?: number | string | null | undefined; cx?: number | string | null | undefined; cy?: number | string | null | undefined; d?: string | null | undefined; decelerate?: number | string | null | undefined; descent?: number | string | null | undefined; diffuseConstant?: number | string | null | undefined; direction?: number | string | null | undefined; display?: number | string | null | undefined; divisor?: number | string | null | undefined; dominantBaseline?: number | string | null | undefined; 'dominant-baseline'?: number | string | null | undefined; dur?: number | string | null | undefined; dx?: number | string | null | undefined; dy?: number | string | null | undefined; edgeMode?: number | string | null | undefined; elevation?: number | string | null | undefined; enableBackground?: number | string | null | undefined; 'enable-background'?: number | string | null | undefined; end?: number | string | null | undefined; exponent?: number | string | null | undefined; externalResourcesRequired?: Booleanish | null | undefined; fill?: string | null | undefined; fillOpacity?: number | string | null | undefined; 'fill-opacity'?: number | string | null | undefined; fillRule?: 'nonzero' | 'evenodd' | 'inherit' | null | undefined; 'fill-rule'?: 'nonzero' | 'evenodd' | 'inherit' | null | undefined; filter?: string | null | undefined; filterRes?: number | string | null | undefined; filterUnits?: number | string | null | undefined; floodColor?: number | string | null | undefined; 'flood-color'?: number | string | null | undefined; floodOpacity?: number | string | null | undefined; 'flood-opacity'?: number | string | null | undefined; focusable?: Booleanish | 'auto' | null | undefined; fontFamily?: string | null | undefined; 'font-family'?: string | null | undefined; fontSize?: number | string | null | undefined; 'font-size'?: number | string | null | undefined; fontSizeAdjust?: number | string | null | undefined; 'font-size-adjust'?: number | string | null | undefined; fontStretch?: number | string | null | undefined; 'font-stretch'?: number | string | null | undefined; fontStyle?: number | string | null | undefined; 'font-style'?: number | string | null | undefined; fontVariant?: number | string | null | undefined; 'font-variant'?: number | string | null | undefined; fontWeight?: number | string | null | undefined; 'font-weight'?: number | string | null | undefined; format?: number | string | null | undefined; fr?: number | string | null | undefined; from?: number | string | null | undefined; fx?: number | string | null | undefined; fy?: number | string | null | undefined; g1?: number | string | null | undefined; g2?: number | string | null | undefined; glyphName?: number | string | null | undefined; 'glyph-name'?: number | string | null | undefined; glyphOrientationHorizontal?: number | string | null | undefined; 'glyph-orientation-horizontal'?: number | string | null | undefined; glyphOrientationVertical?: number | string | null | undefined; 'glyph-orientation-vertical'?: number | string | null | undefined; glyphRef?: number | string | null | undefined; gradientTransform?: string | null | undefined; gradientUnits?: string | null | undefined; hanging?: number | string | null | undefined; horizAdvX?: number | string | null | undefined; 'horiz-advX'?: number | string | null | undefined; horizOriginX?: number | string | null | undefined; 'horiz-origin-x'?: number | string | null | undefined; href?: string | null | undefined; ideographic?: number | string | null | undefined; imageRendering?: number | string | null | undefined; 'image-rendering'?: number | string | null | undefined; in2?: number | string | null | undefined; in?: string | null | undefined; intercept?: number | string | null | undefined; k1?: number | string | null | undefined; k2?: number | string | null | undefined; k3?: number | string | null | undefined; k4?: number | string | null | undefined; k?: number | string | null | undefined; kernelMatrix?: number | string | null | undefined; kernelUnitLength?: number | string | null | undefined; kerning?: number | string | null | undefined; keyPoints?: number | string | null | undefined; keySplines?: number | string | null | undefined; keyTimes?: number | string | null | undefined; lengthAdjust?: number | string | null | undefined; letterSpacing?: number | string | null | undefined; 'letter-spacing'?: number | string | null | undefined; lightingColor?: number | string | null | undefined; 'lighting-color'?: number | string | null | undefined; limitingConeAngle?: number | string | null | undefined; local?: number | string | null | undefined; markerEnd?: string | null | undefined; 'marker-end'?: string | null | undefined; markerHeight?: number | string | null | undefined; 'marker-height'?: number | string | null | undefined; markerMid?: string | null | undefined; 'marker-mid'?: string | null | undefined; markerStart?: string | null | undefined; 'marker-start'?: string | null | undefined; markerUnits?: number | string | null | undefined; markerWidth?: number | string | null | undefined; mask?: string | null | undefined; maskContentUnits?: number | string | null | undefined; maskUnits?: number | string | null | undefined; mathematical?: number | string | null | undefined; mode?: number | string | null | undefined; numOctaves?: number | string | null | undefined; offset?: number | string | null | undefined; opacity?: number | string | null | undefined; operator?: number | string | null | undefined; order?: number | string | null | undefined; orient?: number | string | null | undefined; orientation?: number | string | null | undefined; origin?: number | string | null | undefined; overflow?: number | string | null | undefined; overlinePosition?: number | string | null | undefined; 'overline-position'?: number | string | null | undefined; overlineThickness?: number | string | null | undefined; 'overline-thickness'?: number | string | null | undefined; paintOrder?: number | string | null | undefined; 'paint-order'?: number | string | null | undefined; panose1?: number | string | null | undefined; 'panose-1'?: number | string | null | undefined; path?: string | null | undefined; pathLength?: number | string | null | undefined; patternContentUnits?: string | null | undefined; patternTransform?: number | string | null | undefined; patternUnits?: string | null | undefined; pointerEvents?: number | string | null | undefined; 'pointer-events'?: number | string | null | undefined; points?: string | null | undefined; pointsAtX?: number | string | null | undefined; pointsAtY?: number | string | null | undefined; pointsAtZ?: number | string | null | undefined; preserveAlpha?: Booleanish | null | undefined; preserveAspectRatio?: string | null | undefined; primitiveUnits?: number | string | null | undefined; r?: number | string | null | undefined; radius?: number | string | null | undefined; refX?: number | string | null | undefined; refY?: number | string | null | undefined; renderingIntent?: number | string | null | undefined; 'rendering-intent'?: number | string | null | undefined; repeatCount?: number | string | null | undefined; repeatDur?: number | string | null | undefined; requiredExtensions?: number | string | null | undefined; requiredFeatures?: number | string | null | undefined; restart?: number | string | null | undefined; result?: string | null | undefined; rotate?: number | string | null | undefined; rx?: number | string | null | undefined; ry?: number | string | null | undefined; scale?: number | string | null | undefined; seed?: number | string | null | undefined; shapeRendering?: number | string | null | undefined; 'shape-rendering'?: number | string | null | undefined; slope?: number | string | null | undefined; spacing?: number | string | null | undefined; specularConstant?: number | string | null | undefined; specularExponent?: number | string | null | undefined; speed?: number | string | null | undefined; spreadMethod?: string | null | undefined; startOffset?: number | string | null | undefined; stdDeviation?: number | string | null | undefined; stemh?: number | string | null | undefined; stemv?: number | string | null | undefined; stitchTiles?: number | string | null | undefined; stopColor?: string | null | undefined; 'stop-color'?: string | null | undefined; stopOpacity?: number | string | null | undefined; 'stop-opacity'?: number | string | null | undefined; strikethroughPosition?: number | string | null | undefined; 'strikethrough-position'?: number | string | null | undefined; strikethroughThickness?: number | string | null | undefined; 'strikethrough-thickness'?: number | string | null | undefined; string?: number | string | null | undefined; stroke?: string | null | undefined; strokeDasharray?: string | number | null | undefined; 'stroke-dasharray'?: string | number | null | undefined; strokeDashoffset?: string | number | null | undefined; 'stroke-dashoffset'?: string | number | null | undefined; strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit' | null | undefined; 'stroke-linecap'?: | 'butt' | 'round' | 'square' | 'inherit' | null | undefined; strokeLinejoin?: 'miter' | 'round' | 'bevel' | 'inherit' | null | undefined; 'stroke-linejoin'?: | 'miter' | 'round' | 'bevel' | 'inherit' | null | undefined; strokeMiterlimit?: number | string | null | undefined; 'stroke-miterlimit'?: number | string | null | undefined; strokeOpacity?: number | string | null | undefined; 'stroke-opacity'?: number | string | null | undefined; strokeWidth?: number | string | null | undefined; 'stroke-width'?: number | string | null | undefined; surfaceScale?: number | string | null | undefined; systemLanguage?: number | string | null | undefined; tableValues?: number | string | null | undefined; targetX?: number | string | null | undefined; targetY?: number | string | null | undefined; textAnchor?: string | null | undefined; textDecoration?: number | string | null | undefined; 'text-decoration'?: number | string | null | undefined; textLength?: number | string | null | undefined; textRendering?: number | string | null | undefined; 'text-rendering'?: number | string | null | undefined; to?: number | string | null | undefined; transform?: string | null | undefined; u1?: number | string | null | undefined; u2?: number | string | null | undefined; underlinePosition?: number | string | null | undefined; 'underline-position'?: number | string | null | undefined; underlineThickness?: number | string | null | undefined; 'underline-thickness'?: number | string | null | undefined; unicode?: number | string | null | undefined; unicodeBidi?: number | string | null | undefined; 'unicode-bidi'?: number | string | null | undefined; unicodeRange?: number | string | null | undefined; 'unicode-range'?: number | string | null | undefined; unitsPerEm?: number | string | null | undefined; 'units-per-em'?: number | string | null | undefined; vAlphabetic?: number | string | null | undefined; 'v-alphabetic'?: number | string | null | undefined; values?: string | null | undefined; vectorEffect?: number | string | null | undefined; 'vector-effect'?: number | string | null | undefined; version?: string | null | undefined; vertAdvY?: number | string | null | undefined; 'vert-adv-y'?: number | string | null | undefined; vertOriginX?: number | string | null | undefined; 'vert-origin-x'?: number | string | null | undefined; vertOriginY?: number | string | null | undefined; 'vert-origin-y'?: number | string | null | undefined; vHanging?: number | string | null | undefined; 'v-hanging'?: number | string | null | undefined; vIdeographic?: number | string | null | undefined; 'v-ideographic'?: number | string | null | undefined; viewBox?: string | null | undefined; viewTarget?: number | string | null | undefined; visibility?: number | string | null | undefined; vMathematical?: number | string | null | undefined; 'v-mathematical'?: number | string | null | undefined; widths?: number | string | null | undefined; wordSpacing?: number | string | null | undefined; 'word-spacing'?: number | string | null | undefined; writingMode?: number | string | null | undefined; 'writing-mode'?: number | string | null | undefined; x1?: number | string | null | undefined; x2?: number | string | null | undefined; x?: number | string | null | undefined; xChannelSelector?: string | null | undefined; xHeight?: number | string | null | undefined; 'x-height'?: number | string | null | undefined; xlinkActuate?: string | null | undefined; 'xlink:actuate'?: string | null | undefined; xlinkArcrole?: string | null | undefined; 'xlink:arcrole'?: string | null | undefined; xlinkHref?: string | null | undefined; 'xlink:href'?: string | null | undefined; xlinkRole?: string | null | undefined; 'xlink:role'?: string | null | undefined; xlinkShow?: string | null | undefined; 'xlink:show'?: string | null | undefined; xlinkTitle?: string | null | undefined; 'xlink:title'?: string | null | undefined; xlinkType?: string | null | undefined; 'xlink:type'?: string | null | undefined; xmlBase?: string | null | undefined; 'xml:base'?: string | null | undefined; xmlLang?: string | null | undefined; 'xml:lang'?: string | null | undefined; xmlns?: string | null | undefined; xmlnsXlink?: string | null | undefined; 'xmlns:xlink'?: string | null | undefined; xmlSpace?: string | null | undefined; 'xml:space'?: string | null | undefined; y1?: number | string | null | undefined; y2?: number | string | null | undefined; y?: number | string | null | undefined; yChannelSelector?: string | null | undefined; z?: number | string | null | undefined; zoomAndPan?: string | null | undefined; } interface WebViewHTMLAttributes extends HTMLAttributes { allowFullScreen?: boolean | null | undefined; allowpopups?: boolean | null | undefined; autoFocus?: boolean | null | undefined; autosize?: boolean | null | undefined; blinkfeatures?: string | null | undefined; disableblinkfeatures?: string | null | undefined; disableguestresize?: boolean | null | undefined; disablewebsecurity?: boolean | null | undefined; guestinstance?: string | null | undefined; httpreferrer?: string | null | undefined; nodeintegration?: boolean | null | undefined; partition?: string | null | undefined; plugins?: boolean | null | undefined; preload?: string | null | undefined; src?: string | null | undefined; useragent?: string | null | undefined; webpreferences?: string | null | undefined; } // // Inferno.DOM // ---------------------------------------------------------------------- interface InfernoHTML { a: DetailedHTMLFactory< AnchorHTMLAttributes, HTMLAnchorElement >; abbr: DetailedHTMLFactory, HTMLElement>; address: DetailedHTMLFactory, HTMLElement>; area: DetailedHTMLFactory< AreaHTMLAttributes, HTMLAreaElement >; article: DetailedHTMLFactory, HTMLElement>; aside: DetailedHTMLFactory, HTMLElement>; audio: DetailedHTMLFactory< AudioHTMLAttributes, HTMLAudioElement >; b: DetailedHTMLFactory, HTMLElement>; base: DetailedHTMLFactory< BaseHTMLAttributes, HTMLBaseElement >; bdi: DetailedHTMLFactory, HTMLElement>; bdo: DetailedHTMLFactory, HTMLElement>; big: DetailedHTMLFactory, HTMLElement>; blockquote: DetailedHTMLFactory< BlockquoteHTMLAttributes, HTMLQuoteElement >; body: DetailedHTMLFactory, HTMLBodyElement>; br: DetailedHTMLFactory, HTMLBRElement>; button: DetailedHTMLFactory< ButtonHTMLAttributes, HTMLButtonElement >; canvas: DetailedHTMLFactory< CanvasHTMLAttributes, HTMLCanvasElement >; caption: DetailedHTMLFactory, HTMLElement>; cite: DetailedHTMLFactory, HTMLElement>; code: DetailedHTMLFactory, HTMLElement>; col: DetailedHTMLFactory< ColHTMLAttributes, HTMLTableColElement >; colgroup: DetailedHTMLFactory< ColgroupHTMLAttributes, HTMLTableColElement >; data: DetailedHTMLFactory< DataHTMLAttributes, HTMLDataElement >; datalist: DetailedHTMLFactory< HTMLAttributes, HTMLDataListElement >; dd: DetailedHTMLFactory, HTMLElement>; del: DetailedHTMLFactory, HTMLModElement>; details: DetailedHTMLFactory< DetailsHTMLAttributes, HTMLDetailsElement >; dfn: DetailedHTMLFactory, HTMLElement>; dialog: DetailedHTMLFactory< DialogHTMLAttributes, HTMLDialogElement >; div: DetailedHTMLFactory, HTMLDivElement>; dl: DetailedHTMLFactory, HTMLDListElement>; dt: DetailedHTMLFactory, HTMLElement>; em: DetailedHTMLFactory, HTMLElement>; embed: DetailedHTMLFactory< EmbedHTMLAttributes, HTMLEmbedElement >; fieldset: DetailedHTMLFactory< FieldsetHTMLAttributes, HTMLFieldSetElement >; figcaption: DetailedHTMLFactory, HTMLElement>; figure: DetailedHTMLFactory, HTMLElement>; footer: DetailedHTMLFactory, HTMLElement>; form: DetailedHTMLFactory< FormHTMLAttributes, HTMLFormElement >; h1: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; h2: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; h3: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; h4: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; h5: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; h6: DetailedHTMLFactory< HTMLAttributes, HTMLHeadingElement >; head: DetailedHTMLFactory, HTMLHeadElement>; header: DetailedHTMLFactory, HTMLElement>; hgroup: DetailedHTMLFactory, HTMLElement>; hr: DetailedHTMLFactory, HTMLHRElement>; html: DetailedHTMLFactory< HtmlHTMLAttributes, HTMLHtmlElement >; i: DetailedHTMLFactory, HTMLElement>; iframe: DetailedHTMLFactory< IframeHTMLAttributes, HTMLIFrameElement >; img: DetailedHTMLFactory< ImgHTMLAttributes, HTMLImageElement >; input: DetailedHTMLFactory< InputHTMLAttributes, HTMLInputElement >; ins: DetailedHTMLFactory, HTMLModElement>; kbd: DetailedHTMLFactory, HTMLElement>; keygen: DetailedHTMLFactory, HTMLElement>; label: DetailedHTMLFactory< LabelHTMLAttributes, HTMLLabelElement >; legend: DetailedHTMLFactory< HTMLAttributes, HTMLLegendElement >; li: DetailedHTMLFactory, HTMLLIElement>; link: DetailedHTMLFactory< LinkHTMLAttributes, HTMLLinkElement >; main: DetailedHTMLFactory, HTMLElement>; map: DetailedHTMLFactory, HTMLMapElement>; mark: DetailedHTMLFactory, HTMLElement>; menu: DetailedHTMLFactory, HTMLElement>; menuitem: DetailedHTMLFactory, HTMLElement>; meta: DetailedHTMLFactory< MetaHTMLAttributes, HTMLMetaElement >; meter: DetailedHTMLFactory< MeterHTMLAttributes, HTMLMeterElement >; nav: DetailedHTMLFactory, HTMLElement>; noscript: DetailedHTMLFactory, HTMLElement>; object: DetailedHTMLFactory< ObjectHTMLAttributes, HTMLObjectElement >; ol: DetailedHTMLFactory< OlHTMLAttributes, HTMLOListElement >; optgroup: DetailedHTMLFactory< OptgroupHTMLAttributes, HTMLOptGroupElement >; option: DetailedHTMLFactory< OptionHTMLAttributes, HTMLOptionElement >; output: DetailedHTMLFactory< OutputHTMLAttributes, HTMLOutputElement >; p: DetailedHTMLFactory< HTMLAttributes, HTMLParagraphElement >; param: DetailedHTMLFactory< ParamHTMLAttributes, HTMLParamElement >; picture: DetailedHTMLFactory, HTMLElement>; pre: DetailedHTMLFactory, HTMLPreElement>; progress: DetailedHTMLFactory< ProgressHTMLAttributes, HTMLProgressElement >; q: DetailedHTMLFactory< QuoteHTMLAttributes, HTMLQuoteElement >; rp: DetailedHTMLFactory, HTMLElement>; rt: DetailedHTMLFactory, HTMLElement>; ruby: DetailedHTMLFactory, HTMLElement>; s: DetailedHTMLFactory, HTMLElement>; samp: DetailedHTMLFactory, HTMLElement>; slot: DetailedHTMLFactory< SlotHTMLAttributes, HTMLSlotElement >; script: DetailedHTMLFactory< ScriptHTMLAttributes, HTMLScriptElement >; section: DetailedHTMLFactory, HTMLElement>; select: DetailedHTMLFactory< SelectHTMLAttributes, HTMLSelectElement >; small: DetailedHTMLFactory, HTMLElement>; source: DetailedHTMLFactory< SourceHTMLAttributes, HTMLSourceElement >; span: DetailedHTMLFactory, HTMLSpanElement>; strong: DetailedHTMLFactory, HTMLElement>; style: DetailedHTMLFactory< StyleHTMLAttributes, HTMLStyleElement >; sub: DetailedHTMLFactory, HTMLElement>; summary: DetailedHTMLFactory, HTMLElement>; sup: DetailedHTMLFactory, HTMLElement>; table: DetailedHTMLFactory< TableHTMLAttributes, HTMLTableElement >; template: DetailedHTMLFactory< HTMLAttributes, HTMLTemplateElement >; tbody: DetailedHTMLFactory< HTMLAttributes, HTMLTableSectionElement >; td: DetailedHTMLFactory< TdHTMLAttributes, HTMLTableDataCellElement >; textarea: DetailedHTMLFactory< TextareaHTMLAttributes, HTMLTextAreaElement >; tfoot: DetailedHTMLFactory< HTMLAttributes, HTMLTableSectionElement >; th: DetailedHTMLFactory< ThHTMLAttributes, HTMLTableHeaderCellElement >; thead: DetailedHTMLFactory< HTMLAttributes, HTMLTableSectionElement >; time: DetailedHTMLFactory< TimeHTMLAttributes, HTMLTimeElement >; title: DetailedHTMLFactory< HTMLAttributes, HTMLTitleElement >; tr: DetailedHTMLFactory< HTMLAttributes, HTMLTableRowElement >; track: DetailedHTMLFactory< TrackHTMLAttributes, HTMLTrackElement >; u: DetailedHTMLFactory, HTMLElement>; ul: DetailedHTMLFactory, HTMLUListElement>; var: DetailedHTMLFactory, HTMLElement>; video: DetailedHTMLFactory< VideoHTMLAttributes, HTMLVideoElement >; wbr: DetailedHTMLFactory, HTMLElement>; // webview: DetailedHTMLFactory, HTMLWebViewElement>; } interface InfernoSVG { animate: SVGFactory; circle: SVGFactory; clipPath: SVGFactory; defs: SVGFactory; desc: SVGFactory; ellipse: SVGFactory; feBlend: SVGFactory; feColorMatrix: SVGFactory; feComponentTransfer: SVGFactory; feComposite: SVGFactory; feConvolveMatrix: SVGFactory; feDiffuseLighting: SVGFactory; feDisplacementMap: SVGFactory; feDistantLight: SVGFactory; feDropShadow: SVGFactory; feFlood: SVGFactory; feFuncA: SVGFactory; feFuncB: SVGFactory; feFuncG: SVGFactory; feFuncR: SVGFactory; feGaussianBlur: SVGFactory; feImage: SVGFactory; feMerge: SVGFactory; feMergeNode: SVGFactory; feMorphology: SVGFactory; feOffset: SVGFactory; fePointLight: SVGFactory; feSpecularLighting: SVGFactory; feSpotLight: SVGFactory; feTile: SVGFactory; feTurbulence: SVGFactory; filter: SVGFactory; foreignObject: SVGFactory; g: SVGFactory; image: SVGFactory; line: SVGFactory; linearGradient: SVGFactory; marker: SVGFactory; mask: SVGFactory; metadata: SVGFactory; path: SVGFactory; pattern: SVGFactory; polygon: SVGFactory; polyline: SVGFactory; radialGradient: SVGFactory; rect: SVGFactory; stop: SVGFactory; svg: SVGFactory; switch: SVGFactory; symbol: SVGFactory; text: SVGFactory; textPath: SVGFactory; tspan: SVGFactory; use: SVGFactory; view: SVGFactory; } // // Browser Interfaces // https://github.com/nikeee/2048-typescript/blob/master/2048/js/touch.d.ts // ---------------------------------------------------------------------- interface AbstractView { styleMedia: StyleMedia; document: Document; } interface Touch { identifier: number; target: EventTarget; screenX: number; screenY: number; clientX: number; clientY: number; pageX: number; pageY: number; } interface TouchList { [index: number]: Touch; length: number; item(index: number): Touch; identifiedTouch(identifier: number): Touch; } } type Defaultize = P extends any ? string extends keyof P ? P : Pick> & Partial>> & Partial>> : never; type InfernoManagedAttributes = C extends { defaultProps: infer D } ? Defaultize : P; declare global { namespace JSX { interface ElementClass extends IComponent { render(nextProps, nextState, nextContext): InfernoNode; } interface ElementAttributesProperty { props: {}; } interface ElementChildrenAttribute { children: {}; } type LibraryManagedAttributes = InfernoManagedAttributes; interface IntrinsicAttributes extends Inferno.Attributes {} interface IntrinsicAttributes extends Inferno.Attributes, Refs {} interface IntrinsicClassAttributes extends Inferno.ClassAttributes {} interface IntrinsicElements { // HTML a: Inferno.DetailedHTMLProps< Inferno.AnchorHTMLAttributes, HTMLAnchorElement >; abbr: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; address: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; area: Inferno.DetailedHTMLProps< Inferno.AreaHTMLAttributes, HTMLAreaElement >; article: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; aside: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; audio: Inferno.DetailedHTMLProps< Inferno.AudioHTMLAttributes, HTMLAudioElement >; b: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; base: Inferno.DetailedHTMLProps< Inferno.BaseHTMLAttributes, HTMLBaseElement >; bdi: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; bdo: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; big: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; blockquote: Inferno.DetailedHTMLProps< Inferno.BlockquoteHTMLAttributes, HTMLQuoteElement >; body: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLBodyElement >; br: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLBRElement >; button: Inferno.DetailedHTMLProps< Inferno.ButtonHTMLAttributes, HTMLButtonElement >; canvas: Inferno.DetailedHTMLProps< Inferno.CanvasHTMLAttributes, HTMLCanvasElement >; caption: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; cite: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; code: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; col: Inferno.DetailedHTMLProps< Inferno.ColHTMLAttributes, HTMLTableColElement >; colgroup: Inferno.DetailedHTMLProps< Inferno.ColgroupHTMLAttributes, HTMLTableColElement >; data: Inferno.DetailedHTMLProps< Inferno.DataHTMLAttributes, HTMLDataElement >; datalist: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLDataListElement >; dd: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; del: Inferno.DetailedHTMLProps< Inferno.DelHTMLAttributes, HTMLModElement >; details: Inferno.DetailedHTMLProps< Inferno.DetailsHTMLAttributes, HTMLDetailsElement >; dfn: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; dialog: Inferno.DetailedHTMLProps< Inferno.DialogHTMLAttributes, HTMLDialogElement >; div: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLDivElement >; dl: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLDListElement >; dt: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; em: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; embed: Inferno.DetailedHTMLProps< Inferno.EmbedHTMLAttributes, HTMLEmbedElement >; fieldset: Inferno.DetailedHTMLProps< Inferno.FieldsetHTMLAttributes, HTMLFieldSetElement >; figcaption: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; figure: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; footer: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; form: Inferno.DetailedHTMLProps< Inferno.FormHTMLAttributes, HTMLFormElement >; h1: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; h2: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; h3: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; h4: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; h5: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; h6: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadingElement >; head: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHeadElement >; header: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; hgroup: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; hr: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLHRElement >; html: Inferno.DetailedHTMLProps< Inferno.HtmlHTMLAttributes, HTMLHtmlElement >; i: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; iframe: Inferno.DetailedHTMLProps< Inferno.IframeHTMLAttributes, HTMLIFrameElement >; img: Inferno.DetailedHTMLProps< Inferno.ImgHTMLAttributes, HTMLImageElement >; input: Inferno.DetailedHTMLProps< Inferno.InputHTMLAttributes, HTMLInputElement >; ins: Inferno.DetailedHTMLProps< Inferno.InsHTMLAttributes, HTMLModElement >; kbd: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; keygen: Inferno.DetailedHTMLProps< Inferno.KeygenHTMLAttributes, HTMLElement >; label: Inferno.DetailedHTMLProps< Inferno.LabelHTMLAttributes, HTMLLabelElement >; legend: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLLegendElement >; li: Inferno.DetailedHTMLProps< Inferno.LiHTMLAttributes, HTMLLIElement >; link: Inferno.DetailedHTMLProps< Inferno.LinkHTMLAttributes, HTMLLinkElement >; main: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; map: Inferno.DetailedHTMLProps< Inferno.MapHTMLAttributes, HTMLMapElement >; mark: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; media: Inferno.DetailedHTMLProps< Inferno.MediaHTMLAttributes, HTMLMediaElement >; menu: Inferno.DetailedHTMLProps< Inferno.MenuHTMLAttributes, HTMLElement >; menuitem: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; meta: Inferno.DetailedHTMLProps< Inferno.MetaHTMLAttributes, HTMLMetaElement >; meter: Inferno.DetailedHTMLProps< Inferno.MeterHTMLAttributes, HTMLMeterElement >; nav: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; noindex: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; noscript: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; object: Inferno.DetailedHTMLProps< Inferno.ObjectHTMLAttributes, HTMLObjectElement >; ol: Inferno.DetailedHTMLProps< Inferno.OlHTMLAttributes, HTMLOListElement >; optgroup: Inferno.DetailedHTMLProps< Inferno.OptgroupHTMLAttributes, HTMLOptGroupElement >; option: Inferno.DetailedHTMLProps< Inferno.OptionHTMLAttributes, HTMLOptionElement >; output: Inferno.DetailedHTMLProps< Inferno.OutputHTMLAttributes, HTMLOutputElement >; p: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLParagraphElement >; param: Inferno.DetailedHTMLProps< Inferno.ParamHTMLAttributes, HTMLParamElement >; picture: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; pre: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLPreElement >; progress: Inferno.DetailedHTMLProps< Inferno.ProgressHTMLAttributes, HTMLProgressElement >; q: Inferno.DetailedHTMLProps< Inferno.QuoteHTMLAttributes, HTMLQuoteElement >; rp: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; rt: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; ruby: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; s: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; samp: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; slot: Inferno.DetailedHTMLProps< Inferno.SlotHTMLAttributes, HTMLSlotElement >; script: Inferno.DetailedHTMLProps< Inferno.ScriptHTMLAttributes, HTMLScriptElement >; section: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; select: Inferno.DetailedHTMLProps< Inferno.SelectHTMLAttributes, HTMLSelectElement >; small: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; source: Inferno.DetailedHTMLProps< Inferno.SourceHTMLAttributes, HTMLSourceElement >; span: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLSpanElement >; strong: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; style: Inferno.DetailedHTMLProps< Inferno.StyleHTMLAttributes, HTMLStyleElement >; sub: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; summary: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; sup: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; table: Inferno.DetailedHTMLProps< Inferno.TableHTMLAttributes, HTMLTableElement >; template: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTemplateElement >; tbody: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTableSectionElement >; td: Inferno.DetailedHTMLProps< Inferno.TdHTMLAttributes, HTMLTableDataCellElement >; textarea: Inferno.DetailedHTMLProps< Inferno.TextareaHTMLAttributes, HTMLTextAreaElement >; tfoot: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTableSectionElement >; th: Inferno.DetailedHTMLProps< Inferno.ThHTMLAttributes, HTMLTableHeaderCellElement >; thead: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTableSectionElement >; time: Inferno.DetailedHTMLProps< Inferno.TimeHTMLAttributes, HTMLTimeElement >; title: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTitleElement >; tr: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLTableRowElement >; track: Inferno.DetailedHTMLProps< Inferno.TrackHTMLAttributes, HTMLTrackElement >; u: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; ul: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLUListElement >; var: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; video: Inferno.DetailedHTMLProps< Inferno.VideoHTMLAttributes, HTMLVideoElement >; wbr: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, HTMLElement >; // webview: Inferno.DetailedHTMLProps, HTMLWebViewElement>; // SVG svg: Inferno.SVGProps; animate: Inferno.SVGProps; animateMotion: Inferno.SVGProps; animateTransform: Inferno.SVGProps; circle: Inferno.SVGProps; clipPath: Inferno.SVGProps; defs: Inferno.SVGProps; desc: Inferno.SVGProps; ellipse: Inferno.SVGProps; feBlend: Inferno.SVGProps; feColorMatrix: Inferno.SVGProps; feComponentTransfer: Inferno.SVGProps; feComposite: Inferno.SVGProps; feConvolveMatrix: Inferno.SVGProps; feDiffuseLighting: Inferno.SVGProps; feDisplacementMap: Inferno.SVGProps; feDistantLight: Inferno.SVGProps; feDropShadow: Inferno.SVGProps; feFlood: Inferno.SVGProps; feFuncA: Inferno.SVGProps; feFuncB: Inferno.SVGProps; feFuncG: Inferno.SVGProps; feFuncR: Inferno.SVGProps; feGaussianBlur: Inferno.SVGProps; feImage: Inferno.SVGProps; feMerge: Inferno.SVGProps; feMergeNode: Inferno.SVGProps; feMorphology: Inferno.SVGProps; feOffset: Inferno.SVGProps; fePointLight: Inferno.SVGProps; feSpecularLighting: Inferno.SVGProps; feSpotLight: Inferno.SVGProps; feTile: Inferno.SVGProps; feTurbulence: Inferno.SVGProps; filter: Inferno.SVGProps; foreignObject: Inferno.SVGProps; g: Inferno.SVGProps; image: Inferno.SVGProps; line: Inferno.SVGProps; linearGradient: Inferno.SVGProps; marker: Inferno.SVGProps; mask: Inferno.SVGProps; metadata: Inferno.SVGProps; mpath: Inferno.SVGProps; path: Inferno.SVGProps; pattern: Inferno.SVGProps; polygon: Inferno.SVGProps; polyline: Inferno.SVGProps; radialGradient: Inferno.SVGProps; rect: Inferno.SVGProps; stop: Inferno.SVGProps; switch: Inferno.SVGProps; symbol: Inferno.SVGProps; text: Inferno.SVGProps; textPath: Inferno.SVGProps; tspan: Inferno.SVGProps; use: Inferno.SVGProps; view: Inferno.SVGProps; // MathML maction: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; math: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; menclose: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; merror: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mfenced: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mfrac: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mi: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mmultiscripts: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mn: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mo: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mover: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mpadded: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mphantom: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mroot: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mrow: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; ms: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mspace: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; msqrt: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mstyle: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; msub: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; msubsup: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; msup: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mtable: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mtd: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mtext: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; mtr: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; munder: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; munderover: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; semantics: Inferno.DetailedHTMLProps< Inferno.HTMLAttributes, MathMLElement >; } } var SKIP_INFERNO_WARNINGS: string; } ================================================ FILE: packages/inferno/src/core/validate.ts ================================================ import type { VNode } from './types'; import { isArray, isInvalid, isNullOrUndef, isNumber, isStringOrNumber, throwError } from 'inferno-shared'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { getComponentName } from '../DOM/utils/common'; function getTagName(input): string { let tagName; if (isArray(input)) { const arrayText = input.length > 3 ? input.slice(0, 3).toString() + ',...' : input.toString(); tagName = 'Array(' + arrayText + ')'; } else if (isStringOrNumber(input)) { tagName = 'Text(' + input + ')'; } else if (isInvalid(input)) { tagName = 'InvalidVNode(' + input + ')'; } else { const flags = input?.flags; if (!isNumber(flags)) { try { tagName = `Object(${JSON.stringify(input)})`; } catch { tagName = `Object(${String(input)})`; } return '>> ' + tagName + '\n'; } if (flags & VNodeFlags.Element) { tagName = `<${input.type}${ input.className ? ' class="' + input.className + '"' : '' }>`; } else if (flags & VNodeFlags.Text) { tagName = `Text(${input.children})`; } else if (flags & VNodeFlags.Portal) { tagName = `Portal*`; } else { tagName = `<${getComponentName(input.type)} />`; } } return '>> ' + tagName + '\n'; } function DEV_VALIDATE_KEYS(vNodeTree, childKeys): string | null { if ((childKeys & ChildFlags.HasNonKeyedChildren) !== 0) { return null; } const foundKeys: Record = {}; const forceKeyed = (childKeys & ChildFlags.HasKeyedChildren) !== 0; let foundKeyCount = 0; for (let i = 0, len = vNodeTree.length; i < len; ++i) { const childNode = vNodeTree[i]; if (isArray(childNode)) { return ( 'Encountered ARRAY in mount, array must be flattened, or normalize used. Location: \n' + getTagName(childNode) ); } if (isInvalid(childNode)) { if (forceKeyed) { return ( 'Encountered invalid node when preparing to keyed algorithm. Location: \n' + getTagName(childNode) ); } else if (foundKeyCount !== 0) { return ( 'Encountered invalid node with mixed keys. Location: \n' + getTagName(childNode) ); } continue; } if (typeof (childNode as VNode) === 'object') { if (childNode.isValidated) { continue; } childNode.isValidated = true; } // Key can be undefined, null too. But typescript complains for no real reason const key: string | number = childNode.key as string | number; if (!isNullOrUndef(key) && !isStringOrNumber(key)) { return ( 'Encountered child vNode where key property is not string or number. Location: \n' + getTagName(childNode) ); } const children = childNode.children; const childFlags = childNode.childFlags; if (!isInvalid(children)) { let val; if (childFlags & ChildFlags.MultipleChildren) { val = DEV_VALIDATE_KEYS( children, (childFlags & ChildFlags.HasKeyedChildren) !== 0, ); } else if (childFlags === ChildFlags.HasVNodeChildren) { val = DEV_VALIDATE_KEYS([children], false); } if (val) { val += getTagName(childNode); return val; } } if (forceKeyed && isNullOrUndef(key)) { return ( 'Encountered child without key during keyed algorithm. If this error points to Array make sure children is flat list. Location: \n' + getTagName(childNode) ); } else if (isNullOrUndef(key)) { if (foundKeyCount !== 0) { return ( 'Encountered children with key missing. Location: \n' + getTagName(childNode) ); } continue; } if (foundKeys[key]) { return ( 'Encountered two children with same key: {' + key + '}. Location: \n' + getTagName(childNode) ); } foundKeys[key] = true; foundKeyCount++; } return null; } export function validateVNodeElementChildren(vNode): void { if (process.env.NODE_ENV !== 'production') { if (vNode.childFlags === ChildFlags.HasInvalidChildren) { return; } if (vNode.flags & VNodeFlags.InputElement) { throwError("input elements can't have children."); } if (vNode.flags & VNodeFlags.TextareaElement) { throwError("textarea elements can't have children."); } if (vNode.flags & VNodeFlags.Element) { const tag = vNode.type.toLowerCase(); switch (tag) { case 'media': case 'area': case 'base': case 'br': case 'col': case 'command': case 'embed': case 'hr': case 'img': case 'input': case 'keygen': case 'link': case 'meta': case 'param': case 'source': case 'track': case 'wbr': throwError(`${tag} elements can't have children.`); break; default: break; } } } } export function validateKeys(vNode): void { if (process.env.NODE_ENV !== 'production') { // Checks if there is any key missing or duplicate keys if ( !vNode.isValidated && vNode.children && vNode.flags & VNodeFlags.Element ) { const error = DEV_VALIDATE_KEYS( Array.isArray(vNode.children) ? vNode.children : [vNode.children], vNode.childFlags ); if (error) { throwError(error + getTagName(vNode)); } } vNode.isValidated = true; } } function getChildFlagsName(childFlags: ChildFlags): string { switch (childFlags) { case ChildFlags.HasInvalidChildren: return 'ChildFlags.HasInvalidChildren'; case ChildFlags.HasVNodeChildren: return 'ChildFlags.HasVNodeChildren'; case ChildFlags.HasNonKeyedChildren: return 'ChildFlags.HasNonKeyedChildren'; case ChildFlags.HasKeyedChildren: return 'ChildFlags.HasKeyedChildren'; case ChildFlags.HasTextChildren: return 'ChildFlags.HasTextChildren'; case ChildFlags.UnknownChildren: return 'ChildFlags.UnknownChildren'; default: return `ChildFlags.Unknown(${childFlags})`; } } export function validateChildFlags(vNode: VNode): void { if (process.env.NODE_ENV === 'production') { return; } const childFlags = vNode.childFlags; const children = vNode.children as any; const parentTag = getTagName(vNode); switch (childFlags) { case ChildFlags.UnknownChildren: case ChildFlags.HasInvalidChildren: return; case ChildFlags.HasTextChildren: if (isStringOrNumber(children)) { return; } if ( children && isNumber(children.flags) && children.flags & VNodeFlags.Text ) { throwError( `${getChildFlagsName(childFlags)} expects children to be a bare string, not a Text VNode. Location: \n${getTagName(children)}${parentTag}`, ); } throwError( `${getChildFlagsName(childFlags)} expects children to be a string. Location: \n${getTagName(children)}${parentTag}`, ); return; case ChildFlags.HasVNodeChildren: if (isInvalid(children) || isArray(children) || isStringOrNumber(children)) { throwError( `${getChildFlagsName(childFlags)} expects children to be a VNode. Location: \n${getTagName(children)}${parentTag}`, ); } throwIfObjectIsNotVNode(children); return; case ChildFlags.HasNonKeyedChildren: case ChildFlags.HasKeyedChildren: if (!isArray(children)) { throwError( `${getChildFlagsName(childFlags)} expects children to be an array of VNodes. Location: \n${getTagName(children)}${parentTag}`, ); } for (let i = 0; i < children.length; i++) { if (!(i in children)) { throwError( `${getChildFlagsName(childFlags)} expects children to be a flat array without holes; found a hole at index ${i}. Location: \n${parentTag}`, ); } const child = children[i]; if (isArray(child)) { throwError( `${getChildFlagsName(childFlags)} expects children to be a flat array; found a nested array at index ${i}. Location: \n${getTagName(child)}${parentTag}`, ); } if (isInvalid(child)) { throwError( `${getChildFlagsName(childFlags)} expects children to be VNodes; found invalid child at index ${i}. Location: \n${getTagName(child)}${parentTag}`, ); } if (isStringOrNumber(child)) { throwError( `${getChildFlagsName(childFlags)} expects children to be VNodes; found text at index ${i}. Location: \n${getTagName(child)}${parentTag}`, ); } throwIfObjectIsNotVNode(child); if (childFlags === ChildFlags.HasKeyedChildren) { if (isNullOrUndef(child.key)) { throwError( `${getChildFlagsName(childFlags)} expects all children to have keys; missing key at index ${i}. Location: \n${getTagName(child)}${parentTag}`, ); } } } return; default: return; } } export function throwIfObjectIsNotVNode(input): void { if (!isNumber(input.flags)) { throwError( `normalization received an object that's not a valid VNode, you should stringify it first or fix createVNode flags. Object: "${JSON.stringify( input, )}".`, ); } } ================================================ FILE: packages/inferno/src/index.ts ================================================ import { warning } from 'inferno-shared'; import { createComponentVNode, createFragment, createPortal, createTextVNode, createVNode, directClone, getFlagsForElementVnode, normalizeProps, normalizeRoot, } from './core/implementation'; import { linkEvent } from './DOM/events/linkEvent'; import { renderInternal, createRenderer, render } from './DOM/rendering'; import { AnimationQueues, EMPTY_OBJ, findDOMFromVNode, Fragment, options, } from './DOM/utils/common'; import { Component, type ComponentType, rerender } from './core/component'; import { mountProps } from './DOM/props'; import { createClassComponentInstance, renderFunctionalComponent, } from './DOM/utils/componentUtil'; import { mount, mountClassComponentCallbacks, mountElement, mountFunctionalComponentCallbacks, } from './DOM/mounting'; import { createRef, forwardRef, mountRef } from './core/refs'; export * from './core/types'; if (process.env.NODE_ENV !== 'production') { const skipWarnings = typeof SKIP_INFERNO_WARNINGS !== 'undefined' || (typeof process === 'object' && (process.env?.SKIP_INFERNO_WARNINGS !== undefined || process.env?.JEST_WORKER_ID !== undefined)); if (!skipWarnings) { const testFunc = function testFn() {}; if ( !((testFunc as Function).name || testFunc.toString()).includes('testFn') ) { warning( "It looks like you're using a minified copy of the development build " + 'of Inferno. When deploying Inferno apps to production, make sure to use ' + 'the production build which skips development warnings and is faster. ' + 'See https://infernojs.org for more details.', ); } } } const version = process.env.INFERNO_VERSION; export { AnimationQueues, Component, type ComponentType, Fragment, EMPTY_OBJ, createComponentVNode, createFragment, createPortal, createRef, createRenderer, createTextVNode, createVNode, forwardRef, directClone, findDOMFromVNode, getFlagsForElementVnode, linkEvent, normalizeProps, options, render, rerender, version, // Internal methods, used by hydration createClassComponentInstance as _CI, normalizeRoot as _HI, // used by inferno-mobx mount as _M, mountClassComponentCallbacks as _MCCC, mountElement as _ME, mountFunctionalComponentCallbacks as _MFCC, mountRef as _MR, mountProps as _MP, renderInternal, renderFunctionalComponent as _RFC, }; ================================================ FILE: packages/inferno-animation/__tests__/animatedAllComponent.spec.tsx ================================================ import { type InfernoNode, render } from 'inferno'; import { renderToString } from 'inferno-server'; import { AnimatedAllComponent, componentDidAppear, componentWillDisappear, componentWillMove, } from 'inferno-animation'; describe('inferno-animation AnimatedAllComponent', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?: any): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render class component extending AnimatedAllComponent into DOM', () => { class MyComponent extends AnimatedAllComponent { public render({ children }): InfernoNode { return

{children}
; } } render(1, container); expect(container.textContent).toBe('1'); }); it('should remove class component extending AnimatedAllComponent from DOM', (done) => { class My extends AnimatedAllComponent { public render({ children }): InfernoNode { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedAllComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move class component extending AnimatedAllComponent from DOM', (done) => { class My extends AnimatedAllComponent { public render({ children }): InfernoNode { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedAllComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedAllComponent to a string', () => { class MyComponent extends AnimatedAllComponent { public render({ children }): InfernoNode { return
{children}
; } } const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); describe('inferno-animation animated functional component', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render functional component extending AnimatedAllComponent into DOM', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; render( 1 , container, ); expect(container.textContent).toBe('1'); }); it('should remove functional component extending AnimatedAllComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, onComponentWillMove: componentWillMove, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedAllComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move functional component extending AnimatedAllComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, onComponentWillMove: componentWillMove, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedAllComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedAllComponent to a string', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, onComponentWillMove: componentWillMove, }; const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); ================================================ FILE: packages/inferno-animation/__tests__/animatedComponent.spec.tsx ================================================ import { type InfernoNode, render } from 'inferno'; import { renderToString } from 'inferno-server'; import { AnimatedComponent, componentDidAppear, componentWillDisappear, } from 'inferno-animation'; describe('inferno-animation AnimatedComponent', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render class component extending AnimatedComponent into DOM', () => { class MyComponent extends AnimatedComponent { public render({ children }): InfernoNode { return
{children}
; } } render(1, container); expect(container.textContent).toBe('1'); }); it('should remove class component extending AnimatedComponent from DOM', (done) => { class My extends AnimatedComponent { public render({ children }): void { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move class component extending AnimatedComponent from DOM', (done) => { class My extends AnimatedComponent { public render({ children }): void { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedComponent to a string', () => { class MyComponent extends AnimatedComponent { public render({ children }): InfernoNode { return
{children}
; } } const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); describe('inferno-animation animated functional component', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render class component extending AnimatedComponent into DOM', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; render( 1 , container, ); expect(container.textContent).toBe('1'); }); it('should remove class component extending AnimatedComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move class component extending AnimatedComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedComponent to a string', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentDidAppear: componentDidAppear, onComponentWillDisappear: componentWillDisappear, }; const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); ================================================ FILE: packages/inferno-animation/__tests__/animatedComponentTypings.tsx ================================================ import { type InfernoNode, render } from 'inferno'; import { AnimatedComponent } from 'inferno-animation'; describe('inferno-animation AnimatedComponent', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); }); it('Should be possible to define typed props for AnimatedComponent', () => { interface MyProps { number: number; } class MyComponent extends AnimatedComponent { public render(props): InfernoNode { return
{props.number}
; } } render(, container); expect(container.innerHTML).toBe('
1
'); }); }); ================================================ FILE: packages/inferno-animation/__tests__/animatedMoveComponent.spec.tsx ================================================ import { render, type InfernoNode } from 'inferno'; import { renderToString } from 'inferno-server'; import { AnimatedMoveComponent, componentWillMove } from 'inferno-animation'; describe('inferno-animation AnimatedMoveComponent', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render class component extending AnimatedMoveComponent into DOM', () => { class MyComponent extends AnimatedMoveComponent { public render({ children }): InfernoNode { return
{children}
; } } render(1, container); expect(container.textContent).toBe('1'); }); it('should remove class component extending AnimatedMoveComponent from DOM', (done) => { class My extends AnimatedMoveComponent { public render({ children }): InfernoNode { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedMoveComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move class component extending AnimatedMoveComponent from DOM', (done) => { class My extends AnimatedMoveComponent { public render({ children }): InfernoNode { return
{children}
; } } render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedMoveComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedMoveComponent to a string', () => { class MyComponent extends AnimatedMoveComponent { public render({ children }): InfernoNode { return
{children}
; } } const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); describe('inferno-animation animated functional component', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); function waitForAnimationAndContinue(condition, callback, arg1?): void { if (container.textContent !== condition) { setTimeout( waitForAnimationAndContinue.bind(null, condition, callback, arg1), 10, ); return; } callback(arg1); } function afterEachClear(done): void { container.innerHTML = ''; document.body.removeChild(container); done(); } afterEach(function (done) { render(null, container); waitForAnimationAndContinue('', afterEachClear, done); }); it('should render class component extending AnimatedMoveComponent into DOM', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; render( 1, container, ); expect(container.textContent).toBe('1'); }); it('should remove class component extending AnimatedMoveComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentWillMove: componentWillMove, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedMoveComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ waitForAnimationAndContinue('13', function () { render(
1 4
, container, ); waitForAnimationAndContinue('14', function () { done(); }); }); }); it('should move class component extending AnimatedMoveComponent from DOM', (done) => { const My = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentWillMove: componentWillMove, }; render(
1 2 3
, container, ); expect(container.textContent).toBe('123'); render(
1 3 2
, container, ); /** * The reason for recursively calling checkRenderComplete_XXX instead of * using a simpler setTimeout is due to a couple of async calls during the animations * hooks of AnimatedMoveComponent. These can cause a setTimeout in the test to * trigger prior to the animation callbacks and thus remove operations haven't yet * been completed. As long as the render operation eventually completes correctly, * the test should be considered successful. */ // Disappear animations complete async waitForAnimationAndContinue('132', function () { render(
4 1
, container, ); waitForAnimationAndContinue('41', function () { render(null, container); done(); }); }); }); it('should render class component extending AnimatedMoveComponent to a string', () => { const MyComponent = ({ children }): InfernoNode => { return
{children}
; }; const anim = { onComponentWillMove: componentWillMove, }; const outputStr = renderToString(1); expect(outputStr).toBe('
1
'); }); }); ================================================ FILE: packages/inferno-animation/__tests__/index.spec.tsx ================================================ import { AnimatedComponent, utils } from 'inferno-animation'; describe('inferno-animation public API', () => { it('should expose AnimatedComponent', () => { expect(AnimatedComponent).not.toBeUndefined(); }); it('should expose utils', () => { const { addClassName, removeClassName, registerTransitionListener, forceReflow, clearDimensions, getDimensions, setDimensions, setDisplay, } = utils; expect(addClassName).not.toBeUndefined(); expect(removeClassName).not.toBeUndefined(); expect(registerTransitionListener).not.toBeUndefined(); expect(forceReflow).not.toBeUndefined(); expect(clearDimensions).not.toBeUndefined(); expect(getDimensions).not.toBeUndefined(); expect(setDimensions).not.toBeUndefined(); expect(setDisplay).not.toBeUndefined(); }); }); ================================================ FILE: packages/inferno-animation/__tests__/utils.spec.tsx ================================================ import { addClassName, clearDimensions, forceReflow, getDimensions, registerTransitionListener, removeClassName, setDimensions, setDisplay, } from '../src/utils'; describe('inferno-animation utils', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { container.innerHTML = ''; document.body.removeChild(container); }); function renderTemplate(dom): void { dom.innerHTML = '
content
'; } it('addClassName', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; addClassName(el, 'test'); addClassName(el, ''); expect(el.className).toEqual('target test'); }); it('removeClassName', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; removeClassName(el, 'target'); removeClassName(el, ''); expect(el.className).toEqual(''); }); it('forceReflow', () => { renderTemplate(container); const res = forceReflow(); expect(res).not.toBeUndefined(); }); it('setDisplay', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; setDisplay(el, 'block'); setDisplay(el, 'block'); expect(el.style.getPropertyValue('display')).toEqual('block'); // Removes style prop setDisplay(el, undefined); // NOTE: For some reason we get a lingering 'style' attribute in on the DOM // element. This is the recomended though // https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute expect(el.outerHTML).toEqual('
content
'); // Just clear display prop setDisplay(el, 'block'); setDimensions(el, 10, 10); setDisplay(el, undefined); expect(el.style.getPropertyValue('display')).toEqual(''); }); it('getDimensions', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; const res = getDimensions(el); expect(res).not.toEqual(undefined); el.style.display = 'none'; const res2 = getDimensions(el); expect(res2).not.toEqual(undefined); }); it('setDimensions', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; setDimensions(el, 10, 10); const width = el.style.getPropertyValue('width'); const height = el.style.getPropertyValue('height'); expect(width).toEqual('10px'); expect(height).toEqual('10px'); }); it('clearDimensions', () => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; setDimensions(el, 10, 10); clearDimensions(el); const width = el.style.getPropertyValue('width'); const height = el.style.getPropertyValue('height'); expect(width).toEqual(''); expect(height).toEqual(''); }); it('registerTransitionListener', (done) => { renderTemplate(container); const el = document.querySelector('.target') as HTMLElement; registerTransitionListener([el], () => { // We should always get a callback done(); }); }); it('registerTransitionListener for IMG', (done) => { container.innerHTML = '
'; const el = document.querySelector('.target') as HTMLElement; registerTransitionListener([el], () => { // We should always get a callback done(); }); el.dispatchEvent(new Event('load')); }); }); ================================================ FILE: packages/inferno-animation/index.cjs ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./dist/index.min.cjs'); } else { module.exports = require('./dist/index.cjs'); } ================================================ FILE: packages/inferno-animation/index.css ================================================ :root { --infernoAnimationEnter: all 0.3s ease-out; --infernoAnimationLeave: all 0.2s ease-out; } .inferno-animation-leave { /* Leave animation start state */ opacity: 1; } .inferno-animation-leave-active { /* Leave animation transitions */ overflow: hidden; transition: var(--infernoAnimationLeave); } .inferno-animation-leave-end { /* Leave animation end state */ opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; margin-top: 0; margin-bottom: 0; border-top-width: 0; border-bottom-width: 0; } .inferno-animation-enter { /* Enter animation start state */ opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; margin-top: 0; margin-bottom: 0; border-top-width: 0; border-bottom-width: 0; } .inferno-animation-enter-active { /* Enter animation transitions */ transition: var(--infernoAnimationEnter); } .inferno-animation-enter-end { /* Enter animation end state */ opacity: 1; } ================================================ FILE: packages/inferno-animation/package.json ================================================ { "name": "inferno-animation", "version": "9.0.11", "license": "MIT", "type": "module", "description": "Animation helpers inferno. This package can be used to reuse server side rendered html", "author": { "name": "Sebastian Ware", "email": "sebastian@urbantalk.se", "url": "https://github.com/jhsware" }, "repository": { "type": "git", "url": "https://github.com/infernojs/inferno.git", "directory": "packages/inferno-animation" }, "bugs": { "url": "https://github.com/infernojs/inferno/issues" }, "homepage": "https://github.com/infernojs/inferno#readme", "files": [ "index.cjs", "index.css", "dist/", "README.md", "package.json" ], "exports": { ".": { "import": "./dist/index.mjs", "require": "./index.cjs", "types": "./dist/index.d.ts" }, "./dist/index.dev.mjs": "./dist/index.dev.mjs", "./dist/index.mjs": "./dist/index.mjs" }, "module": "dist/index.mjs", "dev:module": "dist/index.dev.mjs", "typings": "dist/index.d.ts", "keywords": [ "babel", "react", "inferno", "framework", "interfaces", "user interfaces", "vdom", "animation", "css" ], "dependencies": { "inferno": "9.0.11" }, "devDependencies": { "inferno-create-element": "9.0.11", "inferno-shared": "9.0.11", "inferno-utils": "9.0.11", "inferno-vnode-flags": "9.0.11" }, "rollup": { "bundledDependencies": [ "inferno-shared", "inferno-vnode-flags" ], "moduleName": "Inferno.Animation", "moduleGlobals": { "inferno": "Inferno" } } } ================================================ FILE: packages/inferno-animation/readme.md ================================================ # inferno-animation Helper components and utils to add smooth CSS-animations to your Inferno apps. Extend from `` and include the css from index.css in this package to get default animation on opacity and height. Requires setting `box-sizing: border-box;` on the animated element. If you want to customise your animations, just use index.css as a template and replace "inferno-animation" prefix in the CSS-class names with your custom animation name (i.e. mySuperAnimation). Then pass that name to your animated component as an attribute `` and your customised animation will be used. For examples of what animations look like you can try inferno/docs/animations/index.html. ## Install ``` npm install inferno-animation ``` ## Usage There are three base components you can extend from to get animations in a straightforward way without any wiring. - AnimatedComponent -- animates on add/remove - AnimatedMoveComponent -- animates on move (within the same parent) - AnimatedAllComponent -- animates on add/remove and move (within the same parent) You can also animate functional components. There are a couple of examples of animations in the main repos in the `docs/animations` and `docs/animations-demo` folder. If you don't want to extend from one of the pre-wired components, look att src/AnimatedAllComponent.ts to see how to wire up the three animation hooks: - componentDidAppear - componentWillDisappear - componentWillMove Using AnimatedAllComponent is just like working with ordinary components. Don't forget to add the CSS or you can get strange results: app.js ```js import { Component } from 'inferno'; import { AnimatedAllComponent } from 'inferno-animation'; import './app.css'; // Animate on add/remove class MyAnimated extends AnimatedAllComponent { render() { return
  • {this.props.children}
  • ; } } class MyList extends Component { constructor() { super(); this.state = { items: [1, 2, 3, 4, 5], }; } render() { return (
      {this.state.items.map((item) => ( {item} ))}
    ); } } ``` app.css ```css @import '~inferno-animation/index.css'; ul { list-style: none; padding: 0; margin: 0; } li.test { box-sizing: border-box; font-size: 2em; background: #ddd; border-bottom: 1px solid white; } ``` The syntax for hooking up a function component is straight forward too: ```js import { componentDidAppear, componentWillDisappear, componentWillMove, } from 'inferno-animation'; ... ; ``` IMPORTANT! Always use the provided helper methods instead of implementing the hooks yourself. There might be optimisations and/or changes to how the animation hooks are implemented in future versions of Inferno that you want to benefit from. ### Global animations Global animations allow you to animate a component between positions on two different "pages". Technincally this means they don't have the same parent element. When you mount one page imediately after unmounting the other page, inferno-animation will perform a FLIP-animation between the two positions. To match the elements you use the attribute `globalAnimationKey` which accept a string. Global animations are very simple to use, [check this example.](https://github.com/infernojs/inferno/blob/master/docs/animations-global-demo/app.js) ### Bootstrap style modal animation This is an example of how you could implement a Bootstrap style Modal animation using inferno-animation. These two animations are used both for the backdrop and the modal and the purpose is to support the CSS-rules without modification. - always use the inferno-animation utility functions - implementation is straight forward - `callback` in animateModalOnWillDisappear triggers the dom-removal in Inferno and is crucial! Custom animations won't be coordinated with the standard animations to reduce reflow, but performance is not an issue with just a few animations running simultaneously. Use the standard animations for grid or list items. Call these helper methods from `componentDidAppear` and `componentWillDisapper` of your backdrop and content component when you build a Bootstrap style modal. ```js import { utils } from 'inferno-animation'; const { addClassName, removeClassName, registerTransitionListener, forceReflow, setDisplay, } = utils; export function animateModalOnWillDisappear(dom, callback, onClosed) { registerTransitionListener([dom], () => { // Always call the dom removal callback first! callback && callback(); onClosed && onClosed(); }); setTimeout(() => { removeClassName(dom, 'show'); }, 5); } export function animateModalOnDidAppear(dom, onOpened) { setDisplay(dom, 'none'); addClassName(dom, 'fade'); forceReflow(dom); setDisplay(dom, undefined); registerTransitionListener([dom, dom.children[0]], function () { // *** Cleanup *** setDisplay(dom, undefined); onOpened && onOpened(dom); }); addClassName(dom, 'show'); } ``` ================================================ FILE: packages/inferno-animation/src/AnimatedAllComponent.ts ================================================ import { Component, type InfernoNode, type ParentDOM } from 'inferno'; import { type AnimationClass, componentDidAppear, componentWillDisappear, componentWillMove, } from './animations'; interface AnimationProp { animation?: string | AnimationClass; children?: InfernoNode; } export abstract class AnimatedAllComponent extends Component< AnimationProp & P, S > { public componentDidAppear(dom: HTMLElement | SVGElement): void { componentDidAppear(dom, this.props); } public componentWillDisappear( dom: HTMLElement | SVGElement, callback: () => void, ): void { componentWillDisappear(dom, this.props, callback); } public componentWillMove( parentVNode, parent: ParentDOM, dom: HTMLElement | SVGElement, ): void { componentWillMove(parentVNode, parent, dom, this.props); } } ================================================ FILE: packages/inferno-animation/src/AnimatedComponent.ts ================================================ import { Component, type InfernoNode } from 'inferno'; import { type AnimationClass, componentDidAppear, componentWillDisappear, } from './animations'; interface AnimationProp { animation?: string | AnimationClass; children?: InfernoNode; } export abstract class AnimatedComponent extends Component< AnimationProp & P, S > { public componentDidAppear(dom: HTMLElement): void { componentDidAppear(dom, this.props); } public componentWillDisappear( dom: HTMLElement | SVGElement, callback: () => void, ): void { componentWillDisappear(dom, this.props, callback); } } ================================================ FILE: packages/inferno-animation/src/AnimatedMoveComponent.ts ================================================ import { Component, type InfernoNode } from 'inferno'; import { type AnimationClass, componentWillMove } from './animations'; interface AnimationProp { animation?: string | AnimationClass; children?: InfernoNode; } export abstract class AnimatedMoveComponent extends Component< AnimationProp & P, S > { public componentWillMove( parentVNode, parent: HTMLElement | SVGElement, dom: HTMLElement | SVGElement, ): void { componentWillMove(parentVNode, parent, dom, this.props); } } ================================================ FILE: packages/inferno-animation/src/animationCoordinator.ts ================================================ import { forceReflow } from './utils'; // This is only used for development and should be set to false for release // eslint-disable-next-line no-constant-binary-expression const _DBG_COORD_ = false && process.env.NODE_ENV !== 'production'; export const enum AnimationPhase { INITIALIZE, MEASURE, SET_START_STATE, ACTIVATE_TRANSITIONS, REGISTER_LISTENERS, ACTIVATE_ANIMATION, length, // This will equal length of actual phases since TS converts this to a zero based list of ints } type GlobalAnimationKey = string; export interface GlobalAnimationState { width: number; height: number; x: number; y: number; ticks: number; } const _globalAnimationSources: Record< GlobalAnimationKey, GlobalAnimationState > = {}; export function _globalAnimationGC(): void { let entriesLeft = false; for (const key in _globalAnimationSources) { if (--_globalAnimationSources[key].ticks < 0) { delete _globalAnimationSources[key]; } else entriesLeft = true; } if (entriesLeft) { requestAnimationFrame(_globalAnimationGC); } } export function addGlobalAnimationSource( key: GlobalAnimationKey, state: GlobalAnimationState, ): void { state.ticks = 5; _globalAnimationSources[key] = state; if (_globalAnimationGC === null) { requestAnimationFrame(_globalAnimationGC); } } export function consumeGlobalAnimationSource( key: GlobalAnimationKey, ): GlobalAnimationState { const tmp = _globalAnimationSources[key]; if (tmp !== undefined) { delete _globalAnimationSources[key]; } return tmp; } let _animationQueue: Array<(phase: AnimationPhase) => void> = []; let _animationActivationQueue: Array<(phase: AnimationPhase) => void> = []; const IDLE = 0; let _nextAnimationFrame: number = IDLE; let _nextActivateAnimationFrame: number = IDLE; function _runActivateAnimationPhase(): void { _nextActivateAnimationFrame = IDLE; // Get animations to execute const animationQueue = _animationActivationQueue; // Clear global queue _animationActivationQueue = []; for (let i = 0; i < animationQueue.length; i++) { animationQueue[i](AnimationPhase.ACTIVATE_ANIMATION); } } function _runAnimationPhases(): void { _nextAnimationFrame = IDLE; // Get animations to execute const animationQueue = _animationQueue; // Clear global queue _animationQueue = []; // So what this does is run the animation phases in order. Most of the phases are invoked // by a simple call to all the registered callbacks. However: // // - ACTIVATE_TRANSITIONS require a reflow in order to not // interfere with the previous setting of the animation start class // // - ACTIVATE_ANIMATION needs to be called async so the transitions actually fire, // we choose to use an animation frame. // for (let i = 0; i < AnimationPhase.length; i++) { const phase = i as AnimationPhase; switch (phase) { case AnimationPhase.ACTIVATE_ANIMATION: // Final phase - Activate animations // This is a special case and is executed differently from others _animationActivationQueue = _animationActivationQueue.concat(animationQueue); if (_nextActivateAnimationFrame === IDLE) { // Animations are activated on the next animation frame _nextActivateAnimationFrame = requestAnimationFrame( _runActivateAnimationPhase, ); } break; default: if (phase === AnimationPhase.ACTIVATE_TRANSITIONS) { // Force reflow before executing ACTIVATE_TRANSITIONS forceReflow(); } for (let j = 0; j < animationQueue.length; j++) { animationQueue[j](phase); } } } } function _debugAnimationPhases( phase: AnimationPhase, animationQueue: Array<(phase: AnimationPhase) => void>, ): AnimationPhase { // When debugging we call _runAnimationPhases once for each phase // so only set to idle when done if (phase === AnimationPhase.length - 1) { _nextAnimationFrame = IDLE; } switch (phase) { case AnimationPhase.ACTIVATE_ANIMATION: // Final phase - Activate animations // This is a special case and is executed differently from others _animationActivationQueue = _animationActivationQueue.concat(animationQueue); if (_nextActivateAnimationFrame === IDLE) { // Animations are activated on the next animation frame _nextActivateAnimationFrame = requestAnimationFrame( _runActivateAnimationPhase, ); } break; default: if (phase === AnimationPhase.ACTIVATE_TRANSITIONS) { // Force reflow before executing ACTIVATE_TRANSITIONS forceReflow(); } for (let j = 0; j < animationQueue.length; j++) { animationQueue[j](phase); } } return phase + 1; } export function queueAnimation( callback: (phase: AnimationPhase) => void, ): void { _animationQueue.push(callback); if (_nextAnimationFrame === IDLE) { if (!_DBG_COORD_) { _nextAnimationFrame = requestAnimationFrame(_runAnimationPhases); } else { /** ** DEV DEBUGGING code path ****/ // Run animation phases one at a time when debugging // to allow visually inspecting changes. let _animationDebugQueue = _animationQueue; const _runPhase = (startPhase: AnimationPhase): void => { _nextAnimationFrame = requestAnimationFrame(() => { // Reset the global animation queue so any changes // added during this animation round is queued if (_animationDebugQueue === _animationQueue) { _animationQueue = []; } const nextStartPhase = _debugAnimationPhases( startPhase, _animationDebugQueue, ); if ( nextStartPhase !== undefined && nextStartPhase < AnimationPhase.length ) { _runPhase(nextStartPhase); } else if (_animationQueue.length > 0) { // All phases done, check if the queue has been repopulated // and rerun if it has _animationDebugQueue = _animationQueue; _runPhase(0); } }); }; // TODO: We could create hooks to show a simply UI to control // animation execution. For now you need to set a break point _runPhase(0); /** ** /end DEV DEBUGGING ****/ } } } // This is needed for tests. Coordinated animations are run on // next animation frame, so we need to make sure we wait for them to finish. export function hasPendingAnimations(): boolean { return _nextAnimationFrame !== IDLE || _nextActivateAnimationFrame !== IDLE; } ================================================ FILE: packages/inferno-animation/src/animations.ts ================================================ import { addClassName, clearDimensions, clearTransform, decrementMoveCbCount, forceReflow, getDimensions, getGeometry, incrementMoveCbCount, registerTransitionListener, removeClassName, resetDisplay, setDimensions, setDisplay, setTransform, } from './utils'; import { addGlobalAnimationSource, AnimationPhase, consumeGlobalAnimationSource, type GlobalAnimationState, queueAnimation, } from './animationCoordinator'; import { isNull, isNullOrUndef } from 'inferno-shared'; import { type ParentDOM } from 'inferno'; export interface AnimationClass { active: string; end: string; start: string; } function getAnimationClass( animationProp: AnimationClass | string | undefined | null, prefix: string, ): AnimationClass { let animCls: AnimationClass; if (!isNullOrUndef(animationProp) && typeof animationProp === 'object') { animCls = animationProp; } else { const animationName = animationProp || 'inferno-animation'; const placeholder = animationName + prefix; animCls = { active: placeholder + '-active', end: placeholder + '-end', start: placeholder, }; } return animCls; } export function componentDidAppear(dom: HTMLElement | SVGElement, props): void { // Get dimensions and unpack class names const cls = getAnimationClass(props.animation, '-enter'); // Moved measuring to pre_initialize. It causes a reflow for each component beacuse of the setDisplay of previous component. const dimensions = {}; const display = setDisplay(dom, 'none'); const sourceState = props.globalAnimationKey === undefined ? null : consumeGlobalAnimationSource(props.globalAnimationKey); queueAnimation((phase: AnimationPhase) => { _didAppear(phase, dom, cls, dimensions, display, sourceState); }); } function _getDidAppearTransitionCallback(dom, cls) { return () => { // 5. Remove the element clearDimensions(dom); removeClassName(dom, cls.active + ' ' + cls.end); // 6. Call callback to allow stuff to happen // Not currently used but this is where one could // add a call to something like this.didAppearDone }; } function _didAppear( phase: AnimationPhase, dom: HTMLElement | SVGElement, cls: AnimationClass, dimensions, display: string, sourceState: GlobalAnimationState | null, ): void { switch (phase) { case AnimationPhase.INITIALIZE: // Needs to be done in a single pass to avoid reflows // We set display: none whilst waiting for an animation frame to avoid flicker resetDisplay(dom, display); return; case AnimationPhase.MEASURE: // In case of img element that hasn't been loaded, just trigger reflow if (dom.tagName !== 'IMG' || (dom as any).complete) { const tmp = getDimensions(dom); dimensions.x = tmp.x; dimensions.y = tmp.y; dimensions.width = tmp.width; dimensions.height = tmp.height; } else { forceReflow(); } return; case AnimationPhase.SET_START_STATE: // 1. Set start of animation if ( !isNullOrUndef(sourceState) && dimensions.width !== 0 && dimensions.height !== 0 ) { // const diffX = (sourceState.width - dimensions.width) / 2; // const diffY = (sourceState.height - dimensions.height) / 2; const dx = sourceState.x - dimensions.x; const dy = sourceState.y - dimensions.y; const scaleX = sourceState.width / dimensions.width; const scaleY = sourceState.height / dimensions.height; setTransform(dom, dx, dy, scaleX, scaleY); } addClassName(dom, cls.start); return; case AnimationPhase.ACTIVATE_TRANSITIONS: // 2. Activate transition (after a reflow) addClassName(dom, cls.active); return; case AnimationPhase.REGISTER_LISTENERS: // 3. Set an animation listener, code at end // Needs to be done after activating so timeout is calculated correctly registerTransitionListener( // *** Cleanup is broken out as micro optimisation *** [dom], _getDidAppearTransitionCallback(dom, cls), ); return; case AnimationPhase.ACTIVATE_ANIMATION: // 4. Activate target state (called async via requestAnimationFrame) if ( !isNullOrUndef(sourceState) && dimensions.width !== 0 && dimensions.height !== 0 ) { clearTransform(dom); } setDimensions(dom, dimensions.width, dimensions.height); removeClassName(dom, cls.start); addClassName(dom, cls.end); } } export function componentWillDisappear( dom: HTMLElement | SVGElement, props, callback: () => void, ): void { // Get dimensions and unpack class names const cls = getAnimationClass(props.animation, '-leave'); const dimensions = getDimensions(dom); queueAnimation((phase) => { _willDisappear(phase, dom, callback, cls, dimensions); }); if (props.globalAnimationKey !== undefined) { addGlobalAnimationSource( props.globalAnimationKey, dimensions as GlobalAnimationState, ); dom.style.setProperty('visibility', 'hidden'); } } function _willDisappear( phase: AnimationPhase, dom: HTMLElement | SVGElement, callback: () => void, cls: AnimationClass, dimensions, ): void { switch (phase) { case AnimationPhase.MEASURE: // 1. Set animation start state and dimensions setDimensions(dom, dimensions.width, dimensions.height); addClassName(dom, cls.start); return; case AnimationPhase.ACTIVATE_TRANSITIONS: // 2. Activate transition (after a reflow) addClassName(dom, cls.active); return; case AnimationPhase.REGISTER_LISTENERS: // 3. Set an animation listener, code at end // Needs to be done after activating so timeout is calculated correctly registerTransitionListener( // Unlike _didAppear, no cleanup needed since node is removed. // Just passing the componentWillDisappear callback so Inferno can // remove the nodes. [dom], callback, ); return; case AnimationPhase.ACTIVATE_ANIMATION: // 4. Activate target state (called async via requestAnimationFrame) addClassName(dom, cls.end); removeClassName(dom, cls.start); clearDimensions(dom); } } export function componentWillMove( parentVNode, parent: ParentDOM, _dom: HTMLElement | SVGElement, props: any, ): void { // Measure all siblings of moved node once before any mutations are done let els; if (!parentVNode.$MV) { parentVNode.$MV = true; els = []; // @ts-expect-error parent is not supposed to be null let tmpEl = parent.firstChild as HTMLElement | SVGElement; while (!isNull(tmpEl)) { els.push({ dx: 0, dy: 0, geometry: getGeometry(tmpEl), moved: false, node: tmpEl, }); tmpEl = tmpEl.nextSibling as HTMLElement | SVGElement; } } // Get animation class names const cls = getAnimationClass(props.animation, '-move'); const animState = { els, isMaster: !isNullOrUndef(els), parentVNode, }; queueAnimation((phase) => { _willMove(phase, cls, animState); }); } function _willMove( phase: AnimationPhase, cls: AnimationClass, animState, ): void { const { els, isMaster, parentVNode } = animState; switch (phase) { case AnimationPhase.MEASURE: // If we are responsible for triggering measures, we check all the target positions if (isMaster) { for (let i = 0; i < els.length; i++) { const tmpItem = els[i]; // Make sure we can measure target properly removeClassName(tmpItem.node, cls.active); // Measure const geometry = getGeometry(tmpItem.node); const deltaX = tmpItem.geometry.x - geometry.x; const deltaY = tmpItem.geometry.y - geometry.y; if (deltaX !== 0 || deltaY !== 0) { tmpItem.moved = true; tmpItem.dx = deltaX; tmpItem.dy = deltaY; } // TODO: Check dimensions } } return; case AnimationPhase.SET_START_STATE: /** * At this state we have measure the size of the moving node * both at source and target so now we let the source placeholder * fill the source space and move the node to start position * by transform */ if (isMaster) { for (let i = 0; i < els.length; i++) { const tmpItem = els[i]; if (tmpItem.moved) { setTransform(tmpItem.node, tmpItem.dx, tmpItem.dy, 1, 1); } // TODO: Set dimensions } } return; case AnimationPhase.ACTIVATE_TRANSITIONS: // A reflow is triggered prior to this step if (isMaster) { for (let i = 0; i < els.length; i++) { const tmpItem = els[i]; if (tmpItem.moved) { addClassName(tmpItem.node, cls.active); } } } return; case AnimationPhase.REGISTER_LISTENERS: if (isMaster) { for (let i = 0; i < els.length; i++) { const tmpItem = els[i]; if (tmpItem.moved) { registerTransitionListener( // How to know if this is a compound move? [tmpItem.node], _getWillMoveTransitionCallback(tmpItem.node, cls), ); // Keep track of how many callbacks will be fired incrementMoveCbCount(tmpItem.node); } } } return; case AnimationPhase.ACTIVATE_ANIMATION: // 10. Apply target geometry of node to target if (isMaster) { for (let i = 0; i < els.length; i++) { const tmpItem = els[i]; if (tmpItem.moved) { setTransform(tmpItem.node, 0, 0, 1, 1); } } } // TODO: Set dimensions if (parentVNode.$MV) parentVNode.$MV = false; } } function _getWillMoveTransitionCallback( dom: HTMLElement | SVGElement, cls: AnimationClass, ) { return () => { // Only remove these if the translate has completed const cbCount = decrementMoveCbCount(dom); if (cbCount === 0) { clearDimensions(dom); clearTransform(dom); removeClassName(dom, cls.active); } }; } ================================================ FILE: packages/inferno-animation/src/index.ts ================================================ import { addClassName, clearDimensions, forceReflow, getDimensions, registerTransitionListener, removeClassName, setDimensions, setDisplay, } from './utils'; export { AnimatedAllComponent } from './AnimatedAllComponent'; export { AnimatedComponent } from './AnimatedComponent'; export { AnimatedMoveComponent } from './AnimatedMoveComponent'; export { componentDidAppear, componentWillDisappear, componentWillMove, type AnimationClass, } from './animations'; export { hasPendingAnimations } from './animationCoordinator'; export const utils = { addClassName, clearDimensions, forceReflow, getDimensions, registerTransitionListener, removeClassName, setDimensions, setDisplay, }; ================================================ FILE: packages/inferno-animation/src/utils.ts ================================================ import { isFunction } from 'inferno-shared'; export interface Dimensions { height: number; width: number; x: number; y: number; } function filterEmpty(c: string): boolean { return c !== ''; } function getClassNameList(className: string): string[] { return className.split(' ').filter(filterEmpty); } export function addClassName( node: HTMLElement | SVGElement, className: string, ): void { const classNameList = getClassNameList(className); for (let i = 0; i < classNameList.length; i++) { node.classList.add(classNameList[i]); } } export function removeClassName( node: HTMLElement | SVGElement, className: string, ): void { const classNameList = getClassNameList(className); for (let i = 0; i < classNameList.length; i++) { node.classList.remove(classNameList[i]); } } export function forceReflow(): number { return document.body.clientHeight; } // A quicker version used in pre_initialize export function resetDisplay( node: HTMLElement | SVGElement, value?: string, ): void { if (value !== undefined) { node.style.setProperty('display', value); } else { node.style.removeProperty('display'); _cleanStyle(node); } } export function setDisplay( node: HTMLElement | SVGElement, value?: string, ): string { const oldVal = node.style.getPropertyValue('display'); if (oldVal !== value) { if (value !== undefined) { node.style.setProperty('display', value); } else { node.style.removeProperty('display'); _cleanStyle(node); } } return oldVal; } function _cleanStyle(node: HTMLElement | SVGElement): void { if (!node.style) { // https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute node.removeAttribute('style'); } } export function getDimensions(node: HTMLElement | SVGElement): Dimensions { const tmpDisplay = node.style.getPropertyValue('display'); // The `display: none;` workaround was added to support Bootstrap animations in // https://github.com/jhsware/inferno-bootstrap/blob/be4a17bff5e785b993a66a2927846cd463fecae3/src/Modal/AnimateModal.js // we should consider deprecating this, or providing a different solution for // those who only do normal animations. const isDisplayNone = window.getComputedStyle(node).getPropertyValue('display') === 'none'; if (isDisplayNone) { node.style.setProperty('display', 'block'); } const tmp = node.getBoundingClientRect(); if (isDisplayNone) { // node.style.display = tmpDisplay node.style.setProperty('display', tmpDisplay); _cleanStyle(node); } return { height: tmp.height, width: tmp.width, x: tmp.x, y: tmp.y, }; } export function getGeometry(node: HTMLElement | SVGElement): DOMRect { return node.getBoundingClientRect(); } export function setTransform( node: HTMLElement | SVGElement, x: number, y: number, scaleX: number = 1, scaleY: number = 1, ): void { const doScale = scaleX !== 1 || scaleY !== 1; if (doScale) { node.style.transformOrigin = '0 0'; node.style.transform = `translate(${x}px,${y}px) scale(${scaleX},${scaleY})`; } else { node.style.transform = `translate(${x}px,${y}px)`; } } export function clearTransform(node: HTMLElement | SVGElement): void { node.style.transform = ''; node.style.transformOrigin = ''; } export function setDimensions( node: HTMLElement | SVGElement, width: number, height: number, ): void { node.style.width = width + 'px'; node.style.height = height + 'px'; } export function clearDimensions(node: HTMLElement | SVGElement): void { node.style.width = node.style.height = ''; } function _getMaxTransitionDuration(nodes): { maxDuration: number; nrofTransitions: number; } { let nrofTransitions = 0; let maxDuration = 0; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (!node) continue; const cs = window.getComputedStyle(node); const dur = cs.getPropertyValue('transition-duration').split(','); const del = cs.getPropertyValue('transition-delay').split(','); const props = cs.getPropertyValue('transition-property').split(','); for (const prop of props) { const fixedProp = prop.trim(); if (fixedProp[0] === '-') { const tmp = fixedProp.split('-').splice(2).join('-'); // Since I increase number of transition events to expect by // number of durations found I need to remove browser prefix // variations of the same property if (fixedProp.includes(tmp)) { nrofTransitions--; } } } let animTimeout = 0; for (let j = 0; j < dur.length; j++) { const duration = dur[j]; const delay = del[j]; const tp = parseFloat(duration) + parseFloat(delay); if (tp > animTimeout) animTimeout = tp; } nrofTransitions += dur.length; // Max duration should be equal to the longest animation duration // of all found transitions including delay if (animTimeout > maxDuration) { maxDuration = animTimeout; } } return { maxDuration, nrofTransitions, }; } function setAnimationTimeout(onTransitionEnd, rootNode, maxDuration): void { if (rootNode.nodeName === 'IMG' && !rootNode.complete) { // Image animations should wait for loaded until the timeout is started, otherwise animation will be cut short // due to loading delay rootNode.addEventListener('load', () => { setTimeout( () => onTransitionEnd({ target: rootNode, timeout: true }), maxDuration === 0 ? 0 : Math.round(maxDuration * 1000) + 100, ); }); } else { setTimeout( () => onTransitionEnd({ target: rootNode, timeout: true }), maxDuration === 0 ? 0 : Math.round(maxDuration * 1000) + 100, ); } } /** * You need to pass the root element and ALL animated children that have transitions, * if there are any, so the timeout is set to the longest duration. Otherwise there * will be animations that fail to complete before the timeout is triggered. * * @param nodes a list of nodes that have transitions that are part of this animation * @param callback callback when all transitions of participating nodes are completed */ export function registerTransitionListener( nodes: Array, callback: () => void, ): void { const rootNode = nodes[0]; /** * Here comes the transition event listener */ const transitionDuration = _getMaxTransitionDuration(nodes); const maxDuration = transitionDuration.maxDuration; let nrofTransitionsLeft = transitionDuration.nrofTransitions; let done = false; const onTransitionEnd = (event): void => { // Make sure this is an actual event if (!event || done) { return; } if (!event.timeout) { // Make sure it isn't a child that is triggering the event let goAhead = false; for (let i = 0; i < nodes.length; i++) { // Note: Check for undefined nodes (happens when an animated el doesn't have children) if (nodes[i] !== undefined && event.target === nodes[i]) { goAhead = true; break; } } if (!goAhead) return; // Wait for all transitions if (--nrofTransitionsLeft > 0) { return; } } // This is it... done = true; /** * Perform cleanup */ rootNode.removeEventListener('transitioncancel', onTransitionEnd, false); rootNode.removeEventListener('transitionend', onTransitionEnd, false); if (isFunction(callback)) { callback(); } }; // if element gets removed from the DOM before transition is triggered, browser will raise transitioncancel event rootNode.addEventListener('transitioncancel', onTransitionEnd, false); rootNode.addEventListener('transitionend', onTransitionEnd, false); setAnimationTimeout(onTransitionEnd, rootNode, maxDuration); } export function incrementMoveCbCount(node): number { let curr = parseInt(node.dataset.moveCbCount, 10); if (isNaN(curr)) { curr = 1; } else { curr++; } node.dataset.moveCbCount = curr; return curr; } export function decrementMoveCbCount(node): number { let curr = parseInt(node.dataset.moveCbCount, 10); if (isNaN(curr)) { curr = 0; } else { curr--; if (curr === 0) { node.dataset.moveCbCount = ''; } else { node.dataset.moveCbCount = curr; } } return curr; } ================================================ FILE: packages/inferno-clone-vnode/README.md ================================================ # Inferno-clone-vnode This package can be used to clone Inferno virtual nodes. This package can also be aliased to `React.cloneElement`. ``` import { cloneVNode } from 'inferno-clone-vnode'; const cloned = cloneVNode( vNode, [props], [...children] ) ``` Clone and return a new Inferno vNode using element as the starting point. The resulting element will have the original element’s props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved. ================================================ FILE: packages/inferno-clone-vnode/__tests__/cloneVNode.spec.tsx ================================================ import { cloneVNode } from 'inferno-clone-vnode'; import { Component, createTextVNode, Fragment, InfernoNode, type InfernoSingleNode, render, type VNode, } from 'inferno'; describe('cloneVNode (JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); }); it('should clone a tag', () => { const node = cloneVNode(, null); render(node, container); expect(container.innerHTML).toBe(''); }); it('should clone with third argument array', () => { const node = cloneVNode(
    , null, []); render(node, container); expect(container.innerHTML).toBe('
    '); }); it('should clone with third argument overriding props and cloned node children', () => { const node = cloneVNode(
    f
    , { children: 'x' }, [1]); render(node, container); expect(container.innerHTML).toBe(''); }); it('should clone with third argument overriding props and cloned node children', () => { const node = cloneVNode(
    f
    , { children: 'x' }, [undefined]); render(node, container); expect(container.innerHTML).toBe('
    '); }); it('should clone OPT_ELEMENT', () => { const noop = () => {}; // noinspection HtmlUnknownAttribute const node = cloneVNode( // @ts-expect-error // eslint-disable-next-line inferno/no-unknown-property
    , { children: [] } ); render(node, container); expect(container.innerHTML).toBe('
    '); }); it('should clone a basic element with array children', () => { const node = cloneVNode(
    , { children: [] }); render(node, container); expect(container.innerHTML).toBe('
    '); }); it('should clone a basic element with children in props and as third argument', () => { const node1 = cloneVNode(
    , { children: arr1a }, arr2b, ); render(node1, container); expect(container.innerHTML).toBe('
    arr2b
    '); const node2 = cloneVNode(
    , { children: [arr2a] }, arr2b, ); render(node2, container); expect(container.innerHTML).toBe('
    arr2b
    '); const node3 = cloneVNode(
    , { children: [arr3a] }, [ arr3b, ]); render(node3, container); expect(container.innerHTML).toBe('
    arr3b
    '); }); it('Should support multiple parameters as children', () => { // @ts-ignore const node = cloneVNode(
    , null, arr3a, arr3b, arr3c, ); render(node, container); expect(container.innerHTML).toBe( '
    arr3aarr3barr3c
    ', ); }); it('Should support multiple nodes as children inside array', () => { const node = cloneVNode(
    , null, [ arr3a, arr3b, arr3c, ]); render(node, container); expect(container.innerHTML).toBe( '
    arr3aarr3barr3c
    ', ); }); it('Should support single node as children', () => { const node = cloneVNode(
    , null, arr3a); render(node, container); expect(container.innerHTML).toBe('
    arr3a
    '); }); it('Should support single node as children inside array', () => { const node = cloneVNode(
    , null, [arr3a]); render(node, container); expect(container.innerHTML).toBe('
    arr3a
    '); }); it('should clone a basic element with null children', () => { const node = cloneVNode(
    , { children: null }); render(node, container); expect(container.innerHTML).toBe('
    '); }); it('should clone a basic element with key and ref', () => { const ref = () => {}; const node = cloneVNode(
    , { key: 'foo', ref }); expect(node.key).toBe('foo'); expect(node.ref).toBe(ref); }); it('should clone a basic element with different children and props', () => { const node1 =
    Hello world
    ; render(node1, container); expect(container.innerHTML).toBe('
    Hello world
    '); const node2 = cloneVNode(node1, null, 'Hello world 2!'); render(node2, container); expect(container.innerHTML).toBe('
    Hello world 2!
    '); const node3 = cloneVNode(node2, { className: 'foo' }, 'Hello world 2!'); render(node3, container); expect(container.innerHTML).toBe('
    Hello world 2!
    '); const node4 = cloneVNode(node1, { className: 'foo' }, 'Hello world 3!'); render(node4, container); expect(container.innerHTML).toBe('
    Hello world 3!
    '); }); function StatelessComponent(props) { return
    ; } it('should clone a basic stateless component with different children and props', () => { const node1 = ; render(node1, container); expect(container.innerHTML).toBe('
    Hello world
    '); const node2 = cloneVNode(node1, { children: 'Hello world 2!' }); render(node2, container); expect(container.innerHTML).toBe('
    Hello world 2!
    '); const node3 = cloneVNode(node1, { children: 'Hello world 3!', className: 'yo', }); render(node3, container); expect(container.innerHTML).toBe('
    Hello world 3!
    '); }); it('Should prefer falsy children (undefined) if its provided over existing children', () => { const node1 =
    1
    ; const clone = cloneVNode(node1, null, [undefined]); render(clone, container); expect(container.innerHTML).toEqual('
    '); }); it('Should clone Component with vNode div children', () => { class Com extends Component<{ children?: InfernoNode }> { public render({ children }) { return children; } } const com1 = (
    abc
    ); const clone = cloneVNode(com1); render(clone, container); expect(container.innerHTML).toEqual('
    abc
    '); }); it('Should clone Component with no props at all', () => { class Com extends Component<{ children?: InfernoNode }> { public render({ children }) { return children; } } const com1 = ; const clone = cloneVNode(com1); render(clone, container); expect(container.innerHTML).toEqual(''); }); it('Should clone vNode with no props at all', () => { const span = ; const clone = cloneVNode(span); render(clone, container); expect(container.innerHTML).toEqual(''); }); it('Should clone Component with vNode text children', () => { class Com extends Component { public render() { return 'Text'; } } const com1 = (
    1
    ); const clone = cloneVNode(com1); render(clone, container); expect(container.innerHTML).toEqual('Text'); }); it('Should clone textVNode', () => { const textNode = createTextVNode('foobar'); const clone = cloneVNode(textNode); render(clone, container); expect(container.innerHTML).toEqual('foobar'); }); it('Should clone textVnode with new content', () => { const textNode = createTextVNode('foobar'); const clone = cloneVNode(textNode, null, 'foo'); render(clone, container); expect(container.innerHTML).toEqual('foo'); }); it('Should prefer children in order', () => { function Bar({ children }) { return
    {children}
    ; } const nodeToClone = First; render(nodeToClone, container); expect(container.innerHTML).toBe('
    First
    '); render(cloneVNode(nodeToClone, { children: 'Second' }), container); expect(container.innerHTML).toBe('
    Second
    '); render(cloneVNode(nodeToClone, { children: 'Second' }, 'Third'), container); expect(container.innerHTML).toBe('
    Third
    '); // @ts-ignore render( cloneVNode(nodeToClone, { children: 'Second' }, 'Third', 'Fourth'), container, ); expect(container.innerHTML).toBe('
    ThirdFourth
    '); }); it('Should prefer children in order #2', () => { function Bar({ children }) { return
    {children}
    ; } const nodeToClone = First; render(nodeToClone, container); expect(container.innerHTML).toBe('
    First
    '); render(cloneVNode(nodeToClone, null), container); expect(container.innerHTML).toBe('
    First
    '); render(cloneVNode(nodeToClone, null, null), container); expect(container.innerHTML).toBe('
    '); }); describe('Cloning className', () => { it('Should prefer new props over cloned object', () => { const node =
    ; render(node, container); expect(container.firstChild.className).toEqual('test'); const newNode = cloneVNode(node, { className: 'foo', }); render(newNode, container); // expect(newNode.props.className).toBe(undefined); , This depends on if we are running inferno-compat or not // expect(newNode.props.hasOwnProperty('className')).toBe(false); expect(container.firstChild.className).toBe('foo'); expect(container.innerHTML).toEqual('
    '); }); it('Should remove className if new one is empty', () => { const node =
    ; render(node, container); expect(container.firstChild.className).toEqual('test'); const newNode = cloneVNode(node, { className: null, }); render(newNode, container); expect(container.firstChild.className).toBe(''); expect(container.innerHTML).toEqual('
    '); }); it('Should keep previous className when new props dont have that property at all', () => { const node =
    ; render(node, container); expect(container.firstChild.className).toEqual('test'); const newNode = cloneVNode(node, { id: 'wow', }); render(newNode, container); expect(Object.prototype.hasOwnProperty.call(newNode.props, 'id')).toBe( true, ); // expect(newNode.props.className).toBe(undefined); // expect(newNode.props.hasOwnProperty('className')).toBe(false); expect(container.firstChild.className).toBe('test'); expect(container.firstChild.getAttribute('id')).toBe('wow'); expect(container.innerHTML).toEqual('
    '); }); it('Should be possible to add props to children', () => { function Foobar(props) { return
    {cloneVNode(props.children, { foo: 'bar' })}
    ; } function ChildCom(props) { return
    {props.foo}
    ; } render( , container, ); expect(container.innerHTML).toEqual('
    bar
    '); }); }); describe('Cloning key', () => { it('Should prefer new props over cloned object', () => { const node =
    ; expect(node.key).toEqual('test'); render(node, container); expect(container.innerHTML).toEqual('
    '); const newNode = cloneVNode(node, { key: 'foo', }); expect(newNode.key).toEqual('foo'); expect(newNode.props.ref).toBe(undefined); // expect(newNode.props.hasOwnProperty('key')).toBe(false); render(newNode, container); expect(container.innerHTML).toEqual('
    '); }); it('Should remove key if new one is empty', () => { const node =
    ; expect(node.key).toEqual('test'); render(node, container); expect(container.innerHTML).toEqual('
    '); const newNode = cloneVNode(node, { key: null, }); expect(newNode.key).toEqual(null); render(newNode, container); expect(newNode.props.key).toBe(undefined); // expect(newNode.props.hasOwnProperty('key')).toBe(false); expect(container.innerHTML).toEqual('
    '); }); it('Should keep previous key when new props dont have that property at all', () => { const node =
    ; expect(node.key).toEqual('test'); render(node, container); expect(container.innerHTML).toEqual('
    '); const newNode = cloneVNode(node, { className: null, }); expect(newNode.key).toEqual('test'); expect(newNode.props.key).toBe(undefined); expect(newNode.props.className).toBe(undefined); // expect(newNode.props.hasOwnProperty('key')).toBe(false); // expect(newNode.props.hasOwnProperty('className')).toBe(false); render(newNode, container); expect(container.innerHTML).toEqual('
    '); }); }); describe('Cloning Ref', () => { function initialFunc() {} it('Should prefer new props over cloned object', () => { const node =
    ; expect(node.ref).toEqual(initialFunc); render(node, container); expect(container.innerHTML).toEqual('
    '); function newFunction() {} const newNode = cloneVNode(node, { ref: newFunction, }); expect(newNode.ref).toEqual(newFunction); // expect(newNode.props.hasOwnProperty('ref')).toBe(false); expect(newNode.props.ref).toBe(undefined); render(newNode, container); expect(container.innerHTML).toEqual('
    '); }); it('Should remove ref if new one is empty', () => { const node =
    ; expect(node.ref).toEqual(initialFunc); render(node, container); expect(container.innerHTML).toEqual('
    '); const newNode = cloneVNode(node, { ref: null, }); expect(newNode.ref).toEqual(null); // expect(newNode.props.hasOwnProperty('ref')).toBe(false); expect(newNode.props.ref).toBe(undefined); render(newNode, container); expect(container.innerHTML).toEqual('
    '); }); it('Should keep previous ref when new props dont have that property at all', () => { const node =
    ; expect(node.ref).toEqual(initialFunc); render(node, container); expect(container.innerHTML).toEqual('
    '); const newNode = cloneVNode(node, { className: null, }); expect(newNode.ref).toEqual(initialFunc); // expect(newNode.props.hasOwnProperty('className')).toBe(false); expect(newNode.props.className).toBe(undefined); expect(newNode.className).toBe(null); render(newNode, container); expect(container.innerHTML).toEqual('
    '); }); }); describe('without children specified', () => { it('should render children one level deep', () => { interface NameContainerProps { children: InfernoSingleNode[]; } class NameContainer extends Component { public render() { const children = this.props.children.map((c) => cloneVNode(c as VNode, { name: 'Henry', }), ); return {children}; } } const NameViewer = () => { return (
    A child that should render after the clone
    A child that should render after the clone
    ); }; render(, container); expect(container.innerHTML) .toBe(`
    A child that should render after \ the clone
    A child that should render after the clone
    `); }); it('should render children two levels deep', () => { const items = [ { name: 'Mike Brady' }, { name: 'Carol Brady' }, { name: 'Greg Brady' }, { name: 'Marcia Brady' }, ]; const items2 = [{ age: 28 }, { age: 26 }, { age: 16 }, { age: 15 }]; interface Wrapper1Props { children: InfernoSingleNode; } class Wrapper1 extends Component { public render() { const children = cloneVNode(this.props.children as VNode, { items }); return
    {children}
    ; } } interface Wrapper2Props { items2: Array<{ age: number }>; items: Array<{ name: string }>; children: NormalItem[]; } class Wrapper2 extends Component { public render() { const children = this.props.children.map((c) => { return cloneVNode(c as any, { age: (c.props && c.props.index) != null ? this.props.items2[c.props.index as number].age : 'default-age', name: (c.props && c.props.index) != null ? this.props.items[c.props.index as number].name : 'default-name', propsIndex: c.props && c.props.index, }); }); return
    {children}
    ; } } interface ItemProps { index?: number; name?: string; age?: string; } class Item extends Component { public render() { return ( item {this.props.name} - age: {this.props.age} ); } } interface NormalItemProps { index?: number; name?: string; age?: string; } class NormalItem extends Component { public render() { return ( Normal Item {this.props.name} - age: {this.props.age} ); } } class App extends Component { public render() { const content: NormalItem[] = [, ]; for (const _d of items) { const idx = items.indexOf(_d); content.push(); } return ( {content} ); } } render(, container); expect(container.innerHTML) .toBe(`
    Normal Item default-name \ - age: default-ageNormal Item default-name - age: default-ageitem Mike Brady - age: \ 28item Carol Brady - age: 26item Greg Brady - age: 16item Marcia Brady \ - age: 15
    `); }); }); it('Should not clone all children of Component', () => { // React fiddle of cloneElement https://jsfiddle.net/5wh3cfn0/ class Hello extends Component<{ name?: string }> { public render() { return
    Hello {this.props.name}
    ; } } const node1 = ( 1 ); const cloned1 = cloneVNode(node1); expect(node1.props.children).toBe(cloned1.props.children); expect(node1).not.toBe(cloned1); expect(node1.props).not.toBe(cloned1.props); const node = (
    {[null,
    1
    , [
    1
    ,
    2
    ]]}
    ); const cloned = cloneVNode(node); // Following assertion depends on if Inferno-compat is used or not // expect(node.props.children).toBe(cloned.props.children); expect(node).not.toBe(cloned); expect(node.props).not.toBe(cloned.props); }); it('Should be possible to clone fragment', () => { const frag = ( 1 2 ); render(cloneVNode(frag), container); expect(container.innerHTML).toBe('12'); const firstChild = container.firstChild; render(cloneVNode(frag), container); expect(firstChild).toBe(container.firstChild); expect(container.innerHTML).toBe('12'); }); }); ================================================ FILE: packages/inferno-clone-vnode/index.cjs ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./dist/index.min.cjs'); } else { module.exports = require('./dist/index.cjs'); } ================================================ FILE: packages/inferno-clone-vnode/package.json ================================================ { "name": "inferno-clone-vnode", "version": "9.0.11", "license": "MIT", "type": "module", "description": "provides helper function to clone Inferno's vNodes", "author": { "name": "Sampo Kivistö", "email": "sampo.kivisto@live.fi", "url": "https://github.com/havunen" }, "repository": { "type": "git", "url": "https://github.com/infernojs/inferno.git", "directory": "packages/inferno-clone-vnode" }, "bugs": { "url": "https://github.com/infernojs/inferno/issues" }, "homepage": "https://github.com/infernojs/inferno#readme", "files": [ "index.cjs", "dist/", "README.md", "package.json" ], "exports": { ".": { "import": "./dist/index.mjs", "require": "./index.cjs", "types": "./dist/index.d.ts" }, "./dist/index.dev.mjs": "./dist/index.dev.mjs", "./dist/index.mjs": "./dist/index.mjs" }, "module": "dist/index.mjs", "dev:module": "dist/index.dev.mjs", "typings": "dist/index.d.ts", "keywords": [ "babel", "react", "inferno", "framework", "interfaces", "user interfaces", "class", "vdom" ], "dependencies": { "inferno": "9.0.11" }, "devDependencies": { "inferno-shared": "9.0.11", "inferno-vnode-flags": "9.0.11" }, "rollup": { "bundledDependencies": [ "inferno-shared" ], "moduleName": "Inferno" } } ================================================ FILE: packages/inferno-clone-vnode/src/index.ts ================================================ import { createComponentVNode, createFragment, createTextVNode, createVNode, EMPTY_OBJ, normalizeProps, type VNode, } from 'inferno'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; /* directClone is preferred over cloneVNode and used internally also. This function makes Inferno backwards compatible. And can be tree-shaked by modern bundlers */ /** * Clones given virtual node by creating new instance of it * @param {VNode} vNodeToClone virtual node to be cloned * @param {Props=} props additional props for new virtual node * @param {...*} childArgs new children for new virtual node * @returns {VNode} new virtual node */ export function cloneVNode(vNodeToClone: VNode, props?, ...childArgs): VNode { const flags = vNodeToClone.flags; let children = flags & VNodeFlags.Component ? vNodeToClone.props?.children : vNodeToClone.children; const childLen = childArgs.length; let className = vNodeToClone.className; let key = vNodeToClone.key; let ref = vNodeToClone.ref; if (props) { if (props.className !== void 0) { className = props.className as string; } if (props.ref !== void 0) { ref = props.ref; } if (props.key !== void 0) { key = props.key; } if (props.children !== void 0) { children = props.children; } } else { props = {}; } if (childLen === 1) { children = childArgs[0]; } else if (childLen > 1) { children = []; for (let i = 0; i < childLen; i++) { children.push(childArgs[i]); } } props.children = children; if (flags & VNodeFlags.Component) { return createComponentVNode( flags, vNodeToClone.type, !vNodeToClone.props && !props ? EMPTY_OBJ : { ...vNodeToClone.props, ...props }, key, ref, ); } if (flags & VNodeFlags.Text) { return createTextVNode(children); } if (flags & VNodeFlags.Fragment) { return createFragment( childLen === 1 ? [children] : children, ChildFlags.UnknownChildren, key, ); } return normalizeProps( createVNode( flags, vNodeToClone.type, className, null, ChildFlags.HasInvalidChildren, { ...vNodeToClone.props, ...props }, key, ref, ), ); } ================================================ FILE: packages/inferno-compat/README.md ================================================ # inferno-compat This module is a compatibility layer that makes React-based modules work with Inferno, without any code changes. It provides the same exports as `react` and `react-dom`, meaning you can use your build tool of choice to drop it in where React is being depended on. Do note however, as with almost all compatability layer libraries, there is an associated cost of extra overhead. As such, you should never expect native Inferno performance when using `inferno-compat`. You might not always need the `inferno-compat` package. Just the alias to Inferno might be enough. **Inferno-compat** adds the following features: As in React: - ClassName is copied to props - Children is copied to props (for html vNodes too) - styles are converted from camelCase to hyphen-case runtime. You can turn off this feature by setting: `options.reactStyles = false;` - Empty props are always created for element vNodes - You can create Components based on string - `findDOMNOde` -method is available - Iterable data structures are supported - `Children.(map/forEach/count/only/toArray)` - methods are available - Html properties are transformed to inferno compatible format - Some form events (fe: onChange) are transformed to native alternative - PureComponent is available - `unstable_renderSubtreeIntoContainer` - method is available - `DOM` - factory is available - `unmountComponentAtNode` - method is available its same as "render(null, container)" ## How to install? Inferno-compat does not automatically install all its features. For example: If you need createElement support you should also install `inferno-create-element`. All packages: ``` npm install --save inferno npm install --save inferno-compat npm install --save inferno-clone-vnode npm install --save inferno-create-element ``` If you use React/lib/ReactCSSTransitionGroup.ts install `inferno-transition-group` package. If you use React/lib/ReactCSSTransitionGroup.ts install `rc-css-transition-group-modern` package. ## What is currently supported? ### `react` - `React.createElement` - `React.cloneElement` - `React.Component` - `React.PureComponent` - `React.PropTypes` - `React.Children` - `React.isValidElement` Note: Inferno will not currently validate `PropTypes` ### `react-dom` - `ReactDOM.render` - `ReactDOM.hydrate` - `ReactDOM.unmountComponentAtNode` - `ReactDOM.findDOMNode` - `React.DOM` - `React.createFactory` ## Usage with Webpack Using `inferno-compat` with Webpack is easy. All you have to do is add an alias for `react` and `react-dom`: ```js { resolve: { alias: { 'react': 'inferno-compat', 'react-dom': 'inferno-compat' } } } ``` ## Usage with Babel Install the Babel plugin for module aliasing: `npm install --save-dev babel-plugin-module-resolver`. Babel can now alias `react` and `react-dom` to `inferno` by adding the following to your `.babelrc` file: ```js { "plugins": [ ["module-resolver", { "root": ["."], "alias": { "react": "inferno-compat", "react-dom": "inferno-compat" } }] ] } ``` ## Usage with Browserify Using `inferno-compat` with Browserify is as simple as installing and configuring [aliasify](http://npm.im/aliasify). First, install it: `npm install --save-dev aliasify` ... then in your `package.json`, configure aliasify to alias `react` and `react-dom`: ```js { // ... "aliasify": { "aliases": { "react": "inferno-compat", "react-dom": "inferno-compat" } } // ... } ``` ## Once Aliased With the above Webpack or Browserify aliases in place, existing React modules should work nicely: ```js import React from 'react'; import ReactDOM from 'react-dom'; class Foo extends React.Component { propTypes = { a: React.PropTypes.string.isRequired, }; render() { let { a, b, children } = this.props; return
    {children}
    ; } } ReactDOM.render(test, document.getElementById('app')); ``` ================================================ FILE: packages/inferno-compat/__tests__/ReactChildren.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; describe('ReactChildren', function () { let ReactChildren; beforeEach(function () { ReactChildren = React.Children; }); it('should support identity for simple', function () { const callback = jasmine.createSpy().and.callFake(function (kid, index) { return kid; }); const simpleKid = ; // First pass children into a component to fully simulate what happens when // using structures that arrive from transforms. const instance = (
    ); ReactChildren.forEach(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); callback.calls.reset(); ReactChildren.map(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); // expect(mappedChildren[0]).toEqual(); }); it('should treat single arrayless child as being in array', function () { const callback = jasmine.createSpy().and.callFake(function (kid, index) { return kid; }); const simpleKid = ; const instance = (
    ); ReactChildren.forEach(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); callback.calls.reset(); ReactChildren.map(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); // expect(mappedChildren[0]).toEqual(); }); it('should treat single child in array as expected', function () { const callback = jasmine.createSpy().and.callFake(function (kid, index) { return kid; }); const simpleKid = ; // Use optimization flag to avoid normalization to keep flags same... for below assertion const instance =
    {[]}
    ; ReactChildren.forEach(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); // Third param is the array callback.calls.reset(); ReactChildren.map(instance.children, callback); expect(callback).toHaveBeenCalledWith(simpleKid, 0, jasmine.any(Array)); // expect(mappedChildren[0]).toEqual(); // Flags dont match because its implementation detail in Inferno }); it('should pass key to returned component', function () { const mapFn = function (kid, index) { return
    {kid}
    ; }; const instance = (
    ); const mappedChildren = ReactChildren.map(instance.children, mapFn); expect(ReactChildren.count(mappedChildren)).toBe(1); expect(mappedChildren[0].children.key).toBe('simple'); }); it('should invoke callback with the right context', function () { let lastContext; const callback = function (kid, index) { lastContext = this; return this; }; const scopeTester = { fooScope: 'barScope' }; const simpleKid = ; const instance =
    {simpleKid}
    ; ReactChildren.forEach(instance.children, callback, scopeTester); expect(lastContext).toEqual(scopeTester); const mappedChildren = ReactChildren.map( instance.children, callback, scopeTester, ); expect(ReactChildren.count(mappedChildren)).toBe(1); expect(mappedChildren[0]).toEqual(scopeTester); }); it('ForEach should not fail if null children is provided', () => { expect(React.Children.forEach(null, null, null)).toBe(undefined); }); it('should not throw if key provided is a dupe with array key', function () { const zero =
    ; const one =
    ; const mapFn = function () { return null; }; const instance = (
    {zero} {one}
    ); expect(function () { ReactChildren.map(instance.props.children, mapFn); }).not.toThrow(); }); it('should return 0 for null children', function () { const numberOfChildren = ReactChildren.count(null); expect(numberOfChildren).toBe(0); }); it('should return 0 for undefined children', function () { const numberOfChildren = ReactChildren.count(undefined); expect(numberOfChildren).toBe(0); }); it('should return 1 for single child', function () { const simpleKid = ; const instance =
    {simpleKid}
    ; const numberOfChildren = ReactChildren.count(instance.children); expect(numberOfChildren).toBe(1); }); it('should count the number of children in flat structure', function () { const zero =
    ; const one = null; const two =
    ; const three = null; const four =
    ; const instance = (
    {zero} {one} {two} {three} {four}
    ); const numberOfChildren = ReactChildren.count(instance.children); expect(numberOfChildren).toBe(3); // Nulls are removed in Inferno }); // it('should count the number of children in nested structure', function() { // var zero =
    ; // var one = null; // var two =
    ; // var three = null; // var four =
    ; // var five =
    ; // // five is placed into a JS object with a key that is joined to the // // component key attribute. // // Precedence is as follows: // // 1. If grouped in an Object, the object key combined with `key` prop // // 2. If grouped in an Array, the `key` prop, falling back to array index // var instance = ( //
    { // [ // ReactFragment.create({ // firstHalfKey: [zero, one, two], // secondHalfKey: [three, four], // keyFive: five, // }), // null, // ] // }
    // ); // var numberOfChildren = ReactChildren.count(instance.props.children); // expect(numberOfChildren).toBe(5); // }); it('should flatten children to an array', function () { expect(ReactChildren.toArray(undefined)).toEqual([]); expect(ReactChildren.toArray(null)).toEqual([]); expect(ReactChildren.toArray(
    ).length).toBe(1); expect(ReactChildren.toArray([
    ]).length).toBe(1); expect(ReactChildren.toArray(
    )[0].key).toBe( ReactChildren.toArray([
    ])[0].key, ); const flattened = ReactChildren.toArray([ [
    ,
    ,
    ], [
    ,
    ,
    ], ]); expect(flattened.length).toBe(6); expect(flattened[1].key).toContain('banana'); expect(flattened[3].key).toContain('banana'); // Inferno will do this when it normalizes children // // expect(flattened[1].key).not.toBe(flattened[3].key); // // var reversed = ReactChildren.toArray([ // [
    ,
    ,
    ], // [
    ,
    ,
    ], // ]); // expect(flattened[0].key).toBe(reversed[2].key); // expect(flattened[1].key).toBe(reversed[1].key); // expect(flattened[2].key).toBe(reversed[0].key); // expect(flattened[3].key).toBe(reversed[5].key); // expect(flattened[4].key).toBe(reversed[4].key); // expect(flattened[5].key).toBe(reversed[3].key); // // // null/undefined/bool are all omitted // expect(ReactChildren.toArray([1, 'two', null, undefined, true])).toEqual( // [1, 'two'] // ); }); it('should normalize children in forEach', function () { const children = []; const callback = function (child) { children.push(child); }; ReactChildren.forEach([false, true, undefined], callback); expect(children).toEqual([null, null, null]); }); }); ================================================ FILE: packages/inferno-compat/__tests__/ReactComponent.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; import { createComponentVNode } from 'inferno'; import { Wrapper } from 'inferno-test-utils'; import { VNodeFlags } from 'inferno-vnode-flags'; const ReactDOM = React; let mocks; describe('ReactComponent', function () { let container; function renderIntoDocument(input) { return React.render( createComponentVNode(VNodeFlags.ComponentClass, Wrapper, { children: input, }), container, ); } beforeEach(() => { mocks = { getMockFunction: function () { return jasmine.createSpy(); }, }; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { React.render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('should throw on invalid render targets', function () { const container = document.createElement('div'); // jQuery objects are basically arrays; people often pass them in by mistake expect(function () { ReactDOM.render(
    , [container]); }).toThrow(); expect(function () { ReactDOM.render(
    , null); }).toThrow(); }); it('should support new-style refs', function () { const innerObj = {}; const outerObj = {}; class Wrapper extends React.Component { getObject() { return this.props.object; } render() { return
    {this.props.children}
    ; } } let mounted = false; class Component extends React.Component { render() { const inner = ( (this.innerRef = c)} /> ); const outer = ( (this.outerRef = c)}> {inner} ); return outer; } componentDidMount() { expect(this.innerRef.getObject()).toEqual(innerObj); expect(this.outerRef.getObject()).toEqual(outerObj); mounted = true; } } const instance = ; renderIntoDocument(instance); expect(mounted).toBe(true); }); it('fires the callback after a component is rendered', function () { const callback = mocks.getMockFunction(); const container = document.createElement('div'); ReactDOM.render(
    , container, callback); expect(callback.calls.count()).toBe(1); ReactDOM.render(
    , container, callback); expect(callback.calls.count()).toBe(2); ReactDOM.render(, container, callback); expect(callback.calls.count()).toBe(3); }); it('throws usefully when rendering badly-typed elements', function () { // spyOn(console, 'error'); const X = undefined; expect(() => renderIntoDocument()).toThrow(); const Z = {}; expect(() => renderIntoDocument()).toThrow(); // One warning for each element creation // expect(console.error.calls.count()).toBe(3); }); // other it('should pass context to children when not owner', function () { class Parent extends React.Component { render() { return ( ); } } class Child extends React.Component { static childContextTypes = { foo: React.PropTypes.string, }; getChildContext() { return { foo: 'bar', }; } render() { return React.Children.only(this.props.children); } } class Grandchild extends React.Component { contextTypes = { foo: React.PropTypes.string, }; render() { return
    {this.context.foo}
    ; } } const component = renderIntoDocument(); expect(ReactDOM.findDOMNode(component).innerHTML).toBe('bar'); }); }); ================================================ FILE: packages/inferno-compat/__tests__/ReactComponentLifeCycle.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; import { createComponentVNode } from 'inferno'; import { Wrapper } from 'inferno-test-utils'; import { VNodeFlags } from 'inferno-vnode-flags'; const ReactDOM = React; const clone = function (o) { return JSON.parse(JSON.stringify(o)); }; const GET_INIT_STATE_RETURN_VAL = { hasWillMountCompleted: false, hasRenderCompleted: false, hasDidMountCompleted: false, hasWillUnmountCompleted: false, }; const INIT_RENDER_STATE = { hasWillMountCompleted: true, hasRenderCompleted: false, hasDidMountCompleted: false, hasWillUnmountCompleted: false, }; const DID_MOUNT_STATE = { hasWillMountCompleted: true, hasRenderCompleted: true, hasDidMountCompleted: false, hasWillUnmountCompleted: false, }; const NEXT_RENDER_STATE = { hasWillMountCompleted: true, hasRenderCompleted: true, hasDidMountCompleted: true, hasWillUnmountCompleted: false, }; const WILL_UNMOUNT_STATE = { hasWillMountCompleted: true, hasDidMountCompleted: true, hasRenderCompleted: true, hasWillUnmountCompleted: false, }; const POST_WILL_UNMOUNT_STATE = { hasWillMountCompleted: true, hasDidMountCompleted: true, hasRenderCompleted: true, hasWillUnmountCompleted: true, }; /** * Every React component is in one of these life cycles. */ const ComponentLifeCycle = { /** * Mounted components have a DOM node representation and are capable of * receiving new props. */ MOUNTED: 'MOUNTED', /** * Unmounted components are inactive and cannot receive new props. */ UNMOUNTED: 'UNMOUNTED', }; describe('ReactComponentLifeCycle', function () { let container; function renderIntoDocument(input) { return React.render( createComponentVNode(VNodeFlags.ComponentClass, Wrapper, { children: input, }), container, ); } beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { React.render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('should not reuse an instance when it has been unmounted', function () { const container = document.createElement('div'); class StatefulComponent extends React.Component { constructor(props) { super(props); this.state = {}; } render() { return
    ; } } const element = ; const firstInstance = ReactDOM.render(element, container); ReactDOM.unmountComponentAtNode(container); const secondInstance = ReactDOM.render(element, container); expect(firstInstance).not.toBe(secondInstance); }); /** * If a state update triggers rerendering that in turn fires an onDOMReady, * that second onDOMReady should not fail. */ it('it should fire onDOMReady when already in onDOMReady', function (done) { const _testJournal = []; class Child extends React.Component { componentDidMount() { _testJournal.push('Child:onDOMReady'); } render() { return
    ; } } class SwitcherParent extends React.Component { constructor(props) { super(props); _testJournal.push('SwitcherParent:ctr'); this.state = { showHasOnDOMReadyComponent: false }; } componentDidMount() { _testJournal.push('SwitcherParent:onDOMReady'); this.switchIt(); } switchIt() { this.setState({ showHasOnDOMReadyComponent: true }); } render() { return (
    {this.state.showHasOnDOMReadyComponent ? :
    }
    ); } } const instance = ; renderIntoDocument(instance); setTimeout(() => { expect(_testJournal).toEqual([ 'SwitcherParent:ctr', 'SwitcherParent:onDOMReady', 'Child:onDOMReady', ]); done(); }, 20); }); it('should allow update state inside of componentWillMount', function () { class StatefulComponent extends React.Component { componentWillMount() { this.setState({ stateField: 'something' }); } render() { return
    ; } } const instance = ; expect(function () { renderIntoDocument(instance); }).not.toThrow(); }); it('should not throw when updating an auxiliary component', function () { class Tooltip extends React.Component { render() { return
    {this.props.children}
    ; } componentDidMount() { this.container = document.createElement('div'); this.updateTooltip(); } componentDidUpdate() { this.updateTooltip(); } updateTooltip() { // Even though this.props.tooltip has an owner, updating it shouldn't // throw here because it's mounted as a root component ReactDOM.render(this.props.tooltip, this.container); } } class Component extends React.Component { render() { return ( {this.props.tooltipText}
    }> {this.props.text} ); } } const container = document.createElement('div'); ReactDOM.render(, container); // Since `instance` is a root component, we can set its props. This also // makes Tooltip rerender the tooltip component, which shouldn't throw. ReactDOM.render(, container); }); it('should allow state updates in componentDidMount', function (done) { /** * calls setState in an componentDidMount. */ class SetStateInComponentDidMount extends React.Component { constructor(props) { super(props); this.state = { stateField: this.props.valueToUseInitially, }; } componentDidMount() { this.setState({ stateField: this.props.valueToUseInOnDOMReady }); } render() { return
    ; } } let instance = ( ); instance = renderIntoDocument(instance); setTimeout(() => { expect(instance.$LI.children.state.stateField).toBe('goodbye'); done(); }, 25); }); it('should call nested lifecycle methods in the right order', function () { let log; const logger = function (msg) { return function () { // return true for shouldComponentUpdate log.push(msg); return true; }; }; class Outer extends React.Component { render() { return (
    ); } componentWillMount() { log.push('outer componentWillMount'); } componentDidMount() { log.push('outer componentDidMount'); } componentWillReceiveProps() { log.push('outer componentWillReceiveProps'); } shouldComponentUpdate() { log.push('outer shouldComponentUpdate'); return true; } componentWillUpdate() { log.push('outer componentWillUpdate'); } componentDidUpdate() { log.push('outer componentDidUpdate'); } componentWillUnmount() { log.push('outer componentWillUnmount'); } } class Inner extends React.Component { render() { return {this.props.x}; } componentWillMount() { log.push('inner componentWillMount'); } componentDidMount() { log.push('inner componentDidMount'); } componentWillReceiveProps() { log.push('inner componentWillReceiveProps'); } shouldComponentUpdate() { log.push('inner shouldComponentUpdate'); return true; } componentWillUpdate() { log.push('inner componentWillUpdate'); } componentDidUpdate() { log.push('inner componentDidUpdate'); } componentWillUnmount() { log.push('inner componentWillUnmount'); } } const container = document.createElement('div'); log = []; ReactDOM.render(, container); expect(log).toEqual([ 'outer componentWillMount', 'inner componentWillMount', 'inner componentDidMount', 'outer componentDidMount', ]); log = []; ReactDOM.render(, container); expect(log).toEqual([ 'outer componentWillReceiveProps', 'outer shouldComponentUpdate', 'outer componentWillUpdate', 'inner componentWillReceiveProps', 'inner shouldComponentUpdate', 'inner componentWillUpdate', 'inner componentDidUpdate', 'outer componentDidUpdate', ]); log = []; ReactDOM.unmountComponentAtNode(container); expect(log).toEqual([ 'outer componentWillUnmount', 'inner componentWillUnmount', ]); }); }); ================================================ FILE: packages/inferno-compat/__tests__/ReactCompositeComponentState.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; const ReactDOM = React; describe('ReactCompositeComponent-state', function () { it('should batch unmounts', function () { let outer; class Inner extends React.Component { render() { return
    ; } componentWillUnmount() { // This should get silently ignored (maybe with a warning), but it // shouldn't break React. outer.setState({ showInner: false }); } } class Outer extends React.Component { constructor(props) { super(props); this.state = { showInner: true }; } render() { return
    {this.state.showInner && }
    ; } } const container = document.createElement('div'); outer = ReactDOM.render(, container); expect(() => { ReactDOM.unmountComponentAtNode(container); }).not.toThrow(); }); }); ================================================ FILE: packages/inferno-compat/__tests__/ReactDOM.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; import { createComponentVNode } from 'inferno'; import { Wrapper } from 'inferno-test-utils'; import { VNodeFlags } from 'inferno-vnode-flags'; const ReactDOM = React; const div = React.createFactory('div'); describe('ReactDOM', function () { let container; function renderIntoDocument(input) { return React.render( createComponentVNode(VNodeFlags.ComponentClass, Wrapper, { children: input, }), container, ); } beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { React.render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('allows a DOM element to be used with a string', function () { const element = React.createElement('div', { className: 'foo' }); const instance = renderIntoDocument(element); expect(ReactDOM.findDOMNode(instance).tagName).toBe('DIV'); }); it('should allow children to be passed as an argument', function () { const argDiv = renderIntoDocument(div(null, 'child')); const argNode = ReactDOM.findDOMNode(argDiv); expect(argNode.innerHTML).toBe('child'); }); it('should overwrite props.children with children argument', function () { const conflictDiv = renderIntoDocument( div({ children: 'fakechild' }, 'child'), ); const conflictNode = ReactDOM.findDOMNode(conflictDiv); expect(conflictNode.innerHTML).toBe('child'); }); /** * We need to make sure that updates occur to the actual node that's in the * DOM, instead of a stale cache. */ it('should purge the DOM cache when removing nodes', function () { let myDiv = renderIntoDocument(
    , ); // Warm the cache with theDog myDiv = renderIntoDocument(
    , ); // Remove theDog - this should purge the cache myDiv = renderIntoDocument(
    , ); // Now, put theDog back. It's now a different DOM node. myDiv = renderIntoDocument(
    , ); // Change the className of theDog. It will use the same element myDiv = renderIntoDocument(
    , ); const root = ReactDOM.findDOMNode(myDiv); const dog = root.childNodes[0]; expect(dog.className).toBe('bigdog'); }); it('allow React.DOM factories to be called without warnings', function () { spyOn(console, 'error'); const element = div(); expect(element.type).toBe('div'); expect(console.error.calls.count()).toBe(0); }); }); ================================================ FILE: packages/inferno-compat/__tests__/ReactDOMComponent.spec.jsx ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ import React from 'inferno-compat'; const ReactDOM = React; if (global?.usingJSDOM) { describe('ReactDOMComponent', function () { describe('updateDOM', function () { it('should handle className', function () { const container = document.createElement('div'); ReactDOM.render(
    , container); ReactDOM.render(
    , container); expect(container.firstChild.className).toEqual('foo'); ReactDOM.render(
    , container); expect(container.firstChild.className).toEqual('bar'); ReactDOM.render(
    , container); expect(container.firstChild.className).toEqual(''); }); it('should gracefully handle various style value types', function () { const container = document.createElement('div'); ReactDOM.render(
    , container); const stubStyle = container.firstChild.style; // set initial style const setup = { display: 'block', left: '1px', top: '2px', 'font-family': 'Arial', }; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual('block'); expect(stubStyle.left).toEqual('1px'); expect(stubStyle.fontFamily).toEqual('Arial'); // reset the style to their default state const reset = { display: '', left: null, top: null, fontFamily: null }; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual(''); expect(stubStyle.left).toEqual(''); expect(stubStyle.top).toEqual(''); expect(stubStyle.fontFamily).toEqual(''); }); it('should update styles if initially null', function () { let styles = null; const container = document.createElement('div'); ReactDOM.render(
    , container); const stubStyle = container.firstChild.style; styles = { display: 'block' }; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual('block'); }); it('should update styles if updated to null multiple times', function () { let styles = null; const container = document.createElement('div'); ReactDOM.render(
    , container); styles = { display: 'block' }; const stubStyle = container.firstChild.style; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual('block'); ReactDOM.render(
    , container); expect(stubStyle.display).toEqual(''); ReactDOM.render(
    , container); expect(stubStyle.display).toEqual('block'); ReactDOM.render(
    , container); expect(stubStyle.display).toEqual(''); }); it('should remove attributes', function () { const container = document.createElement('div'); ReactDOM.render(, container); expect(container.firstChild.hasAttribute('height')).toBe(true); ReactDOM.render(, container); expect(container.firstChild.hasAttribute('height')).toBe(false); }); it('should remove properties', function () { const container = document.createElement('div'); ReactDOM.render(
    , container); expect(container.firstChild.className).toEqual('monkey'); ReactDOM.render(
    , container); expect(container.firstChild.className).toEqual(''); }); it('should clear a single style prop when changing `style`', function () { let styles = { display: 'none', color: 'red' }; const container = document.createElement('div'); ReactDOM.render(
    , container); const stubStyle = container.firstChild.style; styles = { color: 'green' }; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual(''); expect(stubStyle.color).toEqual('green'); }); it('should update arbitrary attributes for tags containing dashes', function () { const container = document.createElement('div'); const beforeUpdate = React.createElement('x-foo-component', {}, null); ReactDOM.render(beforeUpdate, container); const afterUpdate = ; ReactDOM.render(afterUpdate, container); expect(container.childNodes[0].getAttribute('myattr')).toBe('myval'); }); it('should clear all the styles when removing `style`', function () { const styles = { display: 'none', color: 'red' }; const container = document.createElement('div'); ReactDOM.render(
    , container); const stubStyle = container.firstChild.style; ReactDOM.render(
    , container); expect(stubStyle.display).toEqual(''); expect(stubStyle.color).toEqual(''); }); it('should update styles when `style` changes from null to object', function () { const container = document.createElement('div'); const styles = { color: 'red' }; ReactDOM.render(
    , container); ReactDOM.render(
    , container); ReactDOM.render(
    , container); const stubStyle = container.firstChild.style; expect(stubStyle.color).toEqual('red'); }); it('should empty element when removing innerHTML', function () { const container = document.createElement('div'); ReactDOM.render(
    , container, ); expect(container.firstChild.innerHTML).toEqual(':)'); ReactDOM.render(
    , container); expect(container.firstChild.innerHTML).toEqual(''); }); it('should transition from string content to innerHTML', function () { const container = document.createElement('div'); ReactDOM.render(
    hello
    , container); expect(container.firstChild.innerHTML).toEqual('hello'); ReactDOM.render(
    , container, ); expect(container.firstChild.innerHTML).toEqual('goodbye'); }); it('should transition from innerHTML to string content', function () { const container = document.createElement('div'); ReactDOM.render(
    , container, ); expect(container.firstChild.innerHTML).toEqual('bonjour'); ReactDOM.render(
    adieu
    , container); expect(container.firstChild.innerHTML).toEqual('adieu'); }); it('should transition from innerHTML to children in nested el', function () { const container = document.createElement('div'); ReactDOM.render(
    , container, ); expect(container.textContent).toEqual('bonjour'); ReactDOM.render(
    adieu
    , container, ); expect(container.textContent).toEqual('adieu'); }); it('should transition from children to innerHTML in nested el', function () { const container = document.createElement('div'); ReactDOM.render(
    adieu
    , container, ); expect(container.textContent).toEqual('adieu'); ReactDOM.render(
    , container, ); expect(container.textContent).toEqual('bonjour'); }); it('should ignore attribute whitelist for elements with the "is: attribute', function () { const container = document.createElement('div'); ReactDOM.render(
    ', ); }); it('Second render (update with state change)', (done) => { render(tpl79713834(TEST), container); render(tpl79713834(TEST), container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '', ); done(); }, 25); }); }); describe('should render a component with a list of divs', () => { const BaseView = function (v0, v1) { return createElement( 'div', { class: 'login-view' }, createElement('button', { onclick: v0 }, 'ADD'), createElement('br', null), v1, ); }; const Looper = function (v0) { return createElement('div', null, createElement('h1', null, v0)); }; const starter = function (v0) { return createElement(v0, null); }; class SomeError extends Component { constructor(props) { super(props); this.state = { list: ['SS', 'SS1'], }; } render() { /* eslint new-cap:0 */ return BaseView( undefined, function () { return this.state.list.map(function (result) { return Looper(result); }); }.call(this), ); } } it('Initial render (creation)', () => { render(starter(SomeError), container); expect(container.innerHTML).toBe( '', ); render(starter(SomeError), container); expect(container.innerHTML).toBe( '', ); }); }); describe('should render a component with a list of text nodes', () => { const root = function (children) { return createElement('div', null, children); }; const header = function (children) { return createElement('div', null, children); }; const view = function (state) { return root([state ? header(['Foo']) : header(['Bar', 'Qux'])]); }; it('Initial render (creation)', () => { render(view(true), container); expect(container.innerHTML).toBe('
    Foo
    '); }); it('Second render (update)', () => { render(view(true), container); render(view(false), container); expect(container.innerHTML).toBe('
    BarQux
    '); }); }); describe('SetState function callback', () => { it('Should have state, props, and context as parameters', (done) => { function checkParams(state, props, context) { expect(state).toEqual({ btnstate: 'btnstate' }); expect(props).toEqual({ buttonProp: 'magic', children: 'btn' }); expect(context).toEqual({ color: 'purple' }); done(); } class Button extends Component { constructor(props) { super(props); this.state = { btnstate: 'btnstate', }; } click() { this.setState(checkParams); } render() { return createElement( 'button', { onClick: this.click.bind(this), style: { background: this.context.color, }, }, this.props.children, ); } } class Message extends Component<{ text: string }> { render() { return createElement('div', null, [ this.props.text, createElement(Button, { buttonProp: 'magic' }, 'btn'), ]); } } class MessageList extends Component<{ messages: { text: string }[] }> { getChildContext() { return { color: 'purple' }; } render() { const children = this.props.messages.map(function (message) { return createElement(Message, { text: message.text }); }); return createElement('div', null, children); } } render( createElement(MessageList, { messages: [{ text: 'eka' }, { text: 'toka' }], }), container, ); container.querySelector('button').click(); }); }); } }); ================================================ FILE: packages/inferno-create-element/__tests__/components1.spec.tsx ================================================ import { Component, render } from 'inferno'; describe('Components 1 (JSX)', () => { let container; let attachedListener: () => void = () => {}; let renderedName: string | null = null; interface InnerProps { onClick: () => void; name: null | string; } class Inner extends Component { render() { attachedListener = this.props.onClick; renderedName = this.props.name; return
    ; } } beforeEach(function () { attachedListener = () => {}; renderedName = null; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); document.body.removeChild(container); }); class BasicComponent1 extends Component<{ name: any; title: any }> { render() { return (
    The title is {this.props.title}
    ); } } it('should render a basic component jsx', () => { render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is abc
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is abc
    ', ); const attrs = { title: 'abc', name: 'basic-render2', foo: 'bar' }; // JSX Spread Attribute render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is abc
    ', ); }); class BasicComponent1b extends Component<{ isChecked: boolean | null; title: string; }> { render() { return (
    ); } } it('should render a basic component with inputs', () => { render(
    , container, ); expect(container.innerHTML).toBe( '
    ', ); expect(container.querySelector('input').checked).toBe(true); render(
    , container, ); expect(container.innerHTML).toBe( '
    ', ); expect(container.querySelector('input').checked).toBe(false); render(
    , container, ); render(
    , container); render(
    , container, ); expect(container.querySelector('input').checked).toBe(true); }); it('should render a basic component and remove property if null', () => { render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is abc
    ', ); render(
    , container); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is Hello, World!
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is 123
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is abc
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is 123
    ', ); }); it('should render a basic root component', () => { render(, container); expect(container.firstChild.getAttribute('class')).toBe('basic'); render(, container); expect(container.firstChild.getAttribute('class')).toBe('basic'); render(, container); expect(container.innerHTML).toBe( '
    The title is 123
    ', ); }); class BasicComponent2 extends Component<{ name: string; title: string }> { render() { return (
    The title is {this.props.title} {this.props.children}
    ); } } it('should render a basic component with children', () => { render(
    Im a child
    , container, ); expect(container.innerHTML).toBe( '
    The title is abcIm a child
    ', ); render(
    Im a child
    , container, ); expect(container.innerHTML).toBe( '
    The title is 123Im a child
    ', ); }); it('should render multiple components', () => { render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is component 1
    ' + '
    The title is component 2
    ', ); render(
    , container, ); expect(container.innerHTML).toBe( '
    The title is component 1
    ', ); }); class BasicComponent3 extends Component<{ title?: string; styles?: any }> { render() { return (
    The title is {this.props.title}
    ); } } it('should render a basic component with styling', () => { render( , container, ); expect(container.innerHTML).toBe( '
    The title is styled!
    ', ); render(, container); render( , container, ); expect(container.innerHTML).toBe( '
    The title is styled (again)!
    ', ); }); it('should render a basic component and remove styling', () => { render( , container, ); expect(container.innerHTML).toBe( '
    The title is styled!
    ', ); render( , container, ); expect([null, '']).toContain(container.firstChild.getAttribute('style')); expect(container.firstChild.tagName).toEqual('DIV'); expect(container.firstChild.firstChild.innerHTML).toEqual( 'The title is styles are removed!', ); }); interface SuperState { organizations: { name: string; key: string }[]; } class SuperComponent extends Component { state: SuperState; constructor(props) { super(props); this.state = { organizations: [ { name: 'test1', key: '1' }, { name: 'test2', key: '2' }, { name: 'test3', key: '3' }, { name: 'test4', key: '4' }, { name: 'test5', key: '5' }, { name: 'test6', key: '6' }, ], }; } render() { return ( ); } } it('should render a basic component with a list of values from state', () => { render(, container); expect(container.innerHTML).toBe( '', ); }); it('should render a basic component with an element and components as children', () => { class Navbar extends Component { render() { return (
    • Nav1
    ); } } class Main extends Component { render() { return (
    ); } } render(
    , container); }); function test(element, expectedTag, expectedClassName, callback) { render(element, container, () => { expect(container.firstChild).not.toBe(null); expect(container.firstChild.tagName).toBe(expectedTag); expect(container.firstChild.className).toBe(expectedClassName); callback(); }); } it('should only render once when setting state in componentWillMount', function (done) { let renderCount = 0; interface FooState { bar: string | null; } class Foo extends Component<{ initialValue: string | null }, FooState> { state: FooState; constructor(props) { super(props); this.state = { bar: props.initialValue }; } componentWillMount() { this.setState({ bar: 'bar' }); } render() { renderCount++; return ; } } test(, 'SPAN', 'bar', () => { test(, 'SPAN', 'bar', () => { expect(renderCount).toBe(2); done(); }); }); }); it('should render with null in the initial state property', function (done) { class Foo extends Component { constructor(props) { super(props); this.state = null; } render() { return ; } } test(, 'SPAN', '', done); }); it('should setState through an event handler', (done) => { interface FooState { bar: string | null; } class Foo extends Component<{ initialValue: string | null }, FooState> { state: FooState; constructor(props) { super(props); this.state = { bar: props.initialValue }; } handleClick() { this.setState({ bar: 'bar' }); } render() { return ( ); } } test(, 'DIV', 'foo', () => { expect(renderedName).toBe('foo'); attachedListener(); setTimeout(() => { expect(renderedName).toBe('bar'); done(); }, 10); }); }); it('should render using forceUpdate even when there is no state', (done) => { class Foo extends Component<{ initialValue: string | null }> { private mutativeValue: string; constructor(props) { super(props); this.mutativeValue = props.initialValue; } handleClick() { this.mutativeValue = 'bar'; this.forceUpdate(); } render() { return ( ); } } test(, 'DIV', 'foo', function () { attachedListener(); expect(renderedName).toBe('bar'); done(); }); }); describe('should render a component with a list of children that dynamically update via setState', () => { interface CounterState { count: number; } interface CounterProps { car: string; } class Counter extends Component { state: CounterState; constructor(props) { super(props); this.state = { count: 0, }; this.incrementCount = this.incrementCount.bind(this); } incrementCount() { this.setState({ count: this.state.count + 1, }); } render() { return (

    {this.props.car} {this.state.count}

    ); } } class Wrapper extends Component { constructor(props) { super(props); } render() { return (
    {['Saab', 'Volvo', 'BMW'].map(function (c) { return ; })}
    ); } } it('Initial render (creation)', () => { render(, container); expect(container.innerHTML).toBe( '

    Saab 0

    Volvo 0

    BMW 0

    ', ); }); it('Second render (update) #1', (done) => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    Saab 1

    Volvo 1

    BMW 1

    ', ); done(); }, 25); }); }); describe('should render a component with a conditional state item', () => { interface SomeErrorState { show: boolean; } class SomeError extends Component { state: SomeErrorState; constructor(props) { super(props); this.state = { show: false, }; this.toggle = this.toggle.bind(this); } toggle() { this.setState({ show: !this.state.show, }); } render() { return (

    {function () { if (this.state.show === true) { return

    This is cool!

    ; } else { return

    Not so cool

    ; } }.call(this)}
    ); } } it('Initial render (creation)', () => { render(, container); expect(container.innerHTML).toBe( '', ); }); it('Second render (update with state change) #2', () => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } expect(container.innerHTML).toBe( '', ); }); }); describe('should render a stateless component with a conditional state item', () => { interface TestingState { show: boolean; } const StatelessComponent = (props) =>

    {props.name}

    ; class Testing extends Component { state: TestingState; // @ts-ignore private name: string = 'Kalle'; constructor(props) { super(props); this.state = { show: false, }; this.toggle = this.toggle.bind(this); } toggle() { this.setState({ show: !this.state.show, }); } render() { return (
    {function () { if (this.state.show === true) { return ; } else { return

    Hello folks

    ; } }.call(this)}
    ); } } it('Initial render (creation)', () => { render(null, container); render(, container); expect(container.innerHTML).toBe( '

    Hello folks

    ', ); }); it('Second render (update with state change) #3', (done) => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    Kalle

    ', ); done(); }, 25); }); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/components2b.spec.tsx ================================================ import { Component, render } from 'inferno'; import { createElement } from 'inferno-create-element'; describe('Components 2 (TSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); document.body.removeChild(container); }); describe('recursive component', () => { it('Should be possible to pass props recursively', () => { interface ListProps { data: Array<{ key: string; data: string | Array<{ key: string; data: string }>; }>; } class List extends Component { render() { const children = this.props.data.map((entity) => { const { key, data } = entity; const child = Array.isArray(data) ? ( ) : ( ); return
  • {child}
  • ; }); return
      {children}
    ; } } interface TextProps { data: string; } class Text extends Component { render() { return {this.props.data}; } } const data = [ { key: '0', data: 'Foo' }, { key: '1', data: [ { key: '1/1', data: 'a' }, { key: '1/2', data: 'b' }, ], }, ]; render(, container); expect(container.innerHTML).toBe( '
    • Foo
      • a
      • b
    ', ); }); it('Should be possible to pass props recursively AT BEGINNING (JSX plugin change required)', () => { interface ListProps { data: Array<{ key: string; data: string | Array<{ key: string; data: string }>; }>; } class List extends Component { render() { const children = this.props.data.map((entity) => { const { key, data } = entity; const child = Array.isArray(data) ? ( ) : ( ); return
  • {child}
  • ; }); return
      {children}
    ; } } interface TextProps { data: string; } class Text extends Component { render() { return {this.props.data}; } } const data = [ { key: '0', data: 'Foo' }, { key: '1', data: [ { key: '1/1', data: 'a' }, { key: '1/2', data: 'b' }, ], }, ]; render(, container); expect(container.innerHTML).toBe( '
    • Foo
      • a
      • b
    ', ); }); }); it('Should render (github #117)', (done) => { interface MakeXState { x: boolean; } class MakeX extends Component { state: MakeXState; constructor(props) { super(props); this.state = { x: false }; } componentWillMount() { setTimeout(() => { this.setState({ x: true }); }, 10); } render() { return
    {!this.state.x ? : }
    ; } } class MakeY extends Component { constructor(props) { super(props); } render() { return
    Y
    ; } } interface MakeAState { z: boolean; } class MakeA extends Component { state: MakeAState; constructor(props) { super(props); this.state = { z: false }; } componentWillMount() { setTimeout(() => { this.setState({ z: true }); }, 20); } render() { if (!this.state.z) { return
    A
    ; } return ; } } class MakeB extends Component { constructor(props) { super(props); } render() { return
    B
    ; } } render(, container); setTimeout(function () { done(); }, 50); }); it('Events should propagate between components (github #135)', (done) => { interface LabelProps { text: string; } class Label extends Component { render() { const style = { 'background-color': 'red', padding: '0 20px', fontSize: '40px', }; return {this.props.text}; } } let btnFlag = false; let containerFlag = false; class Button extends Component { onClick(_event) { btnFlag = !btnFlag; } render() { const { text } = this.props; return ( ); } } class Container extends Component { onClick(_event) { containerFlag = !containerFlag; } render() { return (
    ); } } render(, container); expect(btnFlag).toBe(false); expect(containerFlag).toBe(false); const spans = container.querySelectorAll('span'); for (const span of spans) { span.click(); } expect(btnFlag).toBe(true); expect(containerFlag).toBe(true); done(); }); it('Should be possible to stop propagation', (done) => { interface LabelProps { text: string; } class Label extends Component { render() { const style = { 'background-color': 'red', padding: '0 20px', fontSize: '40px', }; return {this.props.text}; } } let btnFlag = false; let containerFlag = false; class Button extends Component { onClick(event) { event.stopPropagation(); btnFlag = !btnFlag; } render() { const { text } = this.props; return ( ); } } class Container extends Component { onClick(_event) { containerFlag = !containerFlag; } render() { return (
    ); } } render(, container); expect(btnFlag).toBe(false); expect(containerFlag).toBe(false); const spans = container.querySelectorAll('span'); for (const span of spans) { span.click(); } expect(btnFlag).toBe(true); expect(containerFlag).toBe(false); done(); }); describe('Inheritance should work', () => { it('Should render div', () => { class A extends Component { constructor(props) { super(props); } } class B extends A { constructor(props) { super(props); } } class C extends B { constructor(props) { super(props); } render() { return
    ; } } render(, container); expect(container.innerHTML).toBe('
    '); }); }); describe('A component rendering a component should work as expected', () => { let forceUpdate; let forceUpdate2; class Bar extends Component { constructor() { super(); forceUpdate = this.forceUpdate.bind(this); } render() { return
    Hello world
    ; } } class Foo extends Component { constructor() { super(); forceUpdate2 = this.forceUpdate.bind(this); } render() { return ; } } it('should render the div correctly', () => { render(, container); expect(container.firstChild.innerHTML).toBe('Hello world'); }); it('should update correctly', () => { render(, container); render(, container); expect(container.firstChild.innerHTML).toBe('Hello world'); }); it('should update correctly via forceUpdate', () => { render(, container); forceUpdate(); forceUpdate2(); render(, container); forceUpdate2(); forceUpdate(); expect(container.firstChild.innerHTML).toBe('Hello world'); }); }); it('Should trigger ref lifecycle after patch', (done) => { let updater; const obj = { fn() {}, }; spyOn(obj, 'fn'); interface BarState { bool: boolean; } class Bar extends Component { state: BarState; constructor(props) { super(props); this.state = { bool: true, }; this.changeDOM = this.changeDOM.bind(this); updater = this.changeDOM; } changeDOM() { this.setState({ bool: !this.state.bool, }); } render() { if (this.state.bool === true) { return
    Hello world
    ; } else { return (
    Hello world2
    ); } } } render(, container); expect(container.innerHTML).toBe('
    Hello world
    '); expect(obj.fn).not.toHaveBeenCalled(); updater(); setTimeout(() => { expect(container.innerHTML).toBe('
    Hello world2
    '); expect(obj.fn).toHaveBeenCalledTimes(1); done(); }, 10); }); describe('Should be able to swap between invalid node and valid node', () => { it('Should be able to swap between invalid node and valid node', () => { let updater; interface BarState { bool: boolean; } class Bar extends Component { state: BarState; constructor(props) { super(props); this.state = { bool: true, }; this.changeDOM = this.changeDOM.bind(this); updater = this.changeDOM; } changeDOM() { this.setState({ bool: !this.state.bool, }); } render() { if (this.state.bool === true) { return null; } else { return
    Rendered!
    ; } } } render(, container); expect(container.innerHTML).toBe(''); updater(); expect(container.innerHTML).toBe('
    Rendered!
    '); updater(); expect(container.innerHTML).toBe(''); updater(); expect(container.innerHTML).toBe('
    Rendered!
    '); updater(); expect(container.innerHTML).toBe(''); updater(); expect(container.innerHTML).toBe('
    Rendered!
    '); }); }); it('Should be able to swap between text node and html node', () => { let updater; interface BarState { bool: boolean; } class Bar extends Component { state: BarState; constructor(props) { super(props); this.state = { bool: true, }; this.changeDOM = this.changeDOM.bind(this); updater = this.changeDOM; } changeDOM() { this.setState({ bool: !this.state.bool, }); } render() { return (
    {this.state.bool ? span : 'text'}
    div
    ); } } render(, container); expect(container.innerHTML).toBe( '
    span
    div
    ', ); updater(); expect(container.innerHTML).toBe('
    text
    div
    '); updater(); expect(container.innerHTML).toBe( '
    span
    div
    ', ); updater(); expect(container.innerHTML).toBe('
    text
    div
    '); }); it('Should be able to swap between text node and html node #2', (done) => { let updater; interface BarState { bool: boolean; } class Bar extends Component { state: BarState; constructor(props) { super(props); this.state = { bool: false, }; this.changeDOM = this.changeDOM.bind(this); updater = this.changeDOM; } changeDOM() { this.setState({ bool: !this.state.bool, }); } render() { return (
    {this.state.bool ? span : ''}
    div
    ); } } render(, container); expect(container.innerHTML).toBe('
    div
    '); updater(); setTimeout(() => { expect(container.innerHTML).toBe( '
    span
    div
    ', ); updater(); setTimeout(() => { expect(container.innerHTML).toBe('
    div
    '); updater(); setTimeout(() => { expect(container.innerHTML).toBe( '
    span
    div
    ', ); done(); }, 10); }, 10); }, 10); }); describe('handling of sCU', () => { let instance; let shouldUpdate = false; interface Test2Props { foo: string; } class Test extends Component { shouldComponentUpdate() { return shouldUpdate; } render() { instance = this; return
    {this.props.foo}
    ; } } class Test2 extends Component { shouldComponentUpdate() { return shouldUpdate; } render() { instance = this; return createElement('div', { contenteditable: true }, this.props.foo); } } it('should correctly render once but never again', () => { shouldUpdate = false; render(, container); expect(container.innerHTML).toBe('
    bar
    '); render(, container); expect(container.innerHTML).toBe('
    bar
    '); instance.setState({ foo: 'woo' }); expect(container.innerHTML).toBe('
    bar
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should not fail if text node has external change Github#1207 - createElement', () => { shouldUpdate = false; render(, container); expect(container.innerHTML).toBe('
    bar
    '); render(, container); expect(container.innerHTML).toBe('
    bar
    '); container.firstChild.removeChild(container.firstChild.firstChild); // When div is contentEditable user can remove whole text content expect(container.innerHTML).toBe('
    '); shouldUpdate = true; render(, container); expect(container.innerHTML).toBe('
    foo
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should not fail if text node has external change Github#1207', () => { shouldUpdate = false; render(, container); expect(container.innerHTML).toBe('
    bar
    '); render(, container); expect(container.innerHTML).toBe('
    bar
    '); container.firstChild.removeChild(container.firstChild.firstChild); // When div is contentEditable user can remove whole text content expect(container.innerHTML).toBe('
    '); shouldUpdate = true; render(, container); expect(container.innerHTML).toBe('
    foo
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should not fail if text node has external change Github#1207 (variation - 2)', () => { shouldUpdate = false; render(, container); expect(container.innerHTML).toBe('
    bar
    '); render(, container); expect(container.innerHTML).toBe('
    bar
    '); container.firstChild.removeChild(container.firstChild.firstChild); // When div is contentEditable user can remove whole text content expect(container.innerHTML).toBe('
    '); shouldUpdate = true; render(, container); expect(container.innerHTML).toBe('
    '); render(null, container); expect(container.innerHTML).toBe(''); }); }); describe('handling of different primatives', () => { it('Should correctly handle boolean values (github#255)', () => { const Todo = ({ todo }) => ( {todo.id} {todo.desc} {todo.done} ); render(, container); expect(container.innerHTML).toBe(''); render(, container); expect(container.innerHTML).toBe(''); }); }); describe('handling JSX spread attributes', () => { it('should properly handle multiple attributes using spread', () => { class Input extends Component { constructor() { super(); this.handleBlur = this.handleBlur.bind(this); } handleBlur(_event) {} render() { const props = { onBlur: this.handleBlur, className: 'foo', id: 'test', }; return ; } } render(, container); expect(container.innerHTML).toBe(''); }); }); describe('Swapping Component to DOM node', () => { it('Should be able to swap statefull component to DOM list when doing setState', () => { let change1; let unMountCalled = false; class FooBar extends Component { constructor(props) { super(props); } componentWillUnmount() { unMountCalled = true; } render() { return (
    foo1 foo2 foo3 foo4
    ); } } interface TesterState { toggle1: boolean; } class Tester extends Component { state: TesterState; constructor(props) { super(props); this.state = { toggle1: false, }; change1 = this.toggle1.bind(this); } toggle1() { this.setState({ toggle1: !this.state.toggle1, }); } renderContent() { if (this.state.toggle1) { return ; } else { return (

    foo

    ); } } render() { return
    {this.renderContent()}
    ; } } render(, container); expect(container.innerHTML).toBe( '
    ', ); expect(unMountCalled).toEqual(false); change1(); expect(unMountCalled).toEqual(false); expect(container.innerHTML).toBe( '
    foo1foo2foo3foo4
    ', ); change1(); expect(unMountCalled).toEqual(true); expect(container.innerHTML).toBe( '
    ', ); }); it('Should be able to swap stateless component to DOM list when doing setState', () => { let change1; const FooBar = () => (
    foo1 foo2 foo3 foo4
    ); interface TesterState { toggle1: boolean; } class Tester extends Component { state: TesterState; constructor(props) { super(props); this.state = { toggle1: false, }; change1 = this.toggle1.bind(this); } toggle1() { this.setState({ toggle1: !this.state.toggle1, }); } renderContent() { if (this.state.toggle1) { return ; } else { return (

    foo

    ); } } render() { return
    {this.renderContent()}
    ; } } render(, container); expect(container.innerHTML).toBe( '
    ', ); change1(); expect(container.innerHTML).toBe( '
    foo1foo2foo3foo4
    ', ); change1(); expect(container.innerHTML).toBe( '
    ', ); }); }); describe('handling componentWillReceiveProps lifecycle event', () => { it('should correctly handle setState within the lifecycle function', () => { let renderCount = 0; interface Comp1State { foo: number; } class Comp1 extends Component { state: Comp1State; constructor(props) { super(props); this.state = { foo: 0, }; } componentWillReceiveProps() { this.setState({ foo: 1 }); } render() { renderCount++; return
    {this.state.foo}
    ; } } render(, container); expect(container.innerHTML).toBe('
    0
    '); render(, container); expect(container.innerHTML).toBe('
    1
    '); expect(renderCount).toBe(2); }); }); it('mixing JSX components with non-JSX components', () => { function Comp() { return createElement('div', {}); } function Comp2() { return createElement('span', {}); } function Comp3() { return
    ; } render(
    , container, ); expect(container.innerHTML).toBe('
    '); render(
    , container, ); expect(container.innerHTML).toBe('
    '); render( , container, ); expect(container.innerHTML).toBe('
    '); render(createElement('span', null, ), container); expect(container.innerHTML).toBe('
    '); }); describe('components should be able to use defaultProps', () => { interface Comp1Props { a?: string; b?: string; c?: string; } class Comp1 extends Component { constructor(props) { super(props); } static defaultProps = { a: 'A', b: 'B', }; render() { return (
    Hello {this.props.c}!
    ); } } class Comp2 extends Component { constructor(props) { super(props); } static defaultProps = { a: 'aye', b: 'bee', }; render() { return (
    Hello {this.props.c}!
    ); } } it('should mount component with defaultProps', () => { render(, container); expect(container.innerHTML).toBe('
    Hello C!
    '); }); it('should mount child component with its defaultProps', () => { const Parent = (props) =>
    {props.children.props.a}
    ; render( , container, ); expect(container.innerHTML).toBe('
    A
    '); }); it('should patch component with defaultProps', () => { render(, container); render(, container); expect(container.innerHTML).toBe('
    Hello C2!
    '); }); it('should patch component with defaultProps #2', () => { render(, container); render(, container); expect(container.innerHTML).toBe( '
    Hello C1!
    ', ); render(, container); expect(container.innerHTML).toBe('
    Hello C2!
    '); }); it('should as per React: Have childrens defaultProps set before children is mounted', () => { let childrenPropertABeforeMount = 'A'; class Parent extends Component { render() { expect((this.props.children as any).props.a).toBe( childrenPropertABeforeMount, ); return
    {this.props.children}
    ; } } render( , container, ); expect(container.innerHTML).toBe( '
    Hello !
    ', ); childrenPropertABeforeMount = 'ABCD'; render( , container, ); expect(container.innerHTML).toBe( '
    Hello !
    ', ); }); }); describe('when calling setState with a function', () => { let reference; interface Comp1State { foo: string; } class Comp1 extends Component { state: Comp1State; constructor(props) { super(props); this.state = { foo: 'yar', }; reference = this.update.bind(this); } update() { this.setState(() => ({ foo: 'bar', })); } render() { return
    {this.state.foo}
    ; } } it('the state should update properly', (done) => { render(, container); expect(container.innerHTML).toBe('
    yar
    '); reference(); setTimeout(() => { expect(container.innerHTML).toBe('
    bar
    '); done(); }, 10); }); }); describe('node change in updateComponent', () => { it('Should not crash when invalid node returned - statefull', () => { interface Comp1Props { foo?: boolean; } class Comp1 extends Component { constructor(props) { super(props); } render() { if (this.props.foo) { return null; } return
    rendered
    ; } } render(, container); expect(container.innerHTML).toEqual('
    rendered
    '); render(, container); expect(container.innerHTML).toEqual(''); }); it('Should not crash when invalid node returned - stateless', () => { interface Comp1Props { foo?: boolean; } const Comp1 = ({ foo }: Comp1Props) => { if (foo) { return null; } return
    rendered
    ; }; render(, container); expect(container.innerHTML).toEqual('
    rendered
    '); render(, container); expect(container.innerHTML).toEqual(''); }); }); describe('Root handling issues #1', () => { let div; interface AState { n: boolean; } class A extends Component { state: AState; private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; this.onClick = () => { this.setState({ n: !this.state.n }); }; } render() { if (this.state.n) { return (
    (div = dom)} onClick={this.onClick}> DIV
    ); } return SPAN; } } class B extends Component { shouldComponentUpdate() { return false; } render() { return
    ; } } interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [,
    ROW
    ]; if (this.state.reverse) { children.reverse(); } return (
    {children}
    ); } } // this test is to replicate https://jsfiddle.net/localvoid/r070sgrq/2/ it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click on "SPAN" container.querySelector('span').click(); // "SPAN" should now be "DIV" expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    DIV
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click on "DIV" div.click(); // "DIV" should now be "SPAN" expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    SPAN
    ', ); }); }); describe('Root handling issues #2', () => { let div; interface AState { n: boolean; } class A extends Component { state: AState; private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; this.onClick = () => { this.setState({ n: !this.state.n }); }; } render() { if (this.state.n) { return (
    (div = dom)} onClick={this.onClick}> DIV
    ); } return SPAN; } } function F() { return
    ; } class B extends Component { shouldComponentUpdate() { return false; } render() { return ; } } interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [,
    ROW
    ]; if (this.state.reverse) { children.reverse(); } return (
    {children}
    ); } } // this test is to replicate https://jsfiddle.net/localvoid/r070sgrq/2/ it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click on "SPAN" container.querySelector('span').click(); // "SPAN" should now be "DIV" expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    DIV
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click on "DIV" div.click(); // "DIV" should now be "SPAN" expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    SPAN
    ', ); }); }); describe('Root handling issues #3', () => { let div; interface AState { n: boolean; } class A extends Component { state: AState; private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; this.onClick = () => { this.setState({ n: !this.state.n }); }; } render() { if (this.state.n) { return (
    (div = dom)} onClick={this.onClick}> DIV
    ); } return SPAN; } } function F() { return
    ; } function B() { return false} />; } interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [ false} />,
    ROW
    , ]; if (this.state.reverse) { children.reverse(); } return (
    {children}
    ); } } // this test is to replicate https://jsfiddle.net/localvoid/r070sgrq/2/ it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click on "SPAN" container.querySelector('span').click(); // "SPAN" should now be "DIV" expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    DIV
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    DIV
    ROW
    ', ); // click on "DIV" div.click(); // "DIV" should now be "SPAN" expect(container.innerHTML).toEqual( '
    SPAN
    ROW
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    ROW
    SPAN
    ', ); render(null, container); }); }); describe('Root handling issues #4', () => { interface AState { n: boolean; } class A extends Component { state: AState; private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; this.onClick = () => { this.setState({ n: !this.state.n }); }; } render() { if (this.state.n) { return
    DIV
    ; } return SPAN; } } class B extends Component { shouldComponentUpdate() { return false; } render() { return this.props.children; } } interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [
    ,
    A
    , ]; if (this.state.reverse) { children.reverse(); } return (
    {children}
    {children}
    ); } } it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    SPAN
    A
    SPAN
    A
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    A
    SPAN
    A
    SPAN
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    SPAN
    A
    SPAN
    A
    ', ); }); }); describe('Root handling issues #5', () => { interface AState { n: boolean; } class A extends Component { state: AState; private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; this.onClick = () => { this.setState({ n: !this.state.n }); }; } render() { if (this.state.n) { return
    DIV
    ; } return SPAN; } } const hoisted =
    ; class B extends Component { shouldComponentUpdate() { return false; } render() { return hoisted; } } interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [,
    A
    ]; if (this.state.reverse) { children.reverse(); } Object.freeze(children); return (
    {children}
    {children}
    ); } } it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    SPAN
    A
    SPAN
    A
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    A
    SPAN
    A
    SPAN
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    SPAN
    A
    SPAN
    A
    ', ); }); }); describe('Root handling issues #6', () => { let i; beforeEach(function () { i = 1; }); class B extends Component { constructor(props) { super(props); } shouldComponentUpdate() { return false; } render() { return
    {i}
    ; } } class Test extends Component { render() { return (
    ); } } it('should replace keyed component if key changes', () => { render(, container); expect(container.innerHTML).toEqual( '
    1
    ', ); // click "Replace" container.querySelector('button').click(); expect(container.innerHTML).toEqual( '
    2
    ', ); }); }); describe('Cloned children issues #1', () => { interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const a =
    B
    ; const b =
    A
    ; return (
    {this.state.reverse ? [a, b].reverse() : [a, b]}
    {this.state.reverse ? [a, b].reverse() : [a, b]}
    ); } } // this test is to replicate https://jsfiddle.net/localvoid/fmznjwxv/ it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    B
    A
    B
    A
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); }); }); describe('Cloned children issues #2', () => { interface TestState { reverse: boolean; } class Test extends Component { state: TestState; constructor(props) { super(props); this.state = { reverse: false, }; } render() { const children = [
    B
    ,
    A
    ]; if (this.state.reverse) { children.reverse(); } return (
    {children}
    {children}
    ); } } // this test is to replicate https://jsfiddle.net/localvoid/fmznjwxv/ it('should correct swap rows', () => { render(, container); expect(container.innerHTML).toEqual( '
    B
    A
    B
    A
    ', ); // click "SWAP ROWS" container.querySelector('button').click(); }); }); describe('Asynchronous setStates', () => { it('Should not fail when parent component calls setState on unmounting children', (done) => { interface ParentProps { toggle: boolean; } interface ParentState { text: string; } class Parent extends Component { state: ParentState; constructor(props) { super(props); this.state = { text: 'bar', }; this.changeState = this.changeState.bind(this); } changeState() { this.setState({ text: 'foo', }); } render() { return (
    {this.state.text} {this.props.toggle ? ( [] ) : ( tester )}
    ); } } class Tester extends Component<{ call: () => void; toggle: boolean }> { constructor(props) { super(props); } componentWillUnmount() { // parent will do setState this.props.call(); } render() { return (
    foo
    ); } } render(, container); expect(container.innerHTML).toEqual( '
    bar
    foo
    ', ); render(, container); setTimeout(() => { done(); }, 40); }); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/components3.spec.tsx ================================================ import { Component, InfernoChild, render, rerender } from 'inferno'; describe('Components 3 (TSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); document.body.removeChild(container); }); describe('should render a repeating counter component with component children', () => { interface ValueProps { value: number; } class Value extends Component { constructor(props) { super(props); } render() { return
    {this.props.value}
    ; } } interface RepeaterProps { value: number; } class Repeater extends Component { render() { const children: InfernoChild[] = []; for (let i = 0; i < 3; i++) { children.push(); } return
    {children}
    ; } } it('should correctly render as values increase', () => { let value = 0; render(, container); expect(container.innerHTML).toBe( '
    0
    0
    0
    ', ); value++; render(, container); expect(container.innerHTML).toBe( '
    1
    1
    1
    ', ); value++; render(, container); expect(container.innerHTML).toBe( '
    2
    2
    2
    ', ); }); }); describe('should render a component with component children as the only child', () => { class Jaska extends Component { constructor(props) { super(props); } render() { return (

    Okdokfwoe

    odkodwq

    ); } } class Container extends Component { constructor(props) { super(props); } render() { return
    {this.props.children}
    ; } } class TestingProps extends Component { constructor(props) { super(props); } render() { return (
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe( '

    Okdokfwoe

    odkodwq

    ', ); }); }); describe('should render a component with with mapped text nodes', () => { interface MyComponent99Props { isok: boolean; } interface MyComponent98State { isok: boolean; } class MyComponent98 extends Component { state: MyComponent98State; constructor(props) { super(props); this.state = { isok: false, }; } componentDidMount() { this.setState({ isok: true }); } render() { return ; } } class MyComponent99 extends Component { constructor(props) { super(props); } render() { return (
    isok= {this.props.isok ? 'true' : 'false'}
    {this.props.isok && ['a', 'b'].map((x) => { return {x}; })}
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe('
    isok=false
    '); rerender(); expect(container.innerHTML).toBe( '
    isok=true
    ab
    ', ); }); }); describe('should render a component with conditional boolean text nodes', () => { interface MyComponent98State { isok: boolean; } interface MyComponent99Props { isok: boolean; } class MyComponent98 extends Component { state: MyComponent98State; constructor(props) { super(props); this.state = { isok: false, }; } componentDidMount() { this.setState({ isok: true }); } render() { return ; } } class MyComponent99 extends Component { constructor(props) { super(props); } render() { const z = function (v) { if (v) { return a; } else { return b; } }; return (
    {z(this.props.isok)}
    ); } } it('should correctly render', (done) => { render(, container); setTimeout(() => { expect(container.innerHTML).toBe( '
    a
    ', ); done(); }, 25); }); }); const StatelessComponent2 = (props) =>
    {props.name}
    ; it('should render stateless component', () => { render(, container); expect(container.textContent).toBe('A'); }); it('should unmount stateless component', function () { render(, container); expect(container.textContent).toBe('A'); render(null, container); expect(container.textContent).toBe(''); }); it('should support module pattern components', function () { function Child({ test }) { return
    {test}
    ; } render(, container); expect(container.textContent).toBe('test'); }); describe('should render a component with a conditional list that changes upon toggle', () => { class BuggyRender extends Component { state: { empty: boolean }; constructor(props) { super(props); this.state = { empty: true, }; this.toggle = this.toggle.bind(this); } toggle() { this.setState({ empty: !this.state.empty, }); } render() { return (
      {(() => { if (this.state.empty === true) { return
    • No cars!
    • ; } else { return ['BMW', 'Volvo', 'Saab'].map(function (car) { return
    • {car}
    • ; }); } })()}
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe( '
    • No cars!
    ', ); }); it('should handle update upon click', () => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } expect(container.innerHTML).toBe( '
    • BMW
    • Volvo
    • Saab
    ', ); }); }); describe('should render a component with a list that instantly changes', () => { interface ChangeChildrenCountState { list: string[]; } class ChangeChildrenCount extends Component< object, ChangeChildrenCountState > { state: ChangeChildrenCountState; constructor(props) { super(props); this.state = { list: ['1', '2', '3', '4'], }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ list: ['1'], }); } render() { return (
    {this.state.list.map(function (_x, i) { return
    {i}
    ; })}
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe( '
    0
    1
    2
    3
    ', ); }); it('should handle update upon click', (done) => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '
    0
    ', ); done(); }, 10); }); }); describe('should render a stateless component with context', () => { const StatelessComponent3 = ({ value }, { fortyTwo }) => (

    {value}-{fortyTwo || 'ERROR'}

    ); interface FirstState { counter: number; } class First extends Component { state: FirstState; constructor(props, context) { super(props, context); this.state = { counter: 0, }; this._onClick = this._onClick.bind(this); } _onClick() { this.setState({ counter: 1, }); } getChildContext() { return { fortyTwo: 42, }; } render() { return (
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe( '

    0-42

    ', ); }); it('should handle update upon click', (done) => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    1-42

    ', ); done(); }, 10); }); }); describe('should render a conditional stateless component', () => { const StatelessComponent4 = ({ value }) =>

    {value}

    ; interface FirstState { counter: number; } class First extends Component { state: FirstState; private condition: boolean; constructor(props) { super(props); this.state = { counter: 0, }; this.condition = true; this._onClick = this._onClick.bind(this); } _onClick() { this.setState({ counter: 1, }); } render() { return (
    {this.condition ? ( ) : null}
    ); } } it('should correctly render', () => { render(, container); expect(container.innerHTML).toBe( '

    0

    ', ); }); it('should handle update upon click', (done) => { render(, container); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    1

    ', ); done(); }, 10); }); }); describe('should render stateless component correctly when changing states', () => { let firstDiv, secondDiv; beforeEach(function () { firstDiv = document.createElement('div'); secondDiv = document.createElement('div'); container.appendChild(firstDiv); container.appendChild(secondDiv); }); afterEach(function () { render(null, firstDiv); render(null, secondDiv); }); const StatelessComponent = ({ value }) =>

    {value}

    ; interface FirstState { counter: number; } class First extends Component<{ name: string }, FirstState> { state: FirstState; private condition: boolean; constructor(props) { super(props); this.state = { counter: 0, }; this.condition = true; this._onClick = this._onClick.bind(this); } _onClick() { this.setState({ counter: 1, }); } render() { return (
    {this.condition ? ( ) : null}
    ); } } it('should correctly render', () => { render(, firstDiv); render(, secondDiv); expect(container.innerHTML).toBe( '

    0

    0

    ', ); }); it('should handle update when changing first component', (done) => { render(, firstDiv); render(, secondDiv); const buttons = firstDiv.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    1

    0

    ', ); done(); }, 10); }); it('should handle update when changing second component', (done) => { render(, firstDiv); render(, secondDiv); const buttons = secondDiv.querySelectorAll('button'); for (const button of buttons) { button.click(); } setTimeout(() => { expect(container.innerHTML).toBe( '

    0

    1

    ', ); done(); }, 10); }); }); describe('updating child should not cause rendering parent to fail', () => { it('should render parent correctly after child changes', (done) => { let updateParent, updateChild; interface ParentState { x: boolean; } class Parent extends Component { state: ParentState; constructor(props) { super(props); this.state = { x: false }; updateParent = () => { this.setState({ x: true }); }; } render() { return (

    parent

    {!this.state.x ? : }
    ); } } class ChildB extends Component { constructor(props) { super(props); } render() { return
    Y
    ; } } interface ChildAState { z: boolean; } class ChildA extends Component { state: ChildAState; constructor(props) { super(props); this.state = { z: false }; updateChild = () => { this.setState({ z: true }); }; } render() { if (!this.state.z) { return
    A
    ; } return ; } } class SubChild extends Component { constructor(props) { super(props); } render() { return
    B
    ; } } render(, container); expect(container.innerHTML).toBe('

    parent

    A
    '); updateChild(); setTimeout(() => { expect(container.innerHTML).toBe( '

    parent

    B
    ', ); updateParent(); setTimeout(() => { expect(container.innerHTML).toBe( '

    parent

    Y
    ', ); done(); }, 10); }, 10); }); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/createElement.fragment.spec.ts ================================================ import { Component, createFragment, createPortal, Fragment, render, rerender, VNode, } from 'inferno'; import { createElement } from 'inferno-create-element'; import { ChildFlags } from 'inferno-vnode-flags'; describe('CreateElement (non-JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); describe('Fragments', () => { it('Should render and unmount fragment', () => { const Example = class Example extends Component { render() { return createFragment( [ createElement('div', null, 'First'), createElement('div', null, 'second'), ], ChildFlags.HasNonKeyedChildren, ); } }; render(createElement(Example, null), container); expect(container.innerHTML).toBe('
    First
    second
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should render nested fragment', () => { const Example = class Example extends Component { render() { return createFragment( [ createElement('div', null, 'First'), createFragment( [ createElement('div', null, 'Sub1'), createElement('div', null, 'Sub2'), ], ChildFlags.HasNonKeyedChildren, ), createElement('div', null, 'second'), ], ChildFlags.HasNonKeyedChildren, ); } }; render(createElement(Example, null), container); expect(container.innerHTML).toBe( '
    First
    Sub1
    Sub2
    second
    ', ); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be to replace component with fragment with another component', () => { const Example = class Example extends Component { render() { return createFragment( [ createElement('div', null, 'First'), createFragment( [ createElement('div', null, 'Sub1'), createElement('div', null, 'Sub2'), ], ChildFlags.HasNonKeyedChildren, ), createElement('div', null, 'second'), ], ChildFlags.HasNonKeyedChildren, ); } }; function FunctionalComp() { return createFragment( [createElement('div', null, 'Functional')], ChildFlags.HasNonKeyedChildren, ); } render(createElement(Example, null), container); expect(container.innerHTML).toBe( '
    First
    Sub1
    Sub2
    second
    ', ); render(createElement(FunctionalComp, null), container); expect(container.innerHTML).toBe('
    Functional
    '); render(createElement(Example, null), container); expect(container.innerHTML).toBe( '
    First
    Sub1
    Sub2
    second
    ', ); render(createElement(FunctionalComp, null), container); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to move fragments', () => { const fragmentA = () => createFragment( [ createElement('div', { id: 'a1' }, 'A1'), createElement('div', null, 'A2'), ], ChildFlags.HasNonKeyedChildren, 'A', ); const fragmentB = () => createFragment( [createElement('div', { id: 'b1' }, 'B1')], ChildFlags.HasNonKeyedChildren, 'B', ); const fragmentC = () => createFragment( [ createElement('div', { id: 'c1' }, 'C1'), createElement('div', null, 'C2'), createElement('div', null, 'C3'), ], ChildFlags.HasNonKeyedChildren, 'C', ); render( createElement('div', null, fragmentA(), fragmentB(), fragmentC()), container, ); expect(container.innerHTML).toBe( '
    A1
    A2
    B1
    C1
    C2
    C3
    ', ); const A1 = container.querySelector('#a1'); const B1 = container.querySelector('#b1'); const C1 = container.querySelector('#c1'); // Switch order render( createElement('div', null, fragmentC(), fragmentA(), fragmentB()), container, ); // Verify dom has changed and nodes are the same expect(container.innerHTML).toBe( '
    C1
    C2
    C3
    A1
    A2
    B1
    ', ); expect(container.querySelector('#a1')).toBe(A1); expect(container.querySelector('#b1')).toBe(B1); expect(container.querySelector('#c1')).toBe(C1); // Switch order again render(createElement('div', null, fragmentB(), fragmentC()), container); // Verify dom has changed and nodes are the same expect(container.innerHTML).toBe( '
    B1
    C1
    C2
    C3
    ', ); expect(container.querySelector('#a1')).toBe(null); expect(container.querySelector('#b1')).toBe(B1); expect(container.querySelector('#c1')).toBe(C1); }); it('Should clone fragment children if they are passed as reference', () => { const fragmentA = createFragment( [ createElement('div', { id: 'a1' }, 'A1'), createElement('div', null, 'A2'), ], ChildFlags.HasNonKeyedChildren, 'A', ); const fragmentB = createFragment( [createElement('div', { id: 'b1' }, 'B1')], ChildFlags.HasNonKeyedChildren, 'B', ); const fragmentC = createFragment( [ createElement('div', { id: 'c1' }, 'C1'), createElement('div', null, 'C2'), createElement('div', null, 'C3'), ], ChildFlags.HasNonKeyedChildren, 'C', ); const content = [fragmentC]; function SFC() { return createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ); } render( createElement( Fragment, null, fragmentA, createElement(SFC, { key: 'sfc' }), fragmentB, fragmentC, ), container, ); const FragmentAHtml = '
    A1
    A2
    '; const FragmentBHtml = '
    B1
    '; const FragmentCHtml = '
    C1
    C2
    C3
    '; const SFCHtml = '1' + FragmentCHtml + '2'; expect(container.innerHTML).toBe( FragmentAHtml + SFCHtml + FragmentBHtml + FragmentCHtml, ); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to move component with fragment root', () => { const fragmentA = createFragment( [ createElement('div', { id: 'a1' }, 'A1'), createElement('div', null, 'A2'), ], ChildFlags.HasNonKeyedChildren, 'A', ); const fragmentB = createFragment( [createElement('div', { id: 'b1' }, 'B1')], ChildFlags.HasNonKeyedChildren, 'B', ); const fragmentC = createFragment( [ createElement('div', { id: 'c1' }, 'C1'), createElement('div', null, 'C2'), createElement('div', null, 'C3'), ], ChildFlags.HasNonKeyedChildren, 'C', ); const content = [fragmentC]; function SFC() { return createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ); } render( createElement( Fragment, null, fragmentA, createElement(SFC, { key: 'sfc' }), fragmentB, fragmentC, ), container, ); const FragmentAHtml = '
    A1
    A2
    '; const FragmentBHtml = '
    B1
    '; const FragmentCHtml = '
    C1
    C2
    C3
    '; const SFCHtml = '1' + FragmentCHtml + '2'; expect(container.innerHTML).toBe( FragmentAHtml + SFCHtml + FragmentBHtml + FragmentCHtml, ); // Switch order render( createElement( Fragment, null, fragmentA, fragmentC, createElement(SFC, { key: 'sfc' }), ), container, ); expect(container.innerHTML).toBe(FragmentAHtml + FragmentCHtml + SFCHtml); // Switch order again render( createElement( Fragment, null, createElement('div', { key: '1' }, '1'), createElement(SFC, { key: 'sfc' }), fragmentA, fragmentC, createElement('div', { key: '1' }, '2'), ), container, ); // Verify dom has changed and nodes are the same expect(container.innerHTML).toBe( '
    1
    ' + SFCHtml + FragmentAHtml + FragmentCHtml + '
    2
    ', ); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to move component with fragment root #2', () => { const fragmentA = createFragment( [ createElement('div', { id: 'a1' }, 'A1'), createElement('div', null, 'A2'), ], ChildFlags.HasNonKeyedChildren, 'A', ); const fragmentB = createFragment( [createElement('div', { id: 'b1' }, 'B1')], ChildFlags.HasNonKeyedChildren, 'B', ); const fragmentC = createFragment( [ createElement('div', { id: 'c1' }, 'C1'), createElement('div', null, 'C2'), createElement('div', null, 'C3'), ], ChildFlags.HasNonKeyedChildren, 'C', ); const content = [fragmentC]; function SFC() { return createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ); } render( createElement( Fragment, null, fragmentA, createElement(SFC, { key: 'sfc1' }), fragmentB, createElement(SFC, { key: 'sfc2' }), fragmentC, createElement(SFC, { key: 'sfc3' }), ), container, ); const FragmentAHtml = '
    A1
    A2
    '; const FragmentBHtml = '
    B1
    '; const FragmentCHtml = '
    C1
    C2
    C3
    '; const SFCHtml = '1' + FragmentCHtml + '2'; expect(container.innerHTML).toBe( FragmentAHtml + SFCHtml + FragmentBHtml + SFCHtml + FragmentCHtml + SFCHtml, ); // Switch order render( createElement( Fragment, null, createElement(SFC, { key: 'sfc3' }), fragmentA, createElement(SFC, { key: 'sfc1' }), fragmentC, createElement(SFC, { key: 'sfc2' }), ), container, ); expect(container.innerHTML).toBe( SFCHtml + FragmentAHtml + SFCHtml + FragmentCHtml + SFCHtml, ); // Switch order again render( createElement( Fragment, null, createElement('div', { key: '1' }, '1'), createElement(SFC, { key: 'sfc1' }), createElement(SFC, { key: 'sfc2' }), fragmentA, fragmentC, createElement('div', { key: '1' }, '2'), createElement(SFC, { key: 'sfc3' }), ), container, ); // Verify dom has changed and nodes are the same expect(container.innerHTML).toBe( '
    1
    ' + SFCHtml + SFCHtml + FragmentAHtml + FragmentCHtml + '
    2
    ' + SFCHtml, ); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to render fragments JSX way', () => { function Fragmenter({ first, mid, last, changeOrder }) { if (changeOrder) { return createElement( Fragment, null, createElement('div', null, first), createElement( Fragment, null, 'More', null, 'Hey!', createElement( Fragment, null, createElement(Fragment, null, 'Large ', last), createElement(Fragment, null, 'And Small'), ), createElement(Fragment, null, 'Nesting'), mid, ), createElement('span', null, 'bar'), null, ); } return createElement( Fragment, null, createElement('div', null, first), 'Hey!', createElement( Fragment, null, 'More', createElement(Fragment, null, 'Nesting'), mid, createElement( Fragment, null, createElement(Fragment, null, 'Large ', last), createElement(Fragment, null, 'And Small'), ), ), createElement('span', null, 'bar'), ); } let mountCounter = 0; let unmountCounter = 0; const FoobarCom = class FoobarCom extends Component<{ node: HTMLDivElement; }> { componentWillMount() { mountCounter++; } componentWillUnmount() { unmountCounter++; } render(props) { return createElement( Fragment, null, props.children, createPortal( createElement('div', null, 'InvisiblePortalCreator'), props.node, ), null, 'Try out some crazy stuff', ); } }; const portalNode = document.createElement('div'); render( createElement( FoobarCom, { node: portalNode }, createElement(Fragmenter, { first: 'first', mid: 'MID', last: createElement('div', null, 'Why?'), changeOrder: false, }), ), container, ); expect(container.innerHTML).toBe( '
    first
    Hey!MoreNestingMIDLarge
    Why?
    And SmallbarTry out some crazy stuff', ); expect(portalNode.innerHTML).toBe('
    InvisiblePortalCreator
    '); render( createElement( FoobarCom, { node: portalNode }, createElement(Fragmenter, { first: createElement('span', null, 'GoGo'), mid: 'MID', last: createElement('div', null, 'Why?'), changeOrder: true, }), ), container, ); expect(container.innerHTML).toBe( '
    GoGo
    MoreHey!Large
    Why?
    And SmallNestingMIDbarTry out some crazy stuff', ); expect(portalNode.innerHTML).toBe('
    InvisiblePortalCreator
    '); render( createElement( FoobarCom, { node: portalNode }, createElement(Fragmenter, { first: 'first', mid: 'MID', last: createElement('div', null, 'Why?'), changeOrder: false, }), ), container, ); expect(container.innerHTML).toBe( '
    first
    Hey!MoreNestingMIDLarge
    Why?
    And SmallbarTry out some crazy stuff', ); expect(portalNode.innerHTML).toBe('
    InvisiblePortalCreator
    '); }); it('Should render deeply nested fragment', () => { function Fragmenter2() { return createElement( Fragment, null, createElement( Fragment, null, createElement( Fragment, null, createElement( Fragment, null, createElement( Fragment, null, createElement( Fragment, null, createElement( Fragment, null, createElement(Fragment, null, 'Okay!'), ), ), ), ), ), ), ); } render(createElement(Fragmenter2, null), container); expect(container.innerHTML).toBe('Okay!'); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should append DOM nodes to correct position when component root Fragmnet change', () => { const TestRoot = class TestRoot extends Component { render() { return createElement(Fragment, null, this.props.children); } }; render( createElement( 'div', null, createElement( TestRoot, null, createElement('div', null, '1'), createElement('div', null, '2'), ), createElement( TestRoot, null, createElement('span', null, 'Ok'), createElement('span', null, 'Test'), ), ), container, ); expect(container.innerHTML).toBe( '
    1
    2
    OkTest
    ', ); render( createElement( 'div', null, createElement( TestRoot, null, createElement('div', null, '1'), createElement('div', null, '2'), createElement('div', null, '3'), createElement('div', null, '4'), ), createElement(TestRoot, null, createElement('div', null, 'Other')), ), container, ); expect(container.innerHTML).toBe( '
    1
    2
    3
    4
    Other
    ', ); }); it('Should not clear whole parent element when fragment children are cleared', () => { const TestRoot = class TestRoot extends Component { render() { return createElement(Fragment, null, this.props.children); } }; render( createElement( 'div', null, createElement( TestRoot, null, createElement('div', null, '1'), createElement('div', null, '2'), ), createElement( TestRoot, null, createElement('span', null, 'Ok'), createElement('span', null, 'Test'), ), ), container, ); expect(container.innerHTML).toBe( '
    1
    2
    OkTest
    ', ); render( createElement( 'div', null, createElement( TestRoot, null, createElement('div', null, '1'), createElement('div', null, '2'), createElement('div', null, '3'), createElement('div', null, '4'), ), createElement(TestRoot, null), ), container, ); expect(container.innerHTML).toBe( '
    1
    2
    3
    4
    ', ); }); it('Should move fragment and all its contents when using Fragment long syntax with keys', () => { let unmountCounter = 0; let mountCounter = 0; const TestLifecycle = class TestLifecycle extends Component { componentWillUnmount() { unmountCounter++; } componentWillMount() { mountCounter++; } render() { return createElement(Fragment, null, this.props.children); } }; render( createElement( 'div', null, createElement( Fragment, { key: '1' }, createElement(TestLifecycle, null, '1a'), createElement(TestLifecycle, null, '1b'), ), createElement( Fragment, { key: '2' }, createElement(TestLifecycle, null, '2a'), createElement(TestLifecycle, null, '2b'), ), ), container, ); expect(container.innerHTML).toBe('
    1a1b2a2b
    '); expect(unmountCounter).toBe(0); expect(mountCounter).toBe(4); render( createElement( 'div', null, createElement( Fragment, { key: '2' }, createElement(TestLifecycle, null, '2a'), createElement(TestLifecycle, null, '2b'), createElement(TestLifecycle, null, '2c'), ), createElement( Fragment, { key: '1' }, createElement(TestLifecycle, null, '1a'), createElement(TestLifecycle, null, '1b'), ), ), container, ); expect(container.innerHTML).toBe('
    2a2b2c1a1b
    '); expect(unmountCounter).toBe(0); expect(mountCounter).toBe(5); render( createElement( 'div', null, createElement( Fragment, { key: '3' }, createElement(TestLifecycle, null, '3a'), createElement(TestLifecycle, null, '3b'), createElement(TestLifecycle, null, '3c'), ), createElement( Fragment, { key: '2' }, createElement(TestLifecycle, null, '2a'), createElement(TestLifecycle, null, '2Patched'), ), ), container, ); expect(container.innerHTML).toBe('
    3a3b3c2a2Patched
    '); expect(unmountCounter).toBe(3); expect(mountCounter).toBe(8); }); it('Should unmount empty fragments', () => { render( createElement(Fragment, null, createElement(Fragment, null)), container, ); expect(container.innerHTML).toBe(''); render( createElement(Fragment, null, createElement('div', null)), container, ); expect(container.innerHTML).toBe('
    '); render( createElement(Fragment, null, createElement(Fragment, null)), container, ); expect(container.innerHTML).toBe(''); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to replace last element in fragment', () => { render( createElement( Fragment, null, createElement( Fragment, null, createElement('span', null, '1a'), createElement('span', null, '1b'), createElement('div', null, '1c'), ), createElement( Fragment, null, createElement('span', null, '2a'), createElement('span', null, '2b'), createElement('span', null, '2c'), ), createElement(Fragment, null), ), container, ); expect(container.innerHTML).toBe( '1a1b
    1c
    2a2b2c', ); render( createElement( Fragment, null, createElement( Fragment, null, createElement('span', null, '1a'), createElement('span', null, '1c'), ), createElement( Fragment, null, createElement('span', null, '2a'), createElement('span', null, '2b'), createElement('span', null, '2c'), ), createElement(Fragment, null), ), container, ); expect(container.innerHTML).toBe( '1a1c2a2b2c', ); render( createElement(Fragment, null, createElement(Fragment, null)), container, ); expect(container.innerHTML).toBe(''); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should mount Fragment with invalid children', () => { render(createElement(Fragment, null, null, undefined), container); expect(container.innerHTML).toBe(''); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should mount Fragment with invalid children #2', () => { function Foobar() { return null; } render( createElement( Fragment, null, null, createElement(Foobar, null), undefined, ), container, ); expect(container.innerHTML).toBe(''); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should mount Fragment with invalid children #2', () => { let add = false; function Foobar() { if (add) { return createElement('div', null, 'Ok'); } return null; } render( createElement( Fragment, null, null, createElement(Foobar, null), undefined, ), container, ); expect(container.innerHTML).toBe(''); add = true; render( createElement( Fragment, null, null, createElement(Foobar, null), undefined, ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); }); it('Should be possible to update from 0 to 1', () => { function Foobar() { return createElement('div', null, 'Ok'); } let content: (VNode | null)[] = [null]; render( createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ), container, ); expect(container.innerHTML).toBe('12'); content = [createElement(Foobar, null)]; render( createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ), container, ); expect(container.innerHTML).toBe( '1
    Ok
    2', ); }); it('Should be possible to update from 0 to 1 fragment -> fragment', () => { function Foobar() { return createElement('div', null, 'Ok'); } let content: (VNode | null)[] = []; render( createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ), container, ); expect(container.innerHTML).toBe('12'); content = [createElement(Fragment, null, createElement(Foobar, null))]; render( createElement( Fragment, null, createElement('span', null, '1'), createElement(Fragment, null, content), createElement('span', null, '2'), ), container, ); expect(container.innerHTML).toBe( '1
    Ok
    2', ); }); it('Should be possible to mount and patch single component fragment children', () => { let counter = 0; const Foobar = class Foobar extends Component { componentWillMount() { counter++; } render() { return null; } }; render(createElement(Fragment, null), container); render( createElement(Fragment, null, createElement(Foobar, null)), container, ); expect(container.innerHTML).toBe(''); expect(counter).toBe(1); render( createElement( Fragment, null, createElement('div', null, 'Ok'), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to mount and patch single component fragment children - variation 2', () => { let counter = 0; const Foobar = class Foobar extends Component { componentWillMount() { counter++; } render() { return null; } }; let nodes: (VNode | null)[] = []; render(createElement(Fragment, null, nodes), container); nodes = [createElement(Foobar, null)]; render(createElement(Fragment, null, nodes), container); nodes = [ createElement(Foobar, null), createElement(Foobar, null), createElement(Foobar, null), ]; render(createElement(Fragment, null, nodes), container); nodes = []; render(createElement(Fragment, null, nodes), container); expect(container.innerHTML).toBe(''); expect(counter).toBe(3); render( createElement( Fragment, null, createElement('div', null, 'Ok'), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to patch single fragment child component', () => { let counter = 0; const Foobar = class Foobar extends Component { componentWillMount() { counter++; } render() { return null; } }; render( createElement( Fragment, null, createElement(Fragment, null, createElement(Foobar, null)), createElement(Fragment, null, createElement(Foobar, null)), ), container, ); expect(container.innerHTML).toBe(''); expect(counter).toBe(2); render( createElement( Fragment, null, createElement(Fragment, null), createElement(Fragment, null, createElement(Foobar, null)), createElement(Fragment, null, createElement(Foobar, null)), createElement(Fragment, null), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe(''); expect(counter).toBe(4); render( createElement( Fragment, null, createElement('div', null, 'Ok'), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to mount and patch single component fragment children', () => { const Foobar = class Foobar extends Component { render() { return null; } }; render( createElement(Fragment, null, createElement(Foobar, null)), container, ); render( createElement(Fragment, null, createElement(Foobar, null)), container, ); expect(container.innerHTML).toBe(''); render( createElement( Fragment, null, createElement('div', null, 'Ok'), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should be possible to mount and patch single component fragment children #2', () => { const Foobar = class Foobar extends Component { render() { return null; } }; render(createElement(Fragment, null, null), container); render( createElement(Fragment, null, createElement(Foobar, null)), container, ); expect(container.innerHTML).toBe(''); render( createElement( Fragment, null, createElement('div', null, 'Ok'), createElement(Foobar, null), ), container, ); expect(container.innerHTML).toBe('
    Ok
    '); render(null, container); expect(container.innerHTML).toBe(''); }); it('Should mount fragment children to correct position Github #1412', () => { const f = (...xs) => createFragment(xs, 0); interface ArticlesState { articles: string[]; sections: string[]; } class Articles extends Component { state: ArticlesState; constructor() { super(); this.state = { articles: ['id2', 'id3'], sections: ['id0', 'id1'], }; } componentDidMount() { expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3

    id1

    id2
    id3
    2018
    ', ); this.setState({ sections: [] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    2018
    ', ); this.setState({ articles: ['id2', 'id3'], sections: ['id0', 'id1'] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3

    id1

    id2
    id3
    2018
    ', ); } render() { return f( this.state.sections.map((section) => createElement('section', null, [ createElement('h2', null, section), this.state.articles.map((article) => f( article === 'id2' && createElement('aside', null, 'Today'), createElement('article', null, article), ), ), ]), ), ); } } class App extends Component { render() { return f( createElement('h1', null, 'App'), createElement(Articles), createElement('footer', null, '2018'), ); } } render(createElement(App), container); rerender(); }); it('Should re-mount fragment children to correct position when edge is component', () => { const f = (...xs) => createFragment(xs, 0); interface ArticlesState { articles: string[]; sections: string[]; } class Articles extends Component { state: ArticlesState; constructor() { super(); this.state = { articles: ['id2', 'id3'], sections: ['id0', 'id1'] }; } componentDidMount() { expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3
    end

    id1

    id2
    id3
    end
    2018
    1
    2
    ', ); this.setState({ sections: [] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    2018
    1
    2
    ', ); this.setState({ articles: ['id2', 'id3'], sections: ['id0', 'id1'] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3
    end

    id1

    id2
    id3
    end
    2018
    1
    2
    ', ); } render() { return f( this.state.sections.map((section) => createElement(Section, { children: [ createElement('h2', null, section), this.state.articles.map((article) => f( article === 'id2' && createElement('aside', null, 'Today'), createElement('article', null, article), ), ), ], }), ), ); } } function Section(props) { return f( createElement('section', null, props.children), createElement('div', null, 'end'), ); } function EdgeComponent() { return f( createElement('footer', null, '2018'), createElement('div', null, '1'), createElement('div', null, '2'), ); } class App extends Component { render() { return f( createElement('h1', null, 'App'), createElement(Articles), createElement(EdgeComponent), ); } } render(createElement(App), container); rerender(); }); it('Should append more fragment children to correct position when edge is component', () => { const f = (...xs) => createFragment(xs, 0); interface ArticlesState { articles: string[]; sections: string[]; } class Articles extends Component { state: ArticlesState; constructor() { super(); this.state = { articles: ['id2', 'id3'], sections: ['id0', 'id1'] }; } componentDidMount() { expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3
    end

    id1

    id2
    id3
    end
    2018
    1
    2
    ', ); this.setState({ articles: [], sections: ['id0'] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    id0

    end
    2018
    1
    2
    ', ); this.setState({ articles: ['id2', 'id3'], sections: ['id0', 'id1'] }); rerender(); expect(container.innerHTML).toEqual( '

    App

    id0

    id2
    id3
    end

    id1

    id2
    id3
    end
    2018
    1
    2
    ', ); } render() { return f( this.state.sections.map((section) => createElement(Section, { children: [ createElement('h2', null, section), this.state.articles.map((article) => f( article === 'id2' && createElement('aside', null, 'Today'), createElement('article', null, article), ), ), ], }), ), ); } } function Section(props) { return f( createElement('section', null, props.children), createElement('div', null, 'end'), ); } function EdgeComponent() { return f( createElement('footer', null, '2018'), createElement('div', null, '1'), createElement('div', null, '2'), ); } class App extends Component { render() { return f( createElement('h1', null, 'App'), createElement(Articles), createElement(EdgeComponent), ); } } render(createElement(App), container); rerender(); }); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/createElement.spec.ts ================================================ import { Component, createRef, forwardRef, Fragment, type Inferno, type RefObject, render, } from 'inferno'; import { createElement } from 'inferno-create-element'; describe('CreateElement (non-JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('Should handle events correctly when having multiple children', () => { let triggered = false; const App = () => { return createElement( 'div', null, createElement('div', { className: 'title' }, 'Example'), createElement( 'button', { type: 'button', onClick: () => { triggered = !triggered; }, }, 'Do a thing', ), ); }; render(App(), container); expect(container.innerHTML).toBe( '
    Example
    ', ); expect(triggered).toBe(false); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } expect(triggered).toBe(true); }); it('Should handle events correctly when having single child', () => { let triggered = false; const app = () => { return createElement( 'div', null, createElement( 'button', { type: 'button', onClick: () => { triggered = !triggered; }, }, 'Do a thing', ), ); }; render(app(), container); expect(container.innerHTML).toBe( '
    ', ); expect(triggered).toBe(false); const buttons = container.querySelectorAll('button'); for (const button of buttons) { button.click(); } expect(triggered).toBe(true); }); it('Should allow passing childs through "children" property (native component)', () => { const app = () => { return createElement( 'div', null, createElement('button', { type: 'button', children: ['Do a thing'], }), ); }; render(app(), container); expect(container.innerHTML).toBe( '
    ', ); }); it('Should allow passing childs through "children" property (custom component)', () => { const Button = (props) => createElement('button', props); const app = () => { return createElement( 'div', null, createElement(Button, { type: 'button', children: ['Do a thing'], }), ); }; render(app(), container); expect(container.innerHTML).toBe( '
    ', ); }); it('Should handle node with hooks and key', (done) => { const node = () => createElement('div', { key: 'key2' }, 'Hooks'); const app = createElement(node, { key: 'key1', onComponentDidMount(domNode) { expect(app.key).toBe('key1'); expect(domNode.tagName).toBe('DIV'); done(); }, }); render(app, container); expect(container.innerHTML).toBe('
    Hooks
    '); }); it('Should handle node with children but no props', () => { const node = () => createElement('div', null, 'Hooks'); const app = createElement(node, null, 'Hooks'); render(app, container); expect(container.innerHTML).toBe('
    Hooks
    '); }); it('Should handle node with refs', (done) => { let myRef: any = 'myRef'; const app = () => { const node: Inferno.StatelessComponent = () => createElement('a', { ref: (c) => (myRef = c), }); return createElement(node, { onComponentDidMount() { expect(myRef.tagName).toBe('A'); done(); }, }); }; render(createElement(app, null), container); }); describe('Fragments', () => { it('Should render Fragment with key', () => { render( createElement( Fragment, { key: 'first' }, createElement('div', null, 'Ok'), createElement('span', null, 'Test'), ), container, ); expect(container.innerHTML).toBe('
    Ok
    Test'); const div = container.querySelector('div'); const span = container.querySelector('span'); render( createElement( Fragment, { key: 'foobar' }, createElement('div', null, 'Ok'), createElement('span', null, 'Test'), ), container, ); // Verify key works expect(container.innerHTML).toBe('
    Ok
    Test'); expect(div).not.toBe(container.querySelector('div')); expect(span).not.toBe(container.querySelector('span')); }); }); it('Should be possible to forward createRef', () => { // TODO: Investigate how these refs should be typed const FancyButton = forwardRef( (props, ref: any) => createElement( 'button', { ref, className: 'FancyButton' }, props.children, ), ); expect(FancyButton.render).toBeDefined(); class Hello extends Component { btn: RefObject; constructor(props) { super(props); // You can now get a ref directly to the DOM button: this.btn = createRef(); } componentDidMount() { expect(this.btn.current).toBe(container.querySelector('button')); } render() { return createElement( FancyButton, { ref: this.btn as any }, 'Click me!', ); } } render(createElement(Hello), container); expect(container.innerHTML).toBe( '', ); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/createElementTyped.spec.tsx ================================================ import { Component, render } from 'inferno'; import { createElement } from 'inferno-create-element'; describe('CreateElement (non-JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('Should render zero children', () => { const App = () => createElement('div', null); render(App(), container); }); it('Should render null children', () => { const App = () => createElement('div', null, null); render(App(), container); }); it('Should render undefined children', () => { const App = () => createElement('div', null, undefined); render(App(), container); }); it('Should render one child', () => { const App = () => createElement( 'div', null, createElement('div', { className: 'title' }, 'Example'), ); render(App(), container); }); it('Should render multiple children', () => { const App = () => createElement( 'div', null, createElement('div', { className: 'title' }, 'Example'), createElement('button', { type: 'button' }, 'Do a thing'), ); render(App(), container); }); it('Should render array of children', () => { const App = () => createElement('div', null, [ createElement('div', { className: 'title' }, 'Example'), createElement('button', { type: 'button' }, 'Do a thing'), ]); render(App(), container); }); it('Should check component props', () => { interface MyComponentProps { className: string; } class App extends Component { public render() { return createElement( 'div', { className: this.props.className }, createElement('div', { className: 'title' }, 'Example'), createElement('hr'), ); } } render(createElement(App, { className: 'App' }), container); /** Should be an error if uncommented: */ // render(createElement(App, { className: 1 }), container); // render(createElement(App, {}), container); /** Would like to be an error, but it'd break createElement('hr'): */ render(createElement(App), container); }); it('Should check functional component props', () => { const App = ({ className }: { className: string }) => createElement( 'div', { className }, createElement('div', { className: 'title' }, 'Example'), createElement('hr'), ); render(createElement(App, { className: 'App' }), container); /** Should be an error if uncommented: */ // render(createElement(App, { className: 1 }), container); // render(createElement(App, {}), container); /** Would like to be an error but it'd break createElement('hr'): */ render(createElement(App), container); }); }); ================================================ FILE: packages/inferno-create-element/__tests__/creation.spec.ts ================================================ import { render } from 'inferno'; import { createElement } from 'inferno-create-element'; describe('Creation - (non-JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); const tests = [ { description: 'should render div with span child', template: () => { return createElement('div', null, createElement('span')); }, tagName: 'div', children: 1, textContent: '', }, { description: 'should render span with span child', template: () => createElement('span', null, createElement('span')), tagName: 'span', children: 1, textContent: '', }, { description: 'should render div with two span children', template: () => createElement('div', null, createElement('div'), createElement('div')), tagName: 'div', children: 2, textContent: '', }, { description: 'should render div with three span children and unset middle child', template: () => createElement( 'div', null, createElement('span'), null, createElement('span'), ), tagName: 'div', children: 2, textContent: '', }, { description: 'should render div with three span children and unset first, and middle child', template: () => createElement('div', null, null, null, createElement('span')), tagName: 'div', children: 1, textContent: '', }, { description: 'should render div with three span children and unset first, and middle child', template: () => createElement('div', null, null, null, null), tagName: 'div', children: 0, textContent: '', }, { description: 'should render div with two null children and one text node', template: () => createElement('div', null, null, 'Baboy', null), tagName: 'div', children: 1, textContent: 'Baboy', }, { description: 'should render div with one textNode and a span children', template: () => createElement('div', null, 'Hello!', null, createElement('span')), tagName: 'div', children: 2, textContent: 'Hello!', }, { description: 'should render div with two textNodes and a span children', template: () => createElement( 'div', null, 'Hello, ', null, 'World!', createElement('span'), ), tagName: 'div', children: 3, textContent: 'Hello, World!', }, { description: 'should render div with two textNodes and a two span children', template: () => createElement( 'div', null, 'Hello, ', createElement('span'), 'World!', createElement('span'), ), tagName: 'div', children: 4, textContent: 'Hello, World!', }, { description: 'should render div with two textNodes and one span children, and span with textNode', template: () => createElement( 'div', null, 'Hello', createElement('span'), ', ', createElement('span', null, 'World!'), ), tagName: 'div', children: 4, textContent: 'Hello, World!', }, { description: 'should render div with tree null values in an array for children', template: () => createElement('div', null, null, null, null), tagName: 'div', children: 0, textContent: '', }, { description: 'should render div with b child, and tree null values in an array for children', template: () => createElement('div', null, createElement('b', null, null, null, null)), tagName: 'div', children: 1, textContent: '', }, { description: 'should render div with b child, and number and two null values in an array for children', template: () => createElement('div', null, createElement('b', null, null, 123, null)), tagName: 'div', children: 1, textContent: '123', }, { description: 'should render empty div', template: () => createElement('div'), tagName: 'div', children: 0, textContent: '', }, { description: 'should render empty span', template: () => createElement('span'), tagName: 'span', children: 0, textContent: '', }, ]; for (const test of tests) { it(test.description, () => { render(test.template(), container); expect(container.firstChild.nodeType).toBe(1); expect(container.firstChild.tagName.toLowerCase()).toBe(test.tagName); expect(container.firstChild.childNodes.length).toBe(test.children); expect(container.firstChild.textContent).toBe(test.textContent); render(test.template(), container); expect(container.firstChild.nodeType).toBe(1); expect(container.firstChild.tagName.toLowerCase()).toBe(test.tagName); expect(container.firstChild.childNodes.length).toBe(test.children); }); } }); ================================================ FILE: packages/inferno-create-element/__tests__/elements.spec.jsx ================================================ import { render, createVNode } from 'inferno'; import { createElement } from 'inferno-create-element'; import { VNodeFlags } from 'inferno-vnode-flags'; describe('Elements (JSX)', () => { let container; beforeEach(function () { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(function () { render(null, container); container.innerHTML = ''; document.body.removeChild(container); }); it('should render a simple div', () => { render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); }); it('should render a simple div with multiple children', () => { render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(5); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(3); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(undefined, container); render(
    Hello, World!
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(3); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); }); it('should render a simple div with multiple children #2', () => { const items = [1, 2, 3]; const header = 'Hello '; render(
    {header} {items}
    , container, ); expect(container.firstChild.innerHTML).toBe('Hello 123'); render(
    {header} {[4, 5, 6]}
    , container, ); expect(container.firstChild.innerHTML).toBe('Hello 456'); }); it('should render a simple div with span child and dynamic id attribute', () => { render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('id')).toBe('hello'); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('id')).toBe(null); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('class')).toBe('hello'); // class attribute exist! render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('id')).toBe('hello'); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should render a simple div with span child and various dynamic attributes', () => { render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('id')).toBe('hello'); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('class')).toBe('hello'); render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); expect(container.innerHTML).toBe(''); }); it('should render a simple div with dynamic span child', () => { const child = ; render(
    {undefined}
    , container); render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    {null}
    , container); render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); expect(container.innerHTML).toBe(''); }); it('should render a advanced div with static child and dynamic attributes', () => { let attrs; attrs = 'id#1'; render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.getAttribute('id')).toBe('id#1'); attrs = null; render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.getAttribute('id')).toBe(null); attrs = undefined; render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(0); expect(container.firstChild.getAttribute('id')).toBe(null); attrs = 'id#4'; render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.getAttribute('id')).toBe('id#4'); attrs = 13 - (44 * 4) / 4; let b = Hello, World!; let n = {b}; render(
    {n}
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('class')).toBe('Hello, World!'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); expect(container.firstChild.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.firstChild.getAttribute('id')).toBe( '-31', ); expect(container.firstChild.firstChild.firstChild.firstChild.nodeName).toBe( 'N', ); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild.nodeName, ).toBe('B'); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild .innerHTML, ).toBe('Hello, World!'); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild.getAttribute( 'class', ), ).toBe('123'); attrs = 13 - (44 * 4) / 4; b = Hello, World!; n = {b}; render(
    {n}
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('class')).toBe('Hello, World!'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); expect(container.firstChild.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.firstChild.getAttribute('id')).toBe( '-31', ); expect(container.firstChild.firstChild.firstChild.firstChild.nodeName).toBe( 'N', ); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild.nodeName, ).toBe('B'); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild .innerHTML, ).toBe('Hello, World!'); expect( container.firstChild.firstChild.firstChild.firstChild.firstChild.getAttribute( 'class', ), ).toBe('1243'); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); attrs = 'id#444'; render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('class')).toBe('Hello, Dominic'); expect(container.firstChild.getAttribute('id')).toBe('id#444'); expect(container.firstChild.firstChild.getAttribute('id')).toBe('id#444'); attrs = 'id#' + 333 - 333 / 3; render(
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('class')).toBe('Hello, Dominic'); expect(container.firstChild.getAttribute('id')).toBe('NaN'); expect(container.firstChild.firstChild.getAttribute('id')).toBe('NaN'); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should render a simple div with dynamic span child and update to div child', () => { let child = ; render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    , container); child =
    ; render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); child = (
    ); render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.childNodes.length).toBe(9); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.firstChild.nodeName).toBe('DIV'); child =
    Hello, World!
    ; render(
    {child}
    , container); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.innerHTML).toBe('Hello, World!'); render(
    {null}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); render(
    , container); expect(container.firstChild.nodeName).toBe('DIV'); }); it('should render and unset a simple div with dynamic span child', () => { let child; child = ( ); render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.childNodes.length).toBe(2); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); expect(container.firstChild.firstChild.firstChild.nodeName).toBe('SPAN'); render(
    {null}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); const divs =
    ; child = ( {divs} ); render(
    {child}
    , container); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.childNodes.length).toBe(1); expect(container.firstChild.firstChild.nodeName).toBe('SPAN'); expect(container.firstChild.firstChild.firstChild.nodeName).toBe('SPAN'); expect(container.firstChild.firstChild.firstChild.firstChild.nodeName).toBe( 'DIV', ); }); it('should render a simple div children set to undefined', () => { render(
    {undefined}
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe(''); render(
    {undefined}
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe(''); }); it('should render a simple div children set to null', () => { render(
    {null}
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe(''); render(
    {null}
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe(''); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should render a simple div children set to null', () => { render(
    {null}
    , container, ); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.textContent).toBe(''); render(
    {null}
    , container, ); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.firstChild.textContent).toBe(''); }); it('should render a double div and a text node', () => { render(
    {
    Hello, World!
    }
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe('Hello, World!'); render(
    {null}
    , container); render(
    {
    Hello, Inferno!
    }
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe('Hello, Inferno!'); }); it('should render a single div with text node', () => { render(
    , container, ); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe(''); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should render a simple div with a text node', () => { render(
    Hello, world!
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe('Hello, world!'); render(
    Hello, world! 2
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.textContent).toBe('Hello, world! 2'); }); it('should render a simple div with attributes', () => { render(
    Hello, world!
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('id')).toBe('123'); expect(container.firstChild.textContent).toBe('Hello, world!'); render(
    Hello, world! 2
    , container); expect(container.nodeName).toBe('DIV'); expect(container.firstChild.getAttribute('id')).toBe('foo'); expect(container.firstChild.textContent).toBe('Hello, world! 2'); }); it('should render a simple div with inline style', () => { render(
    Hello, world!
    , container, ); expect(container.nodeName).toBe('DIV'); render(
    Hello, world! 2
    , container); expect(container.nodeName).toBe('DIV'); // unset render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should render "className" attribute', () => { render(
    , container); expect(container.firstChild.getAttribute('class')).toEqual('123'); render(
    , container); expect(container.firstChild.className).toEqual(''); render(
    , container); expect(container.firstChild.className).toEqual(''); render(
    , container); expect(container.firstChild.className).toEqual('Inferno rocks!'); expect(container.firstChild.innerHTML).toBe(''); }); it("shouldn't render null value", () => { render(, container); expect(container.value).toBe(undefined); expect(container.innerHTML).toBe(''); render(, container); expect(container.value).toBe(undefined); render(, container); expect(container.value).toBe(undefined); expect(container.innerHTML).toBe(''); render(null, container); expect(container.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(0); }); it('should set values as properties by default', () => { render(, container); expect(container.firstChild.getAttribute('title')).toEqual('Tip!'); expect(container.innerHTML).toBe(''); render(, container); expect(container.firstChild.getAttribute('name')).toEqual('Tip!'); expect(container.innerHTML).toBe(''); render(, container); expect(container.firstChild.getAttribute('title')).toEqual('Tip!'); expect(container.innerHTML).toBe(''); }); it('should render a simple div with dynamic values and props', () => { let val1, val2; val1 = 'Inferno'; val2 = 'Sucks!'; render(
    {val1} {val2}
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(1); expect(container.childNodes[0].childNodes[0].getAttribute('class')).toEqual( 'bar', ); expect(container.childNodes[0].childNodes[0].textContent).toEqual( 'Inferno', ); expect(container.childNodes[0].childNodes[1].getAttribute('class')).toEqual( 'yar', ); expect(container.childNodes[0].childNodes[1].textContent).toEqual('Sucks!'); render(
    {val1} {val2}
    , container, ); expect(container.firstChild.nodeName).toBe('DIV'); expect(container.childNodes.length).toBe(1); expect(container.childNodes[0].childNodes[0].getAttribute('class')).toEqual( 'bar', ); expect(container.childNodes[0].childNodes[0].textContent).toEqual( 'Inferno', ); expect(container.childNodes[0].childNodes[1].getAttribute('class')).toEqual( 'yar', ); expect(container.childNodes[0].childNodes[1].textContent).toEqual('Sucks!'); }); it('should properly render a input with download attribute', () => { let val1; val1 = 'false'; render(, container); expect(container.firstChild.nodeName).toBe('INPUT'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('download')).toBe('false'); val1 = 'true'; render(, container); expect(container.firstChild.nodeName).toBe('INPUT'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('download')).toBe('true'); }); it('should properly render "className" property on a custom element', () => { render(, container); expect(container.firstChild.nodeName).toBe('CUSTOM-ELEM'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('class')).toBe('Hello, world!'); render(, container); expect(container.firstChild.nodeName).toBe('CUSTOM-ELEM'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('class')).toBe('Hello, world!'); }); it('should properly render "width" and "height" attributes', () => { render(Smiley face, container); expect(container.firstChild.nodeName).toBe('IMG'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('src')).toBe(''); expect(container.firstChild.getAttribute('alt')).toBe('Smiley face'); expect(container.firstChild.getAttribute('height')).toBe('42'); expect(container.firstChild.getAttribute('width')).toBe('42'); render( Smiley face, container, ); expect(container.firstChild.nodeName).toBe('IMG'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('src')).toBe(''); expect(container.firstChild.getAttribute('alt')).toBe('Smiley face'); expect(container.firstChild.getAttribute('height')).toBe('42'); expect(container.firstChild.getAttribute('width')).toBe('42'); }); it('should properly render "width" and "height" attributes #2', () => { render( , container, ); expect(container.firstChild.nodeName).toBe('INPUT'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('type')).toBe('file'); let multipleValue = container.firstChild.multiple; // Inferno sets multiple using dom property always to boolean, // but some browsers fe. IE9 still set it as multiple="multiple" which also works as expected if (typeof multipleValue === 'string') { expect(multipleValue).toBe('multiple'); } else { expect(multipleValue).toBe(true); } expect(container.firstChild.capture).toBeTruthy(); // true and "true" are both valid // expect(container.firstChild.getAttribute('accept')).toBe('image/*'); render( , container, ); expect(container.firstChild.nodeName).toBe('INPUT'); expect(container.childNodes.length).toBe(1); expect(container.firstChild.getAttribute('type')).toBe('file'); multipleValue = container.firstChild.multiple; // Inferno sets multiple using dom property always to boolean, // but some browsers fe. IE9 still set it as multiple="multiple" which also works as expected if (typeof multipleValue === 'string') { expect(multipleValue).toBe('multiple'); } else { expect(multipleValue).toBe(true); } expect(container.firstChild.capture).toBeTruthy(); // true and "true" are both valid; // expect(container.firstChild.getAttribute('accept')).toBe('image/*'); }); it('should handle className', () => { render(
    , container); expect(container.firstChild.className).toBe('foo'); render(
    , container); expect(container.firstChild.className).toBe('bar'); render(
    , container); expect(container.firstChild.className).toBe(''); render(
    , container); expect(container.firstChild.className).toBe(''); render(, container); expect(container.firstChild.getAttribute('class')).toBe('fooBar'); }); it('should remove attributes', () => { render(, container); expect(container.firstChild.hasAttribute('height')).toBe(true); render(, container); expect(container.firstChild.hasAttribute('height')).toBe(false); render(, container); expect(container.firstChild.hasAttribute('height')).toBe(false); }); it('should remove properties #2', () => { render(
    , container); expect(container.firstChild.getAttribute('class')).toBe('monkey'); render(
    , container); expect(container.firstChild.className).toBe(''); render(, container); expect(container.firstChild.getAttribute('class')).toBe('monkey'); render(, container); expect(container.firstChild.getAttribute('class')).toBe(null); }); it('should not update when switching between null/undefined', () => { render(
    , container); render(
    , container); render(
    , container); render(
    , container); render(
    , container); render(
    , container); render(
    , container); }); it('should render an iframe', () => { render(