From 051f10c59d14229520f14a531a4de79162e18c02 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 9 Jul 2023 09:57:45 +0200 Subject: [PATCH 01/11] 10.16.0 (#4063) --- devtools/src/devtools.js | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js index c4470482b6..670d45b6df 100644 --- a/devtools/src/devtools.js +++ b/devtools/src/devtools.js @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact'; export function initDevTools() { if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) { - window.__PREACT_DEVTOOLS__.attachPreact('10.15.1', options, { + window.__PREACT_DEVTOOLS__.attachPreact('10.16.0', options, { Fragment, Component }); diff --git a/package-lock.json b/package-lock.json index 46fcee73cb..7876cfe36f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "preact", - "version": "10.15.1", + "version": "10.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "preact", - "version": "10.15.1", + "version": "10.16.0", "license": "MIT", "devDependencies": { "@actions/github": "^5.0.0", diff --git a/package.json b/package.json index 5d435f6f9d..346e6d4c1b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "preact", "amdName": "preact", - "version": "10.15.1", + "version": "10.16.0", "private": false, "description": "Fast 3kb React-compatible Virtual DOM library.", "main": "dist/preact.js", From e16b520eadac9f91a32c645a2447036b73ac98f4 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Tue, 11 Jul 2023 07:47:57 +0200 Subject: [PATCH 02/11] fix react-frame-component by supporting nullish portals (#3896) --- compat/src/portals.js | 61 ++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/compat/src/portals.js b/compat/src/portals.js index 80d70e76e9..521174ce4d 100644 --- a/compat/src/portals.js +++ b/compat/src/portals.js @@ -31,43 +31,34 @@ function Portal(props) { _this.componentWillUnmount(); } - // When props.vnode is undefined/false/null we are dealing with some kind of - // conditional vnode. This should not trigger a render. - if (props._vnode) { - if (!_this._temp) { - _this._container = container; + if (!_this._temp) { + _this._container = container; - // Create a fake DOM parent node that manages a subset of `container`'s children: - _this._temp = { - nodeType: 1, - parentNode: container, - childNodes: [], - appendChild(child) { - this.childNodes.push(child); - _this._container.appendChild(child); - }, - insertBefore(child, before) { - this.childNodes.push(child); - _this._container.appendChild(child); - }, - removeChild(child) { - this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1); - _this._container.removeChild(child); - } - }; - } - - // Render our wrapping element into temp. - render( - createElement(ContextProvider, { context: _this.context }, props._vnode), - _this._temp - ); - } - // When we come from a conditional render, on a mounted - // portal we should clear the DOM. - else if (_this._temp) { - _this.componentWillUnmount(); + // Create a fake DOM parent node that manages a subset of `container`'s children: + _this._temp = { + nodeType: 1, + parentNode: container, + childNodes: [], + appendChild(child) { + this.childNodes.push(child); + _this._container.appendChild(child); + }, + insertBefore(child, before) { + this.childNodes.push(child); + _this._container.appendChild(child); + }, + removeChild(child) { + this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1); + _this._container.removeChild(child); + } + }; } + + // Render our wrapping element into temp. + render( + createElement(ContextProvider, { context: _this.context }, props._vnode), + _this._temp + ); } /** From 2e71176853003171e8dee837e4f7c5220af67ea1 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 11 Jul 2023 10:08:31 +0200 Subject: [PATCH 03/11] Switch default branch to `main` --- .github/workflows/benchmarks.yml | 6 +++--- .github/workflows/ci.yml | 2 +- .github/workflows/pr-reporter.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/saucelabs.yml | 2 +- CONTRIBUTING.md | 10 +++++----- README.md | 4 ++-- .../proxy-packages/preact-master-proxy/package.json | 2 +- benches/scripts/bench.js | 2 +- benches/scripts/config.js | 7 +++---- benches/scripts/index.js | 6 +++--- sizereport.config.js | 2 +- 12 files changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 282726443c..cdc3a9f132 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -9,7 +9,7 @@ on: - 'src/**.js' push: branches: - - master + - main - restructure paths: - 'src/**.js' @@ -41,12 +41,12 @@ jobs: artifact: npm-package workflow: ci.yml required: false - - run: mv preact.tgz preact-master.tgz + - run: mv preact.tgz preact-main.tgz - name: Upload base preact package uses: actions/upload-artifact@v3 with: name: bench-environment - path: preact-master.tgz + path: preact-main.tgz bench_todo: name: Bench todo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4e4a9b3d0..6790d7c4bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: - '**' push: branches: - - master + - main - restructure jobs: diff --git a/.github/workflows/pr-reporter.yml b/.github/workflows/pr-reporter.yml index b5e2f1ba90..18c1311b60 100644 --- a/.github/workflows/pr-reporter.yml +++ b/.github/workflows/pr-reporter.yml @@ -46,6 +46,6 @@ jobs: uses: andrewiggins/tachometer-reporter-action@v2 with: path: results/*.json - base-bench-name: preact-master + base-bench-name: preact-main pr-bench-name: preact-local summarize: 'duration, usedJSHeapSize' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cba03a0498..5bab739ed1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,7 @@ on: create jobs: build: if: github.ref_type == 'tag' - uses: preactjs/preact/.github/workflows/ci.yml@master + uses: preactjs/preact/.github/workflows/ci.yml@main release: runs-on: ubuntu-latest diff --git a/.github/workflows/saucelabs.yml b/.github/workflows/saucelabs.yml index 049a006b05..ec92294d94 100644 --- a/.github/workflows/saucelabs.yml +++ b/.github/workflows/saucelabs.yml @@ -3,7 +3,7 @@ name: Saucelabs on: push: branches: - - master + - main - restructure jobs: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54fefe4f6c..851550025c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Unique to Preact we do support several ways to hook into our renderer. All our a ## Important Branches -We merge every PR into the `master` branch which is the one that we'll use to publish code to npm. For the previous Preact release line we have a branch called `8` which is in maintenance mode. As a new contributor you won't have to deal with that ;) +We merge every PR into the `main` branch which is the one that we'll use to publish code to npm. For the previous Preact release line we have a branch called `8` which is in maintenance mode. As a new contributor you won't have to deal with that ;) ## Creating your first Pull-Request @@ -82,7 +82,7 @@ We try to make it as easy as possible to contribute to Preact and make heavy use Once a PR or a Draft PR has been created our community typically joins the discussion about the proposed change. Sometimes that includes ideas for test cases or even different ways to go about implementing a feature. Often this also includes ideas on how to make the code smaller. We usually refer to the latter as "code-golfing" or just "golfing". -When everything is good to go someone will approve the PR and the changes will be merged into the `master` branch and we usually cut a release a few days/ a week later. +When everything is good to go someone will approve the PR and the changes will be merged into the `main` branch and we usually cut a release a few days/ a week later. _The big takeaway for you here is, that we will guide you along the way. We're here to help to make a PR ready for approval!_ @@ -90,7 +90,7 @@ The short summary is: 1. Make changes and submit a PR 2. Modify change according to feedback (if there is any) -3. PR will be merged into `master` +3. PR will be merged into `main` 4. A new release will be cut (every 2-3 weeks). ## Commonly used scripts for contributions @@ -172,7 +172,7 @@ rights to publish new releases on npm. 1. Make a PR where **only** the version number is incremented in `package.json` and everywhere else. A simple search and replace works. (note: We follow `SemVer` conventions) 2. Wait until the PR is approved and merged. -3. Switch back to the `master` branch and pull the merged PR +3. Switch back to the `main` branch and pull the merged PR 4. Create and push a tag for the new version you want to publish: 1. `git tag 10.0.0` 2. `git push --tags` @@ -195,7 +195,7 @@ rights to publish new releases on npm. 1. I'd recommend writing them in an offline editor because each edit to a draft will change the URL in GitHub. 2. Make a PR where **only** the version number is incremented in `package.json` (note: We follow `SemVer` conventions) 3. Wait until the PR is approved and merged. -4. Switch back to the `master` branch and pull the merged PR +4. Switch back to the `main` branch and pull the merged PR 5. Run `npm run build && npm publish` 1. Make sure you have 2FA enabled in npm, otherwise the above command will fail. 2. If you're doing a pre-release add `--tag next` to the `npm publish` command to publish it under a different tag (default is `latest`) diff --git a/README.md b/README.md index 8795a0e781..e27535a579 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ [![OpenCollective Backers](https://opencollective.com/preact/backers/badge.svg)](#backers) [![OpenCollective Sponsors](https://opencollective.com/preact/sponsors/badge.svg)](#sponsors) -[![coveralls](https://img.shields.io/coveralls/preactjs/preact/master.svg)](https://coveralls.io/github/preactjs/preact) +[![coveralls](https://img.shields.io/coveralls/preactjs/preact/main.svg)](https://coveralls.io/github/preactjs/preact) [![gzip size](http://img.badgesize.io/https://unpkg.com/preact/dist/preact.min.js?compression=gzip&label=gzip)](https://unpkg.com/preact/dist/preact.min.js) [![brotli size](http://img.badgesize.io/https://unpkg.com/preact/dist/preact.min.js?compression=brotli&label=brotli)](https://unpkg.com/preact/dist/preact.min.js) @@ -185,6 +185,6 @@ MIT [![Preact](https://i.imgur.com/YqCHvEW.gif)](https://preactjs.com) -[preact/compat]: https://github.com/preactjs/preact/tree/master/compat +[preact/compat]: https://github.com/preactjs/preact/tree/main/compat [hyperscript]: https://github.com/dominictarr/hyperscript [DevTools]: https://github.com/preactjs/preact-devtools diff --git a/benches/proxy-packages/preact-master-proxy/package.json b/benches/proxy-packages/preact-master-proxy/package.json index 57f641badf..f34ced6c6a 100644 --- a/benches/proxy-packages/preact-master-proxy/package.json +++ b/benches/proxy-packages/preact-master-proxy/package.json @@ -5,6 +5,6 @@ "type": "module", "main": "index.js", "dependencies": { - "preact": "file:../../../preact-master.tgz" + "preact": "file:../../../preact-main.tgz" } } diff --git a/benches/scripts/bench.js b/benches/scripts/bench.js index f9f5119265..a5513280e0 100644 --- a/benches/scripts/bench.js +++ b/benches/scripts/bench.js @@ -21,7 +21,7 @@ export const defaultBenchOptions = { // GitHub Action minutes timeout: 1, 'window-size': '1024,768', - framework: IS_CI ? ['preact-master', 'preact-local', 'preact-hooks'] : null, + framework: IS_CI ? ['preact-main', 'preact-local', 'preact-hooks'] : null, trace: false }; diff --git a/benches/scripts/config.js b/benches/scripts/config.js index 6897a3a364..cbc2809df0 100644 --- a/benches/scripts/config.js +++ b/benches/scripts/config.js @@ -49,14 +49,13 @@ export const frameworks = [ } }, { - label: 'preact-master', + label: 'preact-main', dependencies: { - framework: - 'file:' + repoRoot('benches/proxy-packages/preact-master-proxy') + framework: 'file:' + repoRoot('benches/proxy-packages/preact-main-proxy') }, async isValid() { try { - await stat(repoRoot('preact-master.tgz')); + await stat(repoRoot('preact-main.tgz')); return validateFileDep(this.dependencies.framework); } catch (e) { return false; diff --git a/benches/scripts/index.js b/benches/scripts/index.js index 4eeb21a898..e66ddbcd58 100644 --- a/benches/scripts/index.js +++ b/benches/scripts/index.js @@ -30,7 +30,7 @@ prog .example('bench text*') .example('bench *.html') .example('bench all') - .example('bench many* -f preact-local -f preact-master') + .example('bench many* -f preact-local -f preact-main') .option( '--browser, -b', 'Which browsers to launch in automatic mode, comma-delimited (chrome, chrome-headless, firefox, firefox-headless, safari, edge, ie)', @@ -72,7 +72,7 @@ prog // Tests: // - (no args) // - many* -// - many* -f preact-local -f preact-master +// - many* -f preact-local -f preact-main prog .command('deopts [benchmark]') .describe( @@ -81,7 +81,7 @@ prog .example('deopts many_updates.html') .example('deopts many*') .example('deopts many* -f preact-local') - .example('deopts many* -f preact-local -f preact-master') + .example('deopts many* -f preact-local -f preact-main') .option( '--framework, -f', 'The framework to run the benchmark with.', diff --git a/sizereport.config.js b/sizereport.config.js index 36c9d2dfce..9511c8cbd6 100644 --- a/sizereport.config.js +++ b/sizereport.config.js @@ -1,5 +1,5 @@ module.exports = { repo: 'preactjs/preact', path: ['./{compat,debug,hooks,}/dist/**/!(*.map)'], - branch: 'master' + branch: 'main' }; From 98f6c4237fd088264e9bd9b61ba1e3d11902d9f3 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 11 Jul 2023 10:16:54 +0200 Subject: [PATCH 04/11] Fix IE11 attribute test by sorting them --- compat/test/browser/render.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compat/test/browser/render.test.js b/compat/test/browser/render.test.js index 32404715c3..7187589997 100644 --- a/compat/test/browser/render.test.js +++ b/compat/test/browser/render.test.js @@ -10,7 +10,8 @@ import { setupScratch, teardown, serializeHtml, - createEvent + createEvent, + sortAttributes } from '../../../test/_util/helpers'; describe('compat render', () => { @@ -252,7 +253,7 @@ describe('compat render', () => { ); }); - it('shouldnot transform imageSrcSet', () => { + it('should not transform imageSrcSet', () => { render( { />, scratch ); - expect(scratch.innerHTML).to.equal( - '' + expect(sortAttributes(scratch.innerHTML)).to.equal( + '' ); }); From 2258d7ddec54dc6a89dfdea0a9eb7e8708a0052b Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 12 Jul 2023 08:01:21 +0200 Subject: [PATCH 05/11] fix event typings (#4066) --- src/jsx.d.ts | 6 +++--- test/ts/Component-test.tsx | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 26b91cd81a..42f7e44944 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -1076,9 +1076,9 @@ export namespace JSXInternal { export type TargetedPictureInPictureEvent = TargetedEvent; - export interface EventHandler { - (this: void, event: E): void; - } + export type EventHandler = { + bivarianceHack(event: E): void; + }['bivarianceHack']; export type AnimationEventHandler = EventHandler< TargetedAnimationEvent diff --git a/test/ts/Component-test.tsx b/test/ts/Component-test.tsx index f923fcd37e..ee133c9260 100644 --- a/test/ts/Component-test.tsx +++ b/test/ts/Component-test.tsx @@ -122,6 +122,19 @@ function Mapper() { return [1, 2, 3].map(x => ); } +class Button extends Component { + handleClick(this: HTMLButtonElement, event: MouseEvent) { + event.preventDefault(); + if (event.target instanceof HTMLElement) { + console.log(event.target.localName); + } + } + + render() { + return ; + } +} + describe('Component', () => { const component = new SimpleComponent({ initialName: 'da name' }); From 34e37c5691789c2af50776d011b5e0ffec4fae1e Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 14 Jul 2023 09:18:58 +0200 Subject: [PATCH 06/11] reduce stack size of try catch by excluding non components (#4067) * reduce stack size of try catch by excluding non components * reduce stack size * Revert "reduce stack size" This reverts commit 644c0a3be8fdd824bce12d0cc7a19921d811c1fb. --- src/diff/index.js | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/diff/index.js b/src/diff/index.js index 2537a1b35b..2cd24bbb0f 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -53,8 +53,8 @@ export function diff( if ((tmp = options._diff)) tmp(newVNode); - try { - outer: if (typeof newType == 'function') { + outer: if (typeof newType == 'function') { + try { let c, isNew, oldProps, oldState, snapshot, clearProcessingException; let newProps = newVNode.props; @@ -257,39 +257,39 @@ export function diff( if (clearProcessingException) { c._pendingError = c._processingException = null; } - } else if ( - excessDomChildren == null && - newVNode._original === oldVNode._original - ) { - newVNode._children = oldVNode._children; - newVNode._dom = oldVNode._dom; - } else { - newVNode._dom = diffElementNodes( - oldVNode._dom, - newVNode, - oldVNode, - globalContext, - isSvg, - excessDomChildren, - commitQueue, - isHydrating, - refQueue - ); - } - - if ((tmp = options.diffed)) tmp(newVNode); - } catch (e) { - newVNode._original = null; - // if hydrating or creating initial tree, bailout preserves DOM: - if (isHydrating || excessDomChildren != null) { - newVNode._dom = oldDom; - newVNode._hydrating = !!isHydrating; - excessDomChildren[excessDomChildren.indexOf(oldDom)] = null; - // ^ could possibly be simplified to: - // excessDomChildren.length = 0; + } catch (e) { + newVNode._original = null; + // if hydrating or creating initial tree, bailout preserves DOM: + if (isHydrating || excessDomChildren != null) { + newVNode._dom = oldDom; + newVNode._hydrating = !!isHydrating; + excessDomChildren[excessDomChildren.indexOf(oldDom)] = null; + // ^ could possibly be simplified to: + // excessDomChildren.length = 0; + } + options._catchError(e, newVNode, oldVNode); } - options._catchError(e, newVNode, oldVNode); + } else if ( + excessDomChildren == null && + newVNode._original === oldVNode._original + ) { + newVNode._children = oldVNode._children; + newVNode._dom = oldVNode._dom; + } else { + newVNode._dom = diffElementNodes( + oldVNode._dom, + newVNode, + oldVNode, + globalContext, + isSvg, + excessDomChildren, + commitQueue, + isHydrating, + refQueue + ); } + + if ((tmp = options.diffed)) tmp(newVNode); } /** From 5a56a830b6abf23009d7e2b5a0d0b7707a86a8e8 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Mon, 17 Jul 2023 09:54:16 +0200 Subject: [PATCH 07/11] add missing SvgProps and make the generics mandatory (#4071) --- compat/src/index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index 28125420bc..339b26c623 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -65,9 +65,13 @@ declare namespace React { export function startTransition(cb: () => void): void; // HTML - export import HTMLAttributes = JSXInternal.HTMLAttributes; + export interface HTMLAttributes + extends JSXInternal.HTMLAttributes {} export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps; export import CSSProperties = JSXInternal.CSSProperties; + export interface SVGProps + extends JSXInternal.SVGAttributes, + preact.ClassAttributes {} // Events export import TargetedEvent = JSXInternal.TargetedEvent; From 3e43ba1de61285cd60131e8561935e00abbbbef5 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Fri, 21 Jul 2023 20:14:55 -0500 Subject: [PATCH 08/11] fix: Re-export `ComponentChild` as `ReactNode` in compat's types --- compat/src/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index 339b26c623..fcc973b9b1 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -53,6 +53,7 @@ declare namespace React { export import createElement = preact.createElement; export import cloneElement = preact.cloneElement; export import ComponentProps = preact.ComponentProps; + export import ReactNode = preact.ComponentChild; // Suspense export import Suspense = _Suspense.Suspense; From 7940137a1be101bf42dc2409ed27ed2aca6cb5ac Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 3 Aug 2023 16:23:35 +0200 Subject: [PATCH 09/11] fix bneches (#4089) --- .../{preact-master-proxy => preact-main-proxy}/index.js | 0 .../{preact-master-proxy => preact-main-proxy}/package.json | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename benches/proxy-packages/{preact-master-proxy => preact-main-proxy}/index.js (100%) rename benches/proxy-packages/{preact-master-proxy => preact-main-proxy}/package.json (100%) diff --git a/benches/proxy-packages/preact-master-proxy/index.js b/benches/proxy-packages/preact-main-proxy/index.js similarity index 100% rename from benches/proxy-packages/preact-master-proxy/index.js rename to benches/proxy-packages/preact-main-proxy/index.js diff --git a/benches/proxy-packages/preact-master-proxy/package.json b/benches/proxy-packages/preact-main-proxy/package.json similarity index 100% rename from benches/proxy-packages/preact-master-proxy/package.json rename to benches/proxy-packages/preact-main-proxy/package.json From c294e8b114916cebe64cf929d8668190b5bd27e7 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 3 Aug 2023 16:33:36 +0200 Subject: [PATCH 10/11] fix(child-diffing): avoid skipping re-orders (#4088) * add test * fix * remove unusued import * see if test is failing because of the tgz missing * Revert "see if test is failing because of the tgz missing" This reverts commit 02ab119511e191e6e9bb24761ef63cf382784980. --- src/diff/children.js | 11 +++---- test/browser/keys.test.js | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/diff/children.js b/src/diff/children.js index 90afbaf65e..eb88dc0f74 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -144,7 +144,6 @@ export function diffChildren( ); newDom = childVNode._dom; - if ((j = childVNode.ref) && oldVNode.ref != j) { if (oldVNode.ref) { applyRef(oldVNode.ref, null, childVNode); @@ -158,7 +157,6 @@ export function diffChildren( } let isMounting = oldVNode === EMPTY_OBJ || oldVNode._original === null; - let hasMatchingIndex = !isMounting && matchingIndex === skewedIndex; if (isMounting) { if (matchingIndex == -1) { skew--; @@ -166,11 +164,9 @@ export function diffChildren( } else if (matchingIndex !== skewedIndex) { if (matchingIndex === skewedIndex + 1) { skew++; - hasMatchingIndex = true; } else if (matchingIndex > skewedIndex) { if (remainingOldChildren > newChildrenLength - skewedIndex) { skew += matchingIndex - skewedIndex; - hasMatchingIndex = true; } else { // ### Change from keyed: I think this was missing from the algo... skew--; @@ -187,8 +183,6 @@ export function diffChildren( } skewedIndex = i + skew; - hasMatchingIndex = - hasMatchingIndex || (matchingIndex == i && !isMounting); if ( typeof childVNode.type == 'function' && @@ -196,7 +190,10 @@ export function diffChildren( oldVNode._children === childVNode._children) ) { oldDom = reorderChildren(childVNode, oldDom, parentDom); - } else if (typeof childVNode.type != 'function' && !hasMatchingIndex) { + } else if ( + typeof childVNode.type != 'function' && + (matchingIndex !== skewedIndex || isMounting) + ) { oldDom = placeChild(parentDom, newDom, oldDom); } else if (childVNode._nextDom !== undefined) { // Only Fragments or components that return Fragment like VNodes will diff --git a/test/browser/keys.test.js b/test/browser/keys.test.js index 118efbcf82..7bc883418d 100644 --- a/test/browser/keys.test.js +++ b/test/browser/keys.test.js @@ -752,4 +752,67 @@ describe('keys', () => { expect(Stateful1Ref).to.not.equal(Stateful1MovedRef); expect(Stateful2Ref).to.not.equal(Stateful2MovedRef); }); + + it('should handle full reorders', () => { + const keys = { + Apple: `Apple_1`, + Orange: `Orange_1`, + Banana: `Banana_1`, + Grape: `Grape_1`, + Kiwi: `Kiwi_1`, + Cherry: `Cherry_1` + }; + + let sort; + + class App extends Component { + order; + + state = { items: ['Apple', 'Grape', 'Cherry', 'Orange', 'Banana'] }; + + sort() { + this.order = this.order === 'ASC' ? 'DESC' : 'ASC'; + const items = [...this.state.items].sort((a, b) => + this.order === 'ASC' ? a.localeCompare(b) : b.localeCompare(a) + ); + this.setState({ items }); + } + + render(_, { items }) { + sort = this.sort.bind(this); + return ( +
+ {items.map(item => ( +
{item}
+ ))} +
+ ); + } + } + + const expected = values => { + return values.map(key => `
${key}
`).join(''); + }; + + render(, scratch); + expect(scratch.innerHTML).to.eq( + `
${expected(['Apple', 'Grape', 'Cherry', 'Orange', 'Banana'])}
` + ); + + let sorted = ['Apple', 'Grape', 'Cherry', 'Orange', 'Banana'].sort((a, b) => + a.localeCompare(b) + ); + sort(); + rerender(); + + expect(scratch.innerHTML).to.eq(`
${expected(sorted)}
`); + + sorted = ['Apple', 'Grape', 'Cherry', 'Orange', 'Banana'].sort((a, b) => + b.localeCompare(a) + ); + sort(); + rerender(); + + expect(scratch.innerHTML).to.eq(`
${expected(sorted)}
`); + }); }); From d8f2fb311bff23e4f8599e7cc0170a74374613b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B3=A2=E6=AF=94=E5=B0=8F=E9=87=91=E5=88=9A?= <2890636389@qq.com> Date: Thu, 3 Aug 2023 22:40:08 +0800 Subject: [PATCH 11/11] feature: support isFragment (#4042) * feat: support isFragment * feat: add unit test * feat: CR fixed --------- Co-authored-by: bobihuang Co-authored-by: Jovi De Croock --- compat/src/index.d.ts | 1 + compat/src/index.js | 11 +++++++++++ compat/test/browser/isFragment.test.js | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 compat/test/browser/isFragment.test.js diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index fcc973b9b1..f0d49f393b 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -107,6 +107,7 @@ declare namespace React { ...children: preact.ComponentChildren[] ) => preact.VNode; export function isValidElement(element: any): boolean; + export function isFragment(element: any): boolean; export function findDOMNode( component: preact.Component | Element ): Element | null; diff --git a/compat/src/index.js b/compat/src/index.js index 500af48aac..2a2e569c1a 100644 --- a/compat/src/index.js +++ b/compat/src/index.js @@ -54,6 +54,15 @@ function isValidElement(element) { return !!element && element.$$typeof === REACT_ELEMENT_TYPE; } +/** + * Check if the passed element is a Fragment node. + * @param {*} element The element to check + * @returns {boolean} + */ +function isFragment(element) { + return isValidElement(element) && element.type === Fragment; +} + /** * Wrap `cloneElement` to abort if the passed element is not a valid element and apply * all vnode normalizations. @@ -185,6 +194,7 @@ export { createRef, Fragment, isValidElement, + isFragment, findDOMNode, Component, PureComponent, @@ -231,6 +241,7 @@ export default { createRef, Fragment, isValidElement, + isFragment, findDOMNode, Component, PureComponent, diff --git a/compat/test/browser/isFragment.test.js b/compat/test/browser/isFragment.test.js new file mode 100644 index 0000000000..2e7ebfbc5a --- /dev/null +++ b/compat/test/browser/isFragment.test.js @@ -0,0 +1,22 @@ +import { createElement as preactCreateElement, Fragment } from 'preact'; +import React, { isFragment } from 'preact/compat'; + +describe('isFragment', () => { + it('should check return false for invalid arguments', () => { + expect(isFragment(null)).to.equal(false); + expect(isFragment(false)).to.equal(false); + expect(isFragment(true)).to.equal(false); + expect(isFragment('foo')).to.equal(false); + expect(isFragment(123)).to.equal(false); + expect(isFragment([])).to.equal(false); + expect(isFragment({})).to.equal(false); + }); + + it('should detect a preact vnode', () => { + expect(isFragment(preactCreateElement(Fragment, {}))).to.equal(true); + }); + + it('should detect a compat vnode', () => { + expect(isFragment(React.createElement(Fragment, {}))).to.equal(true); + }); +});