* add mouse buttons
* add `useDisposables` hook
* add `useFrameDebounce` hook
Schedule a task in the next frame
* ensure we reset the `isTyping` flag correctly
* use same `mousedown` API as we did in React
This allows us to never leave the `input`, even when clicking on an
option.
* update changelog
* format comments
* inline `cb`
The scroll locking on iOS was flickering in some scenario's due to the
`window.scrollTo(0, 0)` related code. Instead of that, we now cancel
touch moves instead but still allow it in scrollable containers inside
the Dialog itself.
This was already applied in the React version, but this adds the same
improvement to the Vue version as well.
* bump React & React DOM dependencies
* fix typo `TOmitableProps` → `TOmittableProps`
* bump prettier
* run prettier after prettier version bump
* bump TypeScript
* run prettier after TypeScript version bump
* enable `verbatimModuleSyntax`
This ensures all imported types are using the `type` keyword.
* add `type` to type related imports
* add common testing scenarios
Will be used in the new and existing components.
* add script to make Next.js happy
Right now Next.js does barrel file optimization and re-writing imports
to a real path in the `dist` folder. Most of those rewrites don't
actually exist because they have an assumption:
```js
import { FooBar } from '@headlessui/react'
```
is rewritten as:
```js
import { FooBar } from '@headlessui/react/dist/components/foo-bar/foo-bar'
```
This script will make sure these paths exist...
* improve `by` prop, introduce `useByComparator`
This hook has a default implementation when comparing objects. If the
object contains an `id`, then we will compare the objects by their
`id`'s without the user of the library needing to specify `by="id"`.
If the objects don't have an `id` prop, then the default is still to
compare by reference (unless specicified otherwise).
* sync yarn.lock
* rename `Features` to `HiddenFeatures` for `Hidden` component
* rename `Features` to `FocusTrapFeatures` in `FocusTrap` component
* rename `Features` to `RenderFeatures` in `render` util
* add `floating-ui` as a dependency + introduce internal floating related components
* bump Vue dependencies
* ensure scroll bar calculations can't go negative
* improve types in `@headlessui/vue`
* use snapshot tests for `Transition` tests in `@headlessui/vue`
* use snapshot tests for `portal` tests in `@headlessui/vue`
* rename `src/components/transitions/` to `src/components/transition/` (singular)
This is so that we can be consistent with the other components.
* drop custom `toMatchFormattedCss`, prefer snapshot tests instead
* use snapshot tests for `Label` tests in `@headlessui/vue`
* use snapshot tests for `Description` tests in `@headlessui/vue`
* sort exported components in tests for `@headlessui/vue`
* use snapshot tests in `@headlessui/tailwindcss`
* rename `mergeProps` to `mergePropsAdvanced`
This is a more complex version of a soon to be exported `mergeProps`
that we will be using in our components.
* do not expose `aria-labelledby` if it is only referencing itself
* expose boolean state as `kebab-case` instead of `camelCase`
These are the ones being exposed inside `data-headlessui-state="..."`
* expose boolean data attributes
A slot with `{active,focus,hover}` will be exposed as:
```html
<span data-headlessui-state="active focus hover"></span>
```
But also as boolean attributes:
```html
<span data-active data-focus data-hover></span>
```
* improve internal types for `className` in `render` util
* ensure we keep exposed data attributes into account when trying to forward them to the component inside the `Fragment`
* add small typescript type fix
This is internal code, and the public API is not influenced by this
`:any`. It does make TypeScript happy.
* introduce `mergeProps` util to be used in our components
This will help us to merge props, when event handlers are available they
will be merged by wrapping them in a function such that both (or more)
event handlers are called for the same `event`.
* add new internal `Modal` component
* fix: when using `Focus.Previous` with `activeIndex = -1` should start at the end
* prefer `window.scrollY` instead of `window.pageYOffset`
Because `window.pageYOffset` is deprecated.
* add `'use client'` directives on client only components
These components use hooks that won't work in server components and you
will receive an error otherwise.
* drop `import 'client-only'` in favor of the `'use client'` directive
* add React Aria dependencies
* pin beta dependencies
* prettier bump formatting
* improve TypeScript types in tests
* use new Jest matchers instead of deprecated ones
* improve typescript types in Vue
* prefer `useLabelledBy` and `useDescribedBy`
* add internal `DisabledProvider`
* add internal `IdProvider`
* add internal `useDidElementMove` hook
* add internal `useElementSize` hook
* add internal `useIsTouchDevice` hook
* add internal `useActivePress` hook
* use snapshot tests for `Description` tests
* use snapshot tests for `Label` tests
* use snapshot tests for `Portal` tests
* use snapshot tests for `render` tests
* add (private) `Tooltip` component
Currently this one is not ready yet, so its not publicly exposed yet.
* add internal `FormFields` component
This one adds a component to render (hidden) inputs for native form
support. It also ensures that form fields can be hoisted to the end of
the nearest `Field`. If the components are not inside a `Field` they
will be rendered in place.
* add new `Button` component
* add new `Checkbox` component
* add new `DataInteractive` component
* add new `Field` component
* add new `Fieldset` component
* add new `Legend` component
* add new `Input` component
* add new `Select` component
* add new `Textarea` component
* export new components
* WIP
* remove `within: true`
This only makes sense if anything inside the current element receives
focus, which is not the case for `input`, `select`, `textarea` or
`Radio/RadioOption`.
* group focus/hover/active hooks together
* conditionally link anchor panel
* immediately focus the container
* prevent premature disabling of `Listbox`'s floating integration
+ Track whether the button moved or not when disabling such that we can
disable the transitions earlier.
* improve scroll locking on iOS
* skip hydration tests for now
* skip certain focus trap tests for now
* update CHANGELOG.md
* add missing requires
* drop unused `@ts-expect-error`
* ignore type issues in playgrounds
These playgrounds are mainly test playgrounds. Lower priority for now,
we will get back to them.
* add yarn resolutions to solve swc bug
* add `prettier-plugin-organize-imports` and `prettier-plugin-tailwindcss`
* format
* bump Tailwind CSS
* format playgrounds using updated Tailwind CSS and Prettier plugins
* use import syntax
* Tweak `dom()` helper
The helper can currently return a component instance when it should only ever return a DOM element. So, we fix the implementation to return null if it’s not an `Element` _and_ adjust the types such that if a `ComponentPublicInstance` is passed we change the return type to `Element`.
* Specialize DOM helper to HTML elements
Technically it could be an SVG element but much of Headless UI assumes HTML elements all over. So we’ll adjust the types to assume HTMLElement instead.
* Allow `dom()` helper to return any `Node` type
It doesn’t actually always return an HTMLElement but we have behavior that relies on it returning and checking for `Comment` nodes
* Detect `<button>` nested in components inside `<PopoverButton>`
* Update changelog
* disable smooth scrolling when opening/closing Dialogs
For iOS workaround related purposes we have to capture the scroll
position and offset the margin top with that amount and then
`scrollTo(0,0,)` to prevent all kinds of funny UI jumps.
However, if you have `scroll-behavior: smooth` enabled on your `html`,
then offseting the margin-top and later `scrollTo(0,0)` would be
handled in a smooth way, which means that the actual position would be
off.
To solve this, we disable smooth scrolling entirely in order to make the
position of the Dialog correct. This shouldn't be a problem in practice
since the page itself isn't suppose to scroll anyway.
Once the Dialog closes we reset it such that everything else keeps
working as expected in a (smooth) way.
* add `microTask` to disposables
* ensure the fix works in React's double rendering dev mode
* update changelog
* only render `<MainTreeNode />` in `Popover.Group` instead of after every `Popover`
* make Vue Popover consistent
* apply same `MainTreeNode` logic to Vue version
* update changelog
* listen for both `mousedown` and `pointerdown` events
This is necessary for calculating the target where the focus will
eventually move to. Some other libraries will use an
`event.preventDefault()` and if we are not listening for all "down"
events then we might not capture the necessary target.
We already tried to ensure this was always captured by using the
`capture` phase of the event but that's not enough.
This change won't be enough on its own, but this will improve the
experience with certain 3rd party libraries already.
* refactor one-liners
* listen for `touchend` event to improve "outside click" on mobile devices
* update changelog
* abstract resolving root containers to hook
This way we can reuse it in other components when needed.
* allow registering a `Portal` component to a parent
This allows us to find all the `Portal` components that are nested in a
given component without manually adding refs to every `Portal` component
itself.
This will come in handy in the `Popover` component where we will allow
focus in the child `Portal` components otherwise a focus outside of the
`Popover` will close the it. In other components we often crawl the DOM
directly using `[data-headlessui-portal]` data attributes, however this
will fetch _all_ the `Portal` components, not the ones that started in
the current component.
* allow focus in portalled containers
The `Popover` component will close by default if focus is moved outside
of it. However, if you use a `Portal` comopnent inside the
`Popover.Panel` then from a DOM perspective you are moving the focus
outside of the `Popover.Panel`. This prevents the closing, and allows
the focus into the `Portal`.
It currently only allows for `Portal` components that originated from
the `Popover` component. This means that if you open a `Dialog`
component from within the `Popover` component, the `Dialog` already
renders a `Portal` but since this is part of the `Dialog` and not the
`Popover` it will close the `Popover` when focus is moved to the
`Dialog` component.
* ensure `useNestedPortals` register/unregister with the parent
This ensures that if you have a structure like this:
```jsx
<Dialog> {/* Renders a portal internally */}
<Popover>
<Portal> {/* First level */}
<Popover.Panel>
<Menu>
<Portal> {/* Second level */}
<Menu.Items>
{/* ... */}
</Menu.Items>
</Portal>
</Menu>
</Popover.Panel>
</Portal>
</Popover>
</Dialog>
```
That working with the `Menu` doesn't close the `Popover` or the `Dialog`.
* cleanup `useRootContainers` hook
This will allow you to pass in portal elements as well. + cleanup of
the resolving of all DOM nodes.
* handle nested portals in `Dialog` component
* expose `contains` function from `useRootContainers`
Shorthand to check if any of the root containers contains the given
element.
* add tests to verify that actions in `Portal` components won't close the `Popover`
* update changelog
* re-order use-outside-click logic
To make it similar between React & Vue
* inject the `PortalWrapper` context in the correct spot
* ensure to forward the incoming `attrs`
* 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
* drop `@ts-expect-error`, because `inert` is available now
* fix logical error
We want to apply `inert` when we _don't_ have nested dialogs, because if
we _do_ have nested dialogs, then the inert should be applied from the
nested dialog (or visually the top most dialog).
* update changelog
* replace `useInertOthers` with `useInert`
* add `assertInert` and `assertNotInert` accessibility assertion helpers
* ensure the `main tree` root is marked as inert
As well as the parent dialogs in case of nested dialogs.
* 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
* do not add `disabled` prop to `MenuItem`
We use the `aria-disabled` instead so that you can still style it and
that assistive techonology can read the disabled state. If it has the
`disabled` prop itself, then often you can't interact with it at all.
We also default to `disabled = false`, which means that the default
behaviour was a `<element disabled="false">` in the DOM. If you then
have CSS like `[disabled] { opacity: 0.8; }` then this also applies to
the elements with `disabled="false"`.
Fixes: #2134
* ensure Vue playground still works
* ensure Vue overrides the `onXXX` correctly
* update changelog
* Allow clicks inside dialog panel when target is inside shadow root
* Introduce resettable “server” state
This will aid in testing
* Add SSR and hydration tests for react
* Fix server rendering of Tabs on React 17
* Fix CS
* Skip hydration tests
* Tweak SSR implementation in Vue
* Update changelog
* ensure `syncInputValue` is updated correctly
* WIP
* WIP
* Don’t resync on open
* Fix react value syncing
update
* Add comment
* Port new setup over to Vue
* Remove `inputPropsRef`
We hardly knew ye
* Remove repro
* Cleanup
* Update changelog
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* implement uncontrolled form components
A few versions ago we introduced compatibility with the native `form`
element. This means that behind the scenes we render hidden inputs that
are kept in sync which allows you to submit your normal form and get
data via `new FormData(event.currentTarget)`.
Before this change every form related component (Switch, RadioGroup,
Listbox and Combobox) always had to be passed a `value` and an
`onChange` regardless of this change.
This change will allow you to not even use the `value` and the
`onChange` at all and keep it completely uncontrolled.
This has some changes:
- `value` is made optional
- `onChange` is made optional (but will still be called if passed
regardless of being controlled or uncontrolled)
- `defaultValue` got added so that you can still pre-fill your values
with known values.
- `value` render prop got exposed so that you can still use this while
rendering.
This should also make it completely compatible with tools like Remix
without wiring up your own state.
* update example combinations/form playground to use uncontrolled
components
* improve types, add missing render prop arguments
* add tests for uncontrolled components (React)
* implement uncontrolled form elements in Vue
* ensure outside click works on Safari in iOS
When tapping on an element that is not clickable (like a div), then the
`click` and `mousedown` events will not reach the
`window.addEventListener('click')` listeners.
The only event that does that could be interesting for us is the
`pointerdown` event. The issue with this one is that we then run into
the big issue we ran in a few months ago where clicks on a scrollbar
*also* fired while a click doesn't.
This issue was not an issue in React land, the
`window.addEventListener('click')` was fired even when tapping on a
`div`. This was very very confusing, but we think this is because of the
syntethic event system, where the event listener is added to the root of
your application (E.g.: #app) and React manually bubbles the events.
Because this is done manually, it *does* reach the window as well.
The confusing part is, how does React convert a `pointerdown` event to a
`mousedown` and `click`. There is no code for that in their codebase?
Turns out they don't, and turns out the events **do** bubble, but up
until the `document`, not the `window`. But since they are manually
bubbling events it all makes sense.
So the solution? Let's switch from `window` to `document`...
* update Dialog example to use DialogPanel
* update changelog
* Don’t close dialog if opened during mouse up event
* Don’t close dialog if drag starts inside dialog and ends outside dialog
* Handle closing of nested dialogs that are always mounted
* Fix focus trap restoration in Vue
* Update changelog
* check typeof document in addition to typeof window
* remove unused import
* Extract SSR check to a central spot
* Fix CS
* Update changelog
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
* convert dialog in playground to use Dialog.Panel
* convert `tabs-in-dialog` example to use `Dialog.Panel`
* add scrollable dialog example to the playground
* simplify `outside click` behaviour
Here is a little story. We used to use the `click` event listener on the
window to try and detect whether we clicked outside of the main area we
are working in.
This all worked fine, until we got a bug report that it didn't work
properly on Mobile, especially iOS. After a bit of debugging we switched
this behaviour to use `pointerdown` instead of the `click` event
listener. Worked great! Maybe...
The reason the `click` didn't work was because of another bug fix. In
React if you render a `<form><Dialog></form>` and your `Dialog` contains
a button without a type, (or an input where you press enter) then the
form would submit... even though we portalled the `Dialog` to a
different location, but it bubbled the event up via the SyntethicEvent
System. To fix this, we've added a "simple" `onClick(e) { e.stopPropagation() }`
to make sure that click events didn't leak out.
Alright no worries, but, now that we switched to `pointerdown` we got
another bug report that it didn't work on older iOS devices. Fine, let's
add a `mousedown` next to the `pointerdown` event. Now this works all
great! Maybe...
This doesn't work quite as we expected because it could happen that both
events fire and then the `onClose` of the Dialog component would fire
twice. In fact, there is an open issue about this: #1490 at the time of
writing this commit message.
We tried to only call the close function once by checking if those
events happen within the same "tick", which is not always the case...
Alright, let's ignore that issue for a second, there is another issue
that popped up... If you have a Dialog that is scrollable (because it is
greater than the current viewport) then a wild scrollbar appears (what a
weird Pokémon). The moment you try to click the scrollbar or drag it the
Dialog closes. What in the world...?
Well... turns out that `pointerdown` gets fired if you happen to "click"
(or touch) on the scrollbar. A click event does not get fired. No
worries we can fix this! Maybe...
(Narrator: ... nope ...)
One thing we can try is to measure the scrollbar width, and if you
happen to click near the edge then we ignore this click. You can think
of it like `let safeArea = viewportWidth - scrollBarWidth`. Everything
works great now! Maybe...
Well, let me tell you about macOS and "floating" scrollbars... you can't
measure those... AAAAAAAARGHHHH
Alright, scratch that, let's add an invisible 20px gap all around the
viewport without measuring as a safe area. Nobody will click in the 20px
gap, right, right?! Everything works great now! Maybe...
Mobile devices, yep, Dialogs are used there as well and usually there is
not a lot of room around those Dialogs so you almost always hit the
"safe area". Should we now try and detect the device people are
using...?
/me takes a deep breath...
Inhales... Exhales...
Alright, time to start thinking again... The outside click with a
"simple" click worked on Menu and Listbox not on the Dialog so this
should be enough right?
WAIT A MINUTE
Remember this piece of code from earlier:
```js
onClick(event) {
event.stopPropagation()
}
```
The click event never ever reaches the `window` so we can't detect the
click outside...
Let's move that code to the `Dialog.Panel` instead of on the `Dialog`
itself, this will make sure that we stop the click event from leaking
if you happen to nest a Dialog in a form and have a submitable
button/input in the `Dialog.Panel`. But if you click outside of the
`Dialog.Panel` the "click" event will bubble to the `window` so that we
can detect a click and check whether it was outside or not.
Time to start cleaning:
- ☑️ Remove all the scrollbar measuring code...
- Closing works on mobile now, no more safe area hack
- ☑️ Remove the pointerdown & mousedown event
- Outside click doesn't fire twice anymore
- ☑️ Use a "simple" click event listener
- We can click the scrollbar and the browser ignores it for us
All issues have been fixed! (Until the next one of course...)
* ensure a `Dialog.Panel` exists
* cleanup unnecessary code
* use capture phase for outside click behaviour
* further improve outside click
We added event.preventDefault() & event.defaultPrevented checks to make
sure that we only handle 1 layer at a time.
E.g.:
```js
<Dialog>
<Menu>
<Menu.Button>Button</Menu.Button>
<Menu.Items>...</Menu.Items>
</Menu>
</Dialog>
```
If you open the Dialog, then open the Menu, pressing `Escape` will close
the Menu but not the Dialog, pressing `Escape` again will close the
Dialog.
Now this is also applied to the outside click behaviour.
If you open the Dialog, then open the Menu, clicking outside will close
the Menu but not the Dialog, outside again will close the Dialog.
* add explicit `enabled` value to the `useOutsideClick` hook
* ensure outside click properly works with Poratl components
Usually this works out of the box, however our Portal components will
render inside the Dialog component "root" to ensure that it is inside
the non-inert tree and is inside the Dialog visually.
This means that the Portal is not in a separate container and
technically outside of the `Dialog.Panel` which means that it will close
when you click on a non-interactive item inside that Portal...
This fixes that and allows all Portal components.
* update changelog
* refactor `VisuallyHidden` to `Hidden` component
This new component will also make sure that it is visually hidden to
sighted users. However, it contains a few more features that are going
to be useful in other places as well. These features include:
1. Make visually hidden to sighted users (default)
2. Hide from assistive technology via `features={Features.Hidden}`
(will add `display: none;`)
3. Hide from assistive technology but make the element focusable via
`features={Features.Focusable}` (will add `aria-hidden="true"`)
* add `useEvent` hook
This will behave the same (roughly) as the new to be released `useEvent`
hook in React 18.X
This hook allows you to have a stable function that can "see" the latest
data it is using. We already had this concept using:
```js
let handleX = useLatestValue(() => {
// ...
})
```
But this returned a stable ref so you had to call `handleX.current()`.
This new hook is a bit nicer to work with but doesn't change much in the
end.
* add `useTabDirection` hook
This keeps track of the direction people are tabbing in. This returns a
ref so no re-renders happen because of this hook.
* add `useWatch` hook
This is similar to the `useEffect` hook, but only executes if values are
_actually_ changing... 😒
* add `microTask` util
* refactor `useFocusTrap` hook to `FocusTrap` component
Using a component directly allows us to simplify the focus trap logic
itself. Instead of intercepting the <kbd>Tab</kbd> keydown event and
figuring out the correct element to focus, we will now add 2 "guard"
buttons (hence why we require a component now). These buttons will
receive focus and if they do, redirect the focus to the first/last
element inside the focus trap.
The sweet part is that all the tabs in between those buttons will now be
handled natively by the browser. No need to find the first non disabled,
non hidden with correct tabIndex element!
* refactor the `Dialog` component to use the `FocusTrap` component
Also added a hidden button so that we know the correct "main" tree of
the application. Before this we were assuming the previous active
element which will still be correct in most cases but we don't have
access to that anymore since the logic is encapsulated inside the
FocusTrap component.
* ensure `<Portal />` properly cleans up
We make sure that the Portal is cleaning up its `element` properly.
We also make sure to call the `target.appendChild(element)`
conditionally because I ran into a super annoying bug where a focused
element got blurred because I believe that this re-mounts the element
instead of 'moving' it or just ignoring it, if it already is in the
correct spot.
* refactor: use `useEvent` instead of `useLatestValue`
Not really necessary, just cleaner.
* update changelog
* resolve initialFocusRef correctly
If you are passing a Ref to a component, you don't get the underlying
DOM node even if you put it on the element manually. The ref will be a
ref to the _component_.
This means that the initialFocusRef can be a DOM element or a Vue
component instance. Resolving it guarantees us to resolve to an
HTMLElement or null but not a component.
* update changelog
* ignore "outside click" on removed elements
Co-authored-by: Colin King <me@colinking.co>
* update changelog
Co-authored-by: Colin King <me@colinking.co>
* add tests to verify that tabbing around when using `initialFocus` works
* add nesting example to `playground-vue`
* fix nested dialog and initialFocus cycling
* make React dialog consistent
- Disable FocusLock on leaf Dialog's
* update changelog
* fix restoreElement logic
The code for React already worked, let's update the Vue code to make it
similar which properly restores focus.
* update changelog
* improve outside click support
We used to use `pointerdown`, but some older devices with iOS 12 didn't
have support for that. Instead we used `mousedown`. But now it turns out
that some devices only properly use `pointerdown` and not the `mousedown` event.
Instead, we will listen to both, but make sure to only handle the event
once.
* update changelog
* only record the restoreElement once enabled
Currently we are collecting the `restoreElement` even if the focus trap
is not enabled. When we unmount we try to restore it.
The problem is the moment you unmount you want to restore but only if
the focus trap was enabled.
Another issue is that the dialog state will be `closed` before we get to
the `onUmount` hook. So there is probably a cleaner way to fix this, but
this does the trick as well where we only record the restoreElement the
moment the focus trap gets enabled.
* update changelog
* 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
* add `{type:'button'}` only for buttons
We will try and infer the type based on the passed in `props.as` prop or
the default tag. However, when somebody uses `as={CustomComponent}` then
we don't know what it will render. Therefore we have to pass it a ref
and check if the final result is a button or not. If it is, and it
doesn't have a `type` yet, then we can set the `type` correctly.
* update changelog
* add useTreeWalker hooks
We got a PR to fix the createTreeWalker so that it also works in IE11.
We don't actively support IE11, so if things work (with polyfills) then
it's good but I don't want to maintain IE11 specific code.
That said, I wanted to abstract the createTreeWalker code to a nice
little hook. The fix for IE is also pretty small, it uses a function
instead of an object and it has a last argument that is deprecated, but
has no obvious effect for our use cases.
Since the incoming PR was based on the `main` branch (where we only had
1 reference to createTreeWalker), I wanted to make sure that we got all
the references on the latest `develop` branch.
Closes: #295
Co-authored-by: Simon VDB <simonvdbroeck@gmail.com>
* use useTreeWalker hook
Co-authored-by: Simon VDB <simonvdbroeck@gmail.com>
* add little editor hack
By adding a html`..` to the template strings editors can get syntax
highlighting for these template strings. Even better, prettier can even
format the contents inside those because now it is "aware" of what kind
of content is inside of these template strings.
You might notice that for the Menu component I have a jsx`..`, this is
another little hack, this only provides us with syntax highlighting and
not with prettier support.
The reason why we have this is that for some reason, when you have:
html`
<MenuItem>
`
It will be formatted as:
html`
<menuitem>
`
There might exist a better name we can use instead of jsx, but for now,
this will do. Having syntax highlighting is already 10x better than what
we had before!
* add Alert component
* update changelog
* update REACT readme for Alert component
* expose Alert component
* add Disclosure component
* expose Disclosure component
* add FocusTrap component
* add FocusTrap example
* expose FocusTrap
* update test utils
We've been making some changes in the React utils, so we have to update
them here as well!
* add Popover component
* expose Popover
* drop unused state
* type Disclosure's API object
* add Portal component
* add Portal example
* expose Portal component
* use correct containElement assertion
* add useInertOthers hook
* add Dialog component
* fix various typo's
* expose Dialog
* add Popover example
* force focus on the Popover button on click
* drop own id when using labels
We are nesting the Label and Description components, if we also add the
id of ourselves we get strange results when using Voice Over.
First you would hear the contents (which includes the labels and
descriptions) then you would hear the labels and descriptions again.
We don't want to hear things twice!
* add Dialog example
* ensure to stop propagation
Otherwise if you nest a Menu inside a Dialog and you press `Escape` the
Dialog will close as well, which is not the expected behaviour.
* improve focus management
When you trigger a Popover using a `click` event, then start using `Tab`
the next `a`-tags do not contain the default focus styles. These only
happen when you trigger it using the keyboard first.
Using a tabindex="0" does make it "focusable" and the default browser
styles will be visible. If we remove the tabindex in a
requestAnimationFrame or a setTimeout then the focus styles will be
removed as well.
This should not cause to many issues (fingers crossed) because the
document.activeElement was already referring to the correct element!
* remove Alert component
There are a lot of unknowns and context dependendant questions to
implement Alerts in a good way. The current Alert component just had a
role set, and it had no JS attached.
We will revisit this, once we start working on Alert Dialogs,
Notification center notifications (dismissable, hide after x time, ...)
* ensure Popover.Overlay auto shows/hides based on Popover state
* enable focus trapping based on `open` prop
Only enable focus trapping in the Dialog when `open` is true, regardless
of the `static` prop.
* handle attrs on Dialog manually
* add low level Description component
* add low level Label component
* add RadioGroup component
* expose RadioGroup
* update README with links to new components
* update changelog with all the changes
* add RadioGroup example
* improve type in test
* cleanup internal Dialog Description
We have a low level Description component abstraction that can be used
instead of the Dialog specifiction Description.
* refactor raw window events to a shared useWindowEvent
* passthrough prop bag via context for abstract Description
The Description component is a generic low level component that is
re-used. This causes an issue that the render prop "bag"/"slot" doesn't
contain the data from let's say a Dialog component.
This commit will ensure that you can specify a bag (React) and slot
(Vue) on the DescriptionProvider, so that the Description component can
read it from the context.
* improve render function in React
These contain a few changes that are purely internal changes. Nothing
changes / breaks in the public API of the components.
- Instead of using multiple arguments in your `render()` functions, we now
use an object.
- `propsBag` / `bag` is renamed to `slot`.
- We also provide a `name` to the render function, so that we can use
that to improve error messages.
* use the new internal render api (React)
* improve render function in Vue
* use the new internal render api (Vue)