rgossiaux / svelte-headlessui Goto Github PK
View Code? Open in Web Editor NEWUnofficial Svelte port of the Headless UI component library
Home Page: https://svelte-headlessui.goss.io
License: MIT License
Unofficial Svelte port of the Headless UI component library
Home Page: https://svelte-headlessui.goss.io
License: MIT License
I don't remember any more why this ended up with a different implementation... looking back over it I don't see why it can't be closer to the others. In particular this gets in the way of porting the FocusTrap tests and exposing the component publicly.
This isn't really a user-facing bug, it's just to track internal cleanup.
Can tab through the buttons of all dialogs. Also, opening a new dialog doesn't focus the first button.
As far as I'm aware you cannot change ANY existing copyright line in the MIT license. And when you add to an existing license you should stack on top of the existing copyright line (and not below it). As such, nothing in this world is ever set in stone. I'm simply relaying what I've read and researched on opensource.stackexchange.com and on the web. Lastly you may want to update your modifications copyright line to include something like and contributors if you plan to accept pull requests in this repo.
Thanks for picking an MIT license. Even if you did go with Apache 2.0 (which you possibly still could do if you wanted, though that's just a hip shot statement) both the MIT and Apache 2.0 are GPL-compatible licenses.
When I ported these I completely forgot that Svelte has standard names for these events already: https://svelte.dev/tutorial/transition-events
We should use those event names. It's a breaking change, so I should coordinate it with the release of #47
I only export the TransitionRoot
component under the Transition
name (which is the one that's documented), but it looks like some Tailwind UI snippets might use the Transition.Root
naming, so I should probably export that as well
Is it valuable to extract this function?
I've seen it in almost every component.
// $lib/components/menu/Menu.svelte
const MENU_CONTEXT_NAME = "headlessui-menu-context";
export function useMenuContext(
componentName: string
): Readable<StateDefinition> {
let context: Writable<StateDefinition> | undefined =
getContext(MENU_CONTEXT_NAME);
if (context === undefined) {
throw new Error(
`<${componentName} /> is missing a parent <Menu /> component.`
);
}
return context;
}
// ...
setContext(MENU_CONTEXT_NAME, api);
// $lib/internal/context-store.ts
export function createContextStore<T>(
id = Symbol("context")
): [() => Readable<T> | undefined, (value: Writable<T>) => void] {
return [() => getContext(id), (value) => setContext(id, value)];
}
// $lib/components/menu/Menu.svelte
export const [getMenuContext, setMenuContext] =
createContextStore<StateDefinition>();
export function useMenuContext(componentName: string) {
const context = getMenuContext();
if (context) return context;
throw new Error(
`<${componentName} /> is missing a parent <Menu /> component.`
);
}
// ...
setMenuContext(api);
For example:
<script lang="ts">
import { RadioGroup, RadioGroupLabel, RadioGroupOption } from "$lib";
let showFirst = false;
let active: unknown;
</script>
<button on:click={() => (showFirst = !showFirst)}>Toggle</button>
<RadioGroup value={active} on:change={(e) => (active = e.detail)}>
<RadioGroupLabel>Pizza Delivery</RadioGroupLabel>
{#if showFirst}
<RadioGroupOption value="pickup">Pickup</RadioGroupOption>
{/if}
<RadioGroupOption value="home-delivery">Home delivery</RadioGroupOption>
<RadioGroupOption value="dine-in">Dine in</RadioGroupOption>
</RadioGroup>
Headless UI seems to handle this case for RadioGroup. The Vue implementation can port directly to Svelte: https://github.com/tailwindlabs/headlessui/blob/e9e6aded545c329585d8cf7452ad9fe400a5406b/packages/%40headlessui-vue/src/components/radio-group/radio-group.ts#L120
Interestingly they didn't bother handling this for other components, but I don't see why we shouldn't do the same thing for Listbox, Menu, and Tabs.
Filing this to keep track of it since I'm working on other areas right now. Will fix soon.
Haven't tested it and I'm a little suspicious of the code
When writing this initially I imitated the React API as closely as I could, to make it easy to adapt the Tailwind UI snippets.
However, a second guiding principle is that I want this to feel like a native Svelte library as much as possible. I believe that Tailwind follows this approach themselves with their React/Vue versions. And I think a native Svelte library should really just use bind here. Most convincing, though, is that the Vue library uses data binding with v-model
on several components.
Need to figure out the right way to semver this... I'm still on 1.0.0-beta.x but I think I might have to bump to 2.0 just for this change, not sure yet.
FIrstly, thanks so much for the work that's gone into porting this over for Svelte, it's incredibly valuable!
Secondly, I'm a bit of a novice so this is probably down to my ignorance. I don't know React and I'm beginner with Svelte so reading the official docs isn't a great help to me so far.
Question - at this line, class="{active && checked ? 'ring ring-offset-1' : ''} {!active && checked ? 'ring-2' : ''}
, active
and checked
are both erroring as undefined. No idea what I'm doing wrong. Please could you point me in the right direction?
<RadioGroup value={selectedColour} on:change={(e) => (selectedColour = e.detail)}>
<RadioGroupLabel class="block text-sm font-medium text-gray-900"
>Choose a label colour</RadioGroupLabel
>
<div class="mt-1 flex items-center space-x-3">
{#each colours as colour (colour.name)}
<RadioGroupOption
let:active
let:checked
value={colour}
class="{active && checked ? 'ring ring-offset-1' : ''}
{!active && checked ? 'ring-2' : ''}
{colour.selectedColour} -m-0.5 relative p-0.5 rounded-full flex items-center justify-center cursor-pointer focus:outline-none"
>
<RadioGroupLabel as="p" class="sr-only">
{colour.name}
</RadioGroupLabel>
<span
aria-hidden="true"
class="{colour.bgColour} h-8 w-8 border border-black border-opacity-10 rounded-full"
/>
</RadioGroupOption>
{/each}
</div>
</RadioGroup>
I am unable to click items inside of a popover when using safari. Brave seems to work just fine. I've tried using the below code as well as <PopoverPanel static>
manually controlling the open state to no luck.
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from '@rgossiaux/svelte-headlessui'
</script>
<Popover>
<PopoverButton>open popover</PopoverButton>
<PopoverPanel>
<button on:click={() => console.log('button clicked')}>button</button>
</PopoverPanel>
</Popover>
Tested in a fresh skeleton sveltekit app. Tabbing through the items and hitting enter does work. Clicking, however, causes the panel to disappear before firing any events.
Doing the same test using a fresh vite + vue app and the official @headlessui/vue
popover works as expected (including in safari):
<template>
<Popover>
<PopoverButton>open popover</PopoverButton>
<PopoverPanel>
<button @click="handleClick">button</button>
</PopoverPanel>
</Popover>
</template>
<script setup>
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
function handleClick() {
console.log("button clicked");
}
</script>
For some reason the buttons are not transitioned even though everything else is-- maybe because they are not children of the Transition. Need to understand the details more here. Not being able to render as a <Fragment> is causing the issue & it seems pretty hard to fix at first glance.
Hi there,
as someone who has never used React or Vue, it can be a challenge to crosscheck your documentation with the official headlessui docs. As such, I hope you don't mind a question (even if it seems very basic):
When using Listbox, am I only able to access the value of the Listbox by listening to the component event?
on:change((e) => selectedValue = e.detail)
as per your example?
This doesn't seem to work:
<Listbox as="div" let:value> <ListboxButton>{value}</ListboxButton> //returns undefined
If the above doesn't work, how do I access/make use of the component API? What confuses me is that in the official React docs, "value" is listed as a prop. Does this not automatically mean that the prop is exposed? (https://headlessui.dev/react/listbox#component-api)
I'm getting a critical error when using the Dialog
component with transitions then interacting (hover, click) with a Menu
component before the transition ends.
<Dialog {open} on:close={() => (open = false)}>
<div class="dialog" style={$primaryVars}>
<div transition:fade={{ duration: 300, easing: cubicOut }}>
<DialogOverlay class="dialog-overlay" />
<div
class="dialog-card"
in:fly={{ duration: 500, y: 50, easing: cubicOut }}
>
<DialogTitle as="h1" class="text-brand w-fit">My application</DialogTitle>
...
</div>
</div>
</div>
</Dialog>
The errors I'm getting are:
TypeError: stop is not a function. (In 'stop()', 'stop' is null)
[Error] TypeError: stop is not a function. (In 'stop()', 'stop' is null)
(anonymous function) (chunk-JTPRY6KS.js:50)
(anonymous function) (withError.ts:77)
forEach
run_all (chunk-QOGR3KCG.js:25)
destroy_component (chunk-QOGR3KCG.js:1643)
destroy (AuthDialog.svelte:352)
destroy (AuthDialog.svelte:785)
destroy (Dialog.svelte:118)
destroy (Render.svelte:241)
destroy (Div.svelte:118)
destroy_component (chunk-QOGR3KCG.js:1644)
destroy (Render.svelte:180)
(anonymous function) (chunk-QOGR3KCG.js:1011)
forEach
run_all (chunk-QOGR3KCG.js:25)
(anonymous function) (chunk-QOGR3KCG.js:1196)
(anonymous function) (chunk-QOGR3KCG.js:169)
forEach
run_tasks (chunk-QOGR3KCG.js:168)
Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'node.parentNode.removeChild')
[Error] Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'node.parentNode.removeChild')
detach (chunk-QOGR3KCG.js:321)
detach_dev (chunk-QOGR3KCG.js:1796)
destroy (Render.svelte:179)
(anonymous function) (chunk-QOGR3KCG.js:1011)
forEach
run_all (chunk-QOGR3KCG.js:25)
check_outros (chunk-QOGR3KCG.js:992)
update (Render.svelte:296)
update (chunk-QOGR3KCG.js:964)
flush (chunk-QOGR3KCG.js:935)
promiseReactionJob
macOS: 12.0.1 Monterey
MacBook Pro (15-inch, 2017)
Safari: Version 15.1 (17612.2.9.1.20)
Chrome: Version 97.0.4692.99 (Official Build)
@rgossiaux/svelte-headlessui: v1.0.0-beta.8
svelte: 3.46.2
@sveltejs/kit: 1.0.0-next.245
I'm using Windi CSS with Svelte Kit.
Most of the things work and are straight forward, but using the components I can't use the as
and class
props.
So I need to nest a div
or other html element. Is this intended?
A port from the headless ui react example
<script>
import {
RadioGroup,
RadioGroupDescription,
RadioGroupLabel,
RadioGroupOption
} from '@rgossiaux/svelte-headlessui';
const plans = [
{
name: 'Startup',
ram: '12GB',
cpus: '6 CPUs',
disk: '160 GB SSD disk'
},
{
name: 'Business',
ram: '16GB',
cpus: '8 CPUs',
disk: '512 GB SSD disk'
},
{
name: 'Enterprise',
ram: '32GB',
cpus: '12 CPUs',
disk: '1024 GB SSD disk'
}
];
let value = plans[0];
</script>
<div class="w-full px-4 py-16">
<div class="w-full max-w-md mx-auto">
<RadioGroup {value} on:change={(e) => (value = plans.find((p) => p.name === e.detail.name))}>
<RadioGroupLabel>
<div class="sr-only">Server size</div>
</RadioGroupLabel>
<div class="space-y-2">
{#each plans as plan (plan.name)}
<RadioGroupOption key={plan.name} value={plan} let:active let:checked>
<div
class={`
relative rounded-lg shadow-md px-5 py-4 cursor-pointer flex focus:outline-none
${checked ? 'bg-sky-900 bg-opacity-75 text-white' : 'bg-white'} ${
active ? 'ring-2 ring-offset-2 ring-offset-sky-300 ring-white ring-opacity-60' : ''
}
`}
>
<div class="flex items-center justify-between w-full">
<div class="flex items-center">
<div class="text-sm">
<RadioGroupLabel>
<p class={`font-medium ${checked ? 'text-white' : 'text-gray-900'}`}>
{plan.name}
</p>
</RadioGroupLabel>
<RadioGroupDescription>
<span class={`inline ${checked ? 'text-sky-100' : 'text-gray-500'}`}>
<span>
{plan.ram}/{plan.cpus}
</span>
<span aria-hidden="true">·</span>{' '}
<span>{plan.disk}</span>
</span>
</RadioGroupDescription>
</div>
</div>
{#if checked}
<div class="flex-shrink-0 text-white">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none">
<circle cx={12} cy={12} r={12} fill="#fff" opacity="0.2" />
<path
d="M7 13l3 3 7-7"
stroke="#fff"
stroke-width={1.5}
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
{/if}
</div>
</div>
</RadioGroupOption>
{/each}
</div>
</RadioGroup>
</div>
</div>
Hey all, I have a TransitionChild that contains a <div>
tag with a class something downs the line of class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-purple-700"
. However, when I run this the sizing is all off. I have found that in the inspection there is an additional <div>
tag insterted before the body of a TransitionChild
with class= " "
. This additional div is throwing off my css and the resizing. Is this the expected behaviour and I should work around this? Or is this a bug that needs to get addressed?
Hi,
New to svelte here. I tried using one of TailwindUI components and got the error below.
TransitionRoot is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules
Aware that this is already planned by the readme, but I thought that I might open this issue to track it anyways. Might suggest a few things, since documenting svelte components can be made a lot easier than expected.
Few neat things that can help:
Hello!
I've been tinkering with this library today and it's been great! One thing I've run into is that the packaging that's been done so far doesn't generate typings for the various slotProps
that are used; i.e., the slotProps
for ListBoxOption
is {}
instead of correctly picking up the active/selected/disabled
props.
Root cause here is spreading the slotProps
onto the slot
, instead of defining them manually. Looks like if you did something like the below, sveltekit package
picks up the types correctly
<slot active={slotProps.active} ...etc/>
https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#prebundlesveltelibraries
Should spend some time on the bundling of the library in general and make sure it's working correctly
I've made REPL for showing this behavior.
If you will try to navigate by keyboard through the first Menu links will redirect by pressing enter. But it will not work in the second example.
Ignored this while porting but now with <Render>
it's time to add it.
I am seeing these warning logs at first load.
Forgive my ignorance, am new to front end dev, and also both to Sveltekit
and Vite
. Is it important for vite-plugin-svelte
to care about node_modules
or should it ignore everything except what's in my src
folder?
yarn run v1.22.15
$ svelte-kit dev --host 0.0.0.0
Pre-bundling dependencies:
svelte/store
svelte
svelte-hero-icons
svelte/animate
svelte/easing
(...and 3 more)
(this will be run only when your dependencies or config have changed)
SvelteKit v1.0.0-next.202
local: http://localhost:3000
network: http://172.18.0.2:3000
Note that all files in the following directories will be accessible to anyone on your network: static, src/lib, src/routes, src, .svelte-kit, node_modules
1:30:36 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/dialog/Dialog.svelte:2:11 Dialog has unused export property 'DialogStates'. If it is for external reference only, please consider using `export const DialogStates`
1:30:36 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/disclosure/Disclosure.svelte:3:11 Disclosure has unused export property 'DisclosureStates'. If it is for external reference only, please consider using `export const DisclosureStates`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/listbox/Listbox.svelte:1:37 Listbox has unused export property 'ListboxStates'. If it is for external reference only, please consider using `export const ListboxStates`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/menu/Menu.svelte:9:11 Menu has unused export property 'MenuStates'. If it is for external reference only, please consider using `export const MenuStates`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/popover/Popover.svelte:1:37 Popover has unused export property 'PopoverStates'. If it is for external reference only, please consider using `export const PopoverStates`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/transitions/TransitionRoot.svelte:1:37 TransitionRoot has unused export property 'TreeStates'. If it is for external reference only, please consider using `export const TreeStates`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/internal/StackContextProvider.svelte:1:37 StackContextProvider has unused export property 'StackMessage'. If it is for external reference only, please consider using `export const StackMessage`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/utils/Render.svelte:21:11 Render has unused export property 'RenderStrategy'. If it is for external reference only, please consider using `export const RenderStrategy`
1:30:37 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/utils/Render.svelte:3:11 Render has unused export property 'Features'. If it is for external reference only, please consider using `export const Features`
1:30:39 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/dialog/Dialog.svelte:2:11 Dialog has unused export property 'DialogStates'. If it is for external reference only, please consider using `export const DialogStates`
1:30:39 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/disclosure/Disclosure.svelte:3:11 Disclosure has unused export property 'DisclosureStates'. If it is for external reference only, please consider using `export const DisclosureStates`
1:30:39 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/listbox/Listbox.svelte:1:37 Listbox has unused export property 'ListboxStates'. If it is for external reference only, please consider using `export const ListboxStates`
1:30:40 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/popover/Popover.svelte:1:37 Popover has unused export property 'PopoverStates'. If it is for external reference only, please consider using `export const PopoverStates`
1:30:40 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/menu/Menu.svelte:9:11 Menu has unused export property 'MenuStates'. If it is for external reference only, please consider using `export const MenuStates`
1:30:41 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/components/transitions/TransitionRoot.svelte:1:37 TransitionRoot has unused export property 'TreeStates'. If it is for external reference only, please consider using `export const TreeStates`
1:30:41 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/internal/StackContextProvider.svelte:1:37 StackContextProvider has unused export property 'StackMessage'. If it is for external reference only, please consider using `export const StackMessage`
1:30:41 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/utils/Render.svelte:21:11 Render has unused export property 'RenderStrategy'. If it is for external reference only, please consider using `export const RenderStrategy`
1:30:41 AM [vite-plugin-svelte] /app/node_modules/@rgossiaux/svelte-headlessui/utils/Render.svelte:3:11 Render has unused export property 'Features'. If it is for external reference only, please consider using `export const Features`
The WAI-ARIA spec for Listbox mentions (emphasis mine):
Type a character: focus moves to the next item with a name that starts with the typed character.
However, as reported by a user on Reddit, Headless UI does not do that; instead, we search for the first item with a name that starts with the typed character. You can see this in action on https://headlessui.dev/react/menu by repeatedly typing the letter d (with pauses in between). This affects Listbox and Menu both.
Something I've been thinking about recently--it would be nice if I could add in some error messages if you use React-style prop names (className, onChange, htmlFor, and some SVG attributes come to mind). Ideally I can get these to throw a type error, but even a compile or runtime error will make it a lot friendlier for people porting React code (ie users of Tailwind UI).
We already forward use:
(via the use=
prop) and on:
(via some runtime hacks). We should consider supporting equivalents for the transition/animation directives too: something like in=
, out=
, transition=
, and animate=
. Not sure if any other popular component library supports this, but I think it's probably pretty useful.
Because these components spread $$restProps all over the place, the TypeScript support for props besides those export
ed by the component is lacking. Concretely that means:
<Render>
uses which come via $$restProps
: class
and style
--particularly when using them as functions.It would be nice if we could do this semi-automatically (sveltejs/language-tools#1363), but we can also use $$Props
to type the component manually. Ideally we can get support for some similar $$RestProps
type of interface (suggesting this to the language-tools folks) but if not we can still use $$Props
--it'll just be a bit more painful since we now have to worry about forgetting to add any props there.
Describe the bug
I have a Menu with MenuItem links which can be activated by mouse but not by keyboard.
To Reproduce
Here's the source for the above site:
https://github.com/vhscom/sveltekit-headlessui-starter
And a deep link to the MenuItem implementation:
https://github.com/vhscom/sveltekit-headlessui-starter/blob/bd7df2ff5b248bec95e94b126db42193be08a2f4/src/lib/shared/components/navigation/GlobalNav.svelte#L111-L122
Example linked:
<MenuItem let:active>
<a
href="/account/profile"
sveltekit:prefetch
class={classes(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700'
)}
>
Your Profile
</a>
</MenuItem>
Library version
lib: [email protected]
kit: [email protected]
I have been waiting for this for almost a year. At this point there's nothing wrong if we try not to make it 'official'.
Reproduction Repo: https://github.com/jerriclynsjohn/svelte-headlessui-test-repo.git
I'm trying to use this in a regular svelte project, I've set up a normal repo with only tailwindcss as the additional dependency, and still, when I try to import and use Switch it breaks.
Tested with the svelte kit and it works perfectly but doesn't work with a regular svelte SPA
I noticed I can't select text in Dialogs on all browsers. This doesn't apply to input elements though.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.