Repository: tailwindlabs/headlessui
Pull request #3734 (2025-05-20 13:21:44)

Fix `Combobox` issue when using virtual mode
This PR fixes an issue with the `Combobox` component when using the `virtual` mode.

We recently already fixed this issue (https://github.com/tailwindlabs/headlessui/pull/3678) by applying a patch on the `@tanstack/virtual-core` package. But this was only applied in cjs builds, not in the esm builds.

Instead of trying to fix it in our build process, we decided to just fix it upstream (https://github.com/TanStack/virtual/pull/1004) and this PR bumps the version of `@tanstack/virtual-core` to the latest version (which includes the fix).



Patch:
diff --git a/package-lock.json b/package-lock.json
index 5219f6f..d24b791 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,6 @@
     "": {
       "name": "headlessui",
       "version": "0.0.0",
-      "hasInstallScript": true,
       "license": "MIT",
       "workspaces": [
         "packages/*",
@@ -26,7 +25,6 @@
         "jest": "26",
         "lint-staged": "^12.2.1",
         "npm-run-all": "^4.1.5",
-        "patch-package": "^8.0.0",
         "prettier": "^3.1.0",
         "prettier-plugin-organize-imports": "^3.2.4",
         "prettier-plugin-tailwindcss": "^0.6.11",
@@ -2728,12 +2726,12 @@
       }
     },
     "node_modules/@tanstack/react-virtual": {
-      "version": "3.13.6",
-      "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz",
-      "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==",
+      "version": "3.13.9",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.9.tgz",
+      "integrity": "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA==",
       "license": "MIT",
       "dependencies": {
-        "@tanstack/virtual-core": "3.13.6"
+        "@tanstack/virtual-core": "3.13.9"
       },
       "funding": {
         "type": "github",
@@ -2744,6 +2742,16 @@
         "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
       }
     },
+    "node_modules/@tanstack/react-virtual/node_modules/@tanstack/virtual-core": {
+      "version": "3.13.9",
+      "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.9.tgz",
+      "integrity": "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
     "node_modules/@tanstack/virtual-core": {
       "version": "3.13.6",
       "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz",
@@ -3435,13 +3443,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@yarnpkg/lockfile": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
-      "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
-      "dev": true,
-      "license": "BSD-2-Clause"
-    },
     "node_modules/abab": {
       "version": "2.0.6",
       "dev": true,
@@ -3687,16 +3688,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/at-least-node": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
-      "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
-      "dev": true,
-      "license": "ISC",
-      "engines": {
-        "node": ">= 4.0.0"
-      }
-    },
     "node_modules/atob": {
       "version": "2.1.2",
       "dev": true,
@@ -4010,23 +4001,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/call-bound": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
-      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "get-intrinsic": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "dev": true,
@@ -5584,16 +5558,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/find-yarn-workspace-root": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
-      "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "micromatch": "^4.0.2"
-      }
-    },
     "node_modules/flatpickr": {
       "version": "4.6.13",
       "license": "MIT"
@@ -5688,32 +5652,6 @@
         "tslib": "^2.1.0"
       }
     },
-    "node_modules/fs-extra": {
-      "version": "9.1.0",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
-      "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "at-least-node": "^1.0.0",
-        "graceful-fs": "^4.2.0",
-        "jsonfile": "^6.0.1",
-        "universalify": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/fs-extra/node_modules/universalify": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
-      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 10.0.0"
-      }
-    },
     "node_modules/fs.realpath": {
       "version": "1.0.0",
       "dev": true,
@@ -6415,6 +6353,7 @@
       "version": "2.2.1",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "bin": {
         "is-docker": "cli.js"
       },
@@ -6660,6 +6599,7 @@
       "version": "2.2.0",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "dependencies": {
         "is-docker": "^2.0.0"
       },
@@ -7576,26 +7516,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/json-stable-stringify": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz",
-      "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "call-bind": "^1.0.8",
-        "call-bound": "^1.0.3",
-        "isarray": "^2.0.5",
-        "jsonify": "^0.0.1",
-        "object-keys": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/json5": {
       "version": "2.2.3",
       "dev": true,
@@ -7612,39 +7532,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/jsonfile": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
-      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "universalify": "^2.0.0"
-      },
-      "optionalDependencies": {
-        "graceful-fs": "^4.1.6"
-      }
-    },
-    "node_modules/jsonfile/node_modules/universalify": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
-      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 10.0.0"
-      }
-    },
-    "node_modules/jsonify": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
-      "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
-      "dev": true,
-      "license": "Public Domain",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/kind-of": {
       "version": "3.2.2",
       "dev": true,
@@ -7656,16 +7543,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/klaw-sync": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
-      "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "graceful-fs": "^4.1.11"
-      }
-    },
     "node_modules/kleur": {
       "version": "3.0.3",
       "dev": true,
@@ -8990,23 +8867,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/open": {
-      "version": "7.4.2",
-      "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
-      "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-docker": "^2.0.0",
-        "is-wsl": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/opencollective-postinstall": {
       "version": "2.0.3",
       "dev": true,
@@ -9015,16 +8875,6 @@
         "opencollective-postinstall": "index.js"
       }
     },
-    "node_modules/os-tmpdir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/p-each-series": {
       "version": "2.2.0",
       "dev": true,
@@ -9132,90 +8982,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/patch-package": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
-      "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@yarnpkg/lockfile": "^1.1.0",
-        "chalk": "^4.1.2",
-        "ci-info": "^3.7.0",
-        "cross-spawn": "^7.0.3",
-        "find-yarn-workspace-root": "^2.0.0",
-        "fs-extra": "^9.0.0",
-        "json-stable-stringify": "^1.0.2",
-        "klaw-sync": "^6.0.0",
-        "minimist": "^1.2.6",
-        "open": "^7.4.2",
-        "rimraf": "^2.6.3",
-        "semver": "^7.5.3",
-        "slash": "^2.0.0",
-        "tmp": "^0.0.33",
-        "yaml": "^2.2.2"
-      },
-      "bin": {
-        "patch-package": "index.js"
-      },
-      "engines": {
-        "node": ">=14",
-        "npm": ">5"
-      }
-    },
-    "node_modules/patch-package/node_modules/ci-info": {
-      "version": "3.9.0",
-      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
-      "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/sibiraj-s"
-        }
-      ],
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/patch-package/node_modules/rimraf": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
-      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-      "deprecated": "Rimraf versions prior to v4 are no longer supported",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      }
-    },
-    "node_modules/patch-package/node_modules/slash": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
-      "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/patch-package/node_modules/yaml": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
-      "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "yaml": "bin.mjs"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "dev": true,
@@ -11696,19 +11462,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/tmp": {
-      "version": "0.0.33",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "os-tmpdir": "~1.0.2"
-      },
-      "engines": {
-        "node": ">=0.6.0"
-      }
-    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "dev": true,
@@ -12543,7 +12296,7 @@
         "@floating-ui/react": "^0.26.16",
         "@react-aria/focus": "^3.20.2",
         "@react-aria/interactions": "^3.25.0",
-        "@tanstack/react-virtual": "^3.13.6",
+        "@tanstack/react-virtual": "^3.13.9",
         "use-sync-external-store": "^1.5.0"
       },
       "devDependencies": {
diff --git a/package.json b/package.json
index 9c87b7d..9d75aa7 100644
--- a/package.json
+++ b/package.json
@@ -25,8 +25,7 @@
     "lint-types": "CI=true npm run lint-types --workspaces --if-present",
     "release-channel": "node ./scripts/release-channel.js",
     "release-notes": "node ./scripts/release-notes.js",
-    "package-path": "node ./scripts/package-path.js",
-    "postinstall": "patch-package"
+    "package-path": "node ./scripts/package-path.js"
   },
   "husky": {
     "hooks": {
@@ -77,7 +76,6 @@
     "jest": "26",
     "lint-staged": "^12.2.1",
     "npm-run-all": "^4.1.5",
-    "patch-package": "^8.0.0",
     "prettier": "^3.1.0",
     "prettier-plugin-organize-imports": "^3.2.4",
     "prettier-plugin-tailwindcss": "^0.6.11",
diff --git a/packages/@headlessui-react/package.json b/packages/@headlessui-react/package.json
index 432e061..10ff13b 100644
--- a/packages/@headlessui-react/package.json
+++ b/packages/@headlessui-react/package.json
@@ -59,7 +59,7 @@
     "@floating-ui/react": "^0.26.16",
     "@react-aria/focus": "^3.20.2",
     "@react-aria/interactions": "^3.25.0",
-    "@tanstack/react-virtual": "^3.13.6",
+    "@tanstack/react-virtual": "^3.13.9",
     "use-sync-external-store": "^1.5.0"
   }
 }
diff --git a/patches/@tanstack+virtual-core+3.13.6.patch b/patches/@tanstack+virtual-core+3.13.6.patch
deleted file mode 100644
index 0f1c0f6..0000000
--- a/patches/@tanstack+virtual-core+3.13.6.patch
+++ /dev/null
@@ -1,51 +0,0 @@
-diff --git a/node_modules/@tanstack/virtual-core/dist/cjs/index.cjs b/node_modules/@tanstack/virtual-core/dist/cjs/index.cjs
-index dd4e77f..127987e 100644
---- a/node_modules/@tanstack/virtual-core/dist/cjs/index.cjs
-+++ b/node_modules/@tanstack/virtual-core/dist/cjs/index.cjs
-@@ -665,9 +665,9 @@ class Virtualizer {
-             this.options.getItemKey(index)
-           );
-           if (elementInDOM) {
--            const [latestOffset] = utils.notUndefined(
--              this.getOffsetForIndex(index, align)
--            );
-+            const result = this.getOffsetForIndex(index, align)
-+            if (!result) return
-+            const [latestOffset] = result
-             if (!utils.approxEqual(latestOffset, this.getScrollOffset())) {
-               this.scrollToIndex(index, { align, behavior });
-             }
-diff --git a/node_modules/@tanstack/virtual-core/dist/esm/index.js b/node_modules/@tanstack/virtual-core/dist/esm/index.js
-index 8da519d..8c78af8 100644
---- a/node_modules/@tanstack/virtual-core/dist/esm/index.js
-+++ b/node_modules/@tanstack/virtual-core/dist/esm/index.js
-@@ -663,9 +663,9 @@ class Virtualizer {
-             this.options.getItemKey(index)
-           );
-           if (elementInDOM) {
--            const [latestOffset] = notUndefined(
--              this.getOffsetForIndex(index, align)
--            );
-+            const result = this.getOffsetForIndex(index, align)
-+            if (!result) return
-+            const [latestOffset] = result
-             if (!approxEqual(latestOffset, this.getScrollOffset())) {
-               this.scrollToIndex(index, { align, behavior });
-             }
-diff --git a/node_modules/@tanstack/virtual-core/src/index.ts b/node_modules/@tanstack/virtual-core/src/index.ts
-index 3a0c446..4a9e792 100644
---- a/node_modules/@tanstack/virtual-core/src/index.ts
-+++ b/node_modules/@tanstack/virtual-core/src/index.ts
-@@ -1003,9 +1003,9 @@ export class Virtualizer<
-         )
- 
-         if (elementInDOM) {
--          const [latestOffset] = notUndefined(
--            this.getOffsetForIndex(index, align),
--          )
-+          const result = this.getOffsetForIndex(index, align)
-+          if (!result) return
-+          const [latestOffset] = result
- 
-           if (!approxEqual(latestOffset, this.getScrollOffset())) {
-             this.scrollToIndex(index, { align, behavior })


Test patch:
diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
index 88c0fc6..5390696 100644
--- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
+++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
@@ -67,6 +67,9 @@ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() =>
   toJSON: () => {},
 }))
 
+jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(100)
+jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(40)
+
 beforeAll(() => {
   jest.spyOn(window, 'requestAnimationFrame').mockImplementation(setImmediate as any)
   jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(clearImmediate as any)