This PR adds a new quick trigger feature to the `Menu`. Not sure what
the best
name for this is, but essentially this is the behavior:
Recently we made sure that the `Menu` opens on `mousedown` (not just
`click`).
This means that we can perform the following quick action:
1. `mousedown` on the `MenuButton` — this will open the `Menu`
2. Without releasing the mouse button yet, move your mouse over one of
the `MenuItem`s — this will highlight the currently active `MenuItem`.
3. Release the mouse button — this will invoke the currently active
`MenuItem` and close the `Menu`.
This now means that you can perform actions very quickly.
What this PR doesn't do yet is if you have a scrollable list, then it
won't scroll up or down when you reach the ends of the list. For this we
would need to introduce some new elements. The native Menu items on
macOS show a little placeholder arrow. If you put your cursor in that
area, it starts scrolling:
<img width="489" alt="image"
src="https://github.com/user-attachments/assets/e3a90d5a-daa7-4711-9e19-050578be3e02"
/>
## Test plan
1. Everything still works as expected
2. Quick release has been added:
- Listbox:
https://headlessui-react-git-feat-quick-trigger-tailwindlabs.vercel.app/listbox/listbox-with-pure-tailwind
- Menu:
https://headlessui-react-git-feat-quick-trigger-tailwindlabs.vercel.app/menu/menu
- Combobox:
https://headlessui-react-git-feat-quick-trigger-tailwindlabs.vercel.app/combobox/combobox-countries
Recently we made improvements to the `Transition` component and internal
`useTransition` hook. We now use the `Element.prototype.getAnimations`
API to know whether or not all transitions are done.
This API has been available in browsers since 2020, however jsdom
doesn't have support for this. This results in a lot of failing tests
where users rely on jsdom (e.g. inside of Jest or Vitest).
In a perfect world, jsdom is not used because it's not a real browser
and there is a lot you need to workaround to even mimic a real browser.
I understand that just switching to real browser tests (using Playwright
for example) is not an easy task that can be done easily.
Even our tests still rely on jsdom…
So to make the development experience better, we polyfill the
`Element.prototype.getAnimations` API only in tests
(`process.env.NODE_ENV === 'test'`) and show a warning in the console on
how to proceed.
The polyfill we ship simply returns an empty array for
`node.getAnimations()`. This means that it will be _enough_ for most
tests to pass. The exception is if you are actually relying on
`transition-duration` and `transition-delay` CSS properties.
The warning you will get looks like this:
``````
Headless UI has polyfilled `Element.prototype.getAnimations` for your tests.
Please install a proper polyfill e.g. `jsdom-testing-mocks`, to silence these warnings.
Example usage:
```js
import { mockAnimationsApi } from 'jsdom-testing-mocks'
mockAnimationsApi()
```
``````
Fixes: #3470Fixes: #3469Fixes: #3468
* use `act` from `react` instead of `@testing-library/react`
* bump dependencies
* bump `@testing-library/react`
* bump `@react-aria/interactions`
* bump "@tanstack/react-virtual"
* add `ResizeObserver` polyfill, and enable it by default for tests
* mock `getBoundingClientRect`
Otherwise the virtualization tests don't work as expected because they
rely on the client rect which is not supported (or not correctly
measured) in JSDOM.
* add `get-text-value` helper
* use `getTextValue` in `Listbox` component
* use `getTextValue` in `Menu` component
* update changelog
* ensure we handle multiple values for `aria-labelledby`
* hoist regex
* drop child nodes instead of replacing its innerText
This makes it a bit slower but also more correct. We can use a cache on
another level to ensure that we are not creating useless work.
* add `useTextValue` to improve performance of `getTextValue`
This will add a cache and only if the `innerText` changes, only then
will we calculate the new text value.
* use better `useTextValue` hook
* Fix overflow when swapping dialogs that use transition
* Refactor
* refactor
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Inline shim for ESM support
Until the official package adds an ESM version with a wildcard import we can’t use it. This version was copied from Remix Router
* Add dialog shadow root examples
* Fix SSR error
* Add repro for iOS scrolling issue
* Try to fix vercel build
idk what’s wrong here
* Update repro
A transition is required to delay closing enough to demonstrate the bug
* Port global dialog state to Vue
* Add dialog test to Vue
* wip
* wip
* Workaround bug
This shouldn’t happen at all and we need to find the source of the bug but this should “fix” things for the time being
* wip
* Rebuild overflow locking with simpler API
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update deps
* wip
* simplify
* Port to Vue
* wip
* wip
* Tweak tests
* Update changelog
* Ensure meta callbacks are cleaned up
* cleanup
* wip
* bump dev dependencies to React 18
* setup Jest to include `IS_REACT_ACT_ENVIRONMENT`
* prefer `useId` from React 18 if it exists
In React 16 & 17, where `useId` doesn't exist, we will fallback to our
implementation we have been using up until now.
The `useId` exposed by React 18, ensures stable references even in SSR
environments.
* update expected events
React 18 now uses the proper events:
- `blur` -> `focusout`
- `focus` -> `focusin`
* ensure to wait a bit longer
This is a bit unfortunate, but since React 18 now does an extra
unmount/remount in `StrictMode` to ensure that your code is
ConcurrentMode ready, it takes a bit longer to settle what the DOM sees.
That said, this is a temporary "hack". We are going to experiment with
using tools like Puppeteer/Playwright to run our tests in an actual
browser instead to eliminate all the weird details that we have to keep
in mind.
* prefer `.focus()` over `fireEvent.focus(el)`
* abstract `microTask` polyfill code
* prefer our `focus(el)` function over `el.focus()`
Internally we would still use `el.focus()`, but this allows us to have
more control over that `focus` function.
* add React 18 to the React Playground
* improve hooks for React 18
- Improving the cleanup of useEffect hooks
- useIsoMorphicEffect instead of normal useEffect, so that we can use
useLayoutEffect to be a bit quicker.
* improve disposables
- This allows us to add event listeners on a node, and get automatic
cleanup once `dispose` gets called.
- We also return all the `d.add` calls, so that we can cleanup specific
parts only instead of everything or nothing.
* reimplement the Transition component to be React 18 ready
* wait an additional frame for everything to settle
* update playground examples
* suppressConsoleLogs for RadioGroup components
* update changelog
* keep the `to` classes for a smoother transition
In the next transition we will remove _all_ classes provided and re-add
the once we need.
---
Some extra special thanks:
- Thanks @silvenon for your initial work on the `transition` events in #926
- Thanks @thecrypticace for doing late-night debugging sessions
Co-authored-by: =?UTF-8?q?Matija=20Marohni=C4=87?= <matija.marohnic@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
* use esbuild for React instead of tsdx
* remove tsdx from Vue
* use consistent names
* add jest and prettier
* update scripts
* ignore some folders for prettier
* run lint script instead of tsdx lint
* run prettier en-masse
This has a few changes because of the new prettier version.
* bump typescript to latest version
* make typescript happy
* cleanup playground package.json
* make esbuild a dev dependency
* make scripts consistent
* fix husky hooks
* add dedicated watch script
* add `yarn playground-react` and `yarn react-playground` (alias)
This will make sure to run a watcher for the actual @headlessui/react
package, and start a development server in the playground-react package.
* ignore formatting in the .next folder
* run prettier on playground-react package
* setup playground-vue
Still not 100% working, but getting there!
* add playground aliases in @headlessui/vue and @headlessui/react
This allows you to run `yarn react playground` or `yarn vue playground`
from the root.
* add `clean` script
* move examples folder in playground-vue to root
* ensure new lines for consistency in scripts
* fix typescript issue
* fix typescript issues in playgrounds
* make sure to run prettier on everything it can
* run prettier on all files
* improve error output
If you minify the code, then it could happen that the errors are a bit
obscure. This will hardcode the component name to improve errors.
* add the `prettier-plugin-tailwindcss` plugin, party!
* update changelog
- Made the use of `const` and `let` consistent
- import required functions and types from 'react' instead of using the
`React.` namespace.
- Added `Expand` type, which can expand complex types to their "final"
result.
- Ensured that we use `as const` for DEFAULT_XXX_TAG where we used a
string. So that we have the type of `div` instead of `string` for
example.
- Used `interface` over `type` where possible. I'm personally more of a
`type` fan. But the TypeScript recommends `interfaces` where possible
because they are faster, yield better error messages and so on.
* make jest monorepo aware
* add @testing-library/jest-dom for custom matchers
This way we can use expect(element).toHaveAttribute(key, value?)
* abstract keys enum
* change type to unknown, because we don't know the return value
* update use-id hook, make it suspense aware
Thanks Reach UI!
* hoist the disposables collection
* add accessbility assertions for listbox
Also made it consistent for the Menu component and simplified some of the assertions
* add use-computed hook
This allows us re-render when hooks change, but also return a value. So this is a combination of useEffect and a useState value.
* add Listbox component
* bump dependencies
* add listbox example
* add lint-staged
This way we will only lint the files that have been staged and ready to be committed instead of the whole codebase
* add missing prevent defaults
* improve tests to verify that we can actually update the value of the listbox
* scroll the active listbox item into view
* small optimization, only focus "Nothing" on pointer leave when we are the active item
We used to always go to "Nothing" on pointer leave. And while this code
doesn't get called often, it *gets* called if you are using your arrow
keys and the mouse pointer is still over the list.
* bump dependencies
Also moved the tailwind dependencies to the root
* fix typo
* drop the default Transition inside the Menu and Listbox components
* update examples to reflect drop of default Transition wrapper
* rename Listbox.{Items,Item} to Listbox.{Options,Option}
Also rename all instances of `item` to `option` in tests and comments
and what have you...
* fix typo
* drop disabled prop, use aria-disabled only