@rgossiaux/svelte-headlessui
Advanced tools
Comparing version 1.0.0-beta.4 to 1.0.0-beta.5
@@ -7,2 +7,3 @@ import { SvelteComponentTyped } from "svelte"; | ||
onClose?: (() => void) | undefined; | ||
buttonInside?: boolean | undefined; | ||
buttonText?: string | null | undefined; | ||
@@ -9,0 +10,0 @@ buttonProps?: {} | undefined; |
@@ -18,3 +18,3 @@ export function useActions(node, actions) { | ||
if (((actions && actions.length) || 0) != actionReturns.length) { | ||
throw new Error('You must not change the length of an actions array.'); | ||
throw new Error("You must not change the length of an actions array."); | ||
} | ||
@@ -21,0 +21,0 @@ if (actions) { |
@@ -21,2 +21,3 @@ import type { SvelteComponent } from "svelte"; | ||
import Footer from "./Footer.svelte"; | ||
import Form from "./Form.svelte"; | ||
import H1 from "./H1.svelte"; | ||
@@ -30,2 +31,3 @@ import H2 from "./H2.svelte"; | ||
import I from "./I.svelte"; | ||
import Input from "./Input.svelte"; | ||
import Label from "./Label.svelte"; | ||
@@ -61,2 +63,3 @@ import Li from "./Li.svelte"; | ||
footer: typeof Footer; | ||
form: typeof Form; | ||
h1: typeof H1; | ||
@@ -70,2 +73,3 @@ h2: typeof H2; | ||
i: typeof I; | ||
input: typeof Input; | ||
label: typeof Label; | ||
@@ -84,3 +88,3 @@ li: typeof Li; | ||
export declare type SupportedAs = SupportedElement | SvelteComponent; | ||
export declare function getElementComponent(name: SupportedElement): typeof A | typeof Address | typeof Article | typeof Aside | typeof B | typeof Bdi | typeof Bdo | typeof Blockquote | typeof Button | typeof Cite | typeof Code | typeof Data | typeof Datalist | typeof Dd | typeof Dl | typeof Dt | typeof Div | typeof Em | typeof Footer | typeof H1 | typeof H2 | typeof H3 | typeof H4 | typeof H5 | typeof H6 | typeof Header | typeof I | typeof Label | typeof Li | typeof Main | typeof Nav | typeof Ol | typeof P | typeof Section | typeof Span | typeof Strong | typeof Ul; | ||
export declare function getElementComponent(name: SupportedElement): typeof A | typeof Address | typeof Article | typeof Aside | typeof B | typeof Bdi | typeof Bdo | typeof Blockquote | typeof Button | typeof Cite | typeof Code | typeof Data | typeof Datalist | typeof Dd | typeof Dl | typeof Dt | typeof Div | typeof Em | typeof Footer | typeof Form | typeof H1 | typeof H2 | typeof H3 | typeof H4 | typeof H5 | typeof H6 | typeof Header | typeof I | typeof Input | typeof Label | typeof Li | typeof Main | typeof Nav | typeof Ol | typeof P | typeof Section | typeof Span | typeof Strong | typeof Ul; | ||
export {}; |
@@ -20,2 +20,3 @@ import A from "./A.svelte"; | ||
import Footer from "./Footer.svelte"; | ||
import Form from "./Form.svelte"; | ||
import H1 from "./H1.svelte"; | ||
@@ -29,2 +30,3 @@ import H2 from "./H2.svelte"; | ||
import I from "./I.svelte"; | ||
import Input from "./Input.svelte"; | ||
import Label from "./Label.svelte"; | ||
@@ -41,39 +43,41 @@ import Li from "./Li.svelte"; | ||
const components = { | ||
"a": A, | ||
"address": Address, | ||
"article": Article, | ||
"aside": Aside, | ||
"b": B, | ||
"bdi": Bdi, | ||
"bdo": Bdo, | ||
"blockquote": Blockquote, | ||
"button": Button, | ||
"cite": Cite, | ||
"code": Code, | ||
"data": Data, | ||
"datalist": Datalist, | ||
"dd": Dd, | ||
"dl": Dl, | ||
"dt": Dt, | ||
"div": Div, | ||
"em": Em, | ||
"footer": Footer, | ||
"h1": H1, | ||
"h2": H2, | ||
"h3": H3, | ||
"h4": H4, | ||
"h5": H5, | ||
"h6": H6, | ||
"header": Header, | ||
"i": I, | ||
"label": Label, | ||
"li": Li, | ||
"main": Main, | ||
"nav": Nav, | ||
"ol": Ol, | ||
"p": P, | ||
"section": Section, | ||
"span": Span, | ||
"strong": Strong, | ||
"ul": Ul, | ||
a: A, | ||
address: Address, | ||
article: Article, | ||
aside: Aside, | ||
b: B, | ||
bdi: Bdi, | ||
bdo: Bdo, | ||
blockquote: Blockquote, | ||
button: Button, | ||
cite: Cite, | ||
code: Code, | ||
data: Data, | ||
datalist: Datalist, | ||
dd: Dd, | ||
dl: Dl, | ||
dt: Dt, | ||
div: Div, | ||
em: Em, | ||
footer: Footer, | ||
form: Form, | ||
h1: H1, | ||
h2: H2, | ||
h3: H3, | ||
h4: H4, | ||
h5: H5, | ||
h6: H6, | ||
header: Header, | ||
i: I, | ||
input: Input, | ||
label: Label, | ||
li: Li, | ||
main: Main, | ||
nav: Nav, | ||
ol: Ol, | ||
p: P, | ||
section: Section, | ||
span: Span, | ||
strong: Strong, | ||
ul: Ul, | ||
}; | ||
@@ -80,0 +84,0 @@ export function getElementComponent(name) { |
@@ -1,4 +0,9 @@ | ||
import type { SvelteComponent } from 'svelte'; | ||
export declare function forwardEventsBuilder(component: SvelteComponent, except?: string[]): (node: HTMLElement | SVGElement) => { | ||
import type { SvelteComponent } from "svelte"; | ||
declare type ForwardException = string | { | ||
name: string; | ||
shouldExclude: () => boolean; | ||
}; | ||
export declare function forwardEventsBuilder(component: SvelteComponent, except?: ForwardException[]): (node: HTMLElement | SVGElement) => { | ||
destroy: () => void; | ||
}; | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import { bubble, listen, prevent_default, stop_propagation, } from 'svelte/internal'; | ||
import { bubble, listen, prevent_default, stop_propagation, } from "svelte/internal"; | ||
const MODIFIER_DIVIDER = "!"; | ||
@@ -13,11 +13,22 @@ const modifierRegex = new RegExp(`^[^${MODIFIER_DIVIDER}]+(?:${MODIFIER_DIVIDER}(?:preventDefault|stopPropagation|passive|nonpassive|capture|once|self))+$`); | ||
let destructor = () => { }; | ||
if (except.includes(eventType)) { | ||
// Bail out of the event forwarding and run the normal Svelte $on() code | ||
const callbacks = (component.$$.callbacks[eventType] || (component.$$.callbacks[eventType] = [])); | ||
callbacks.push(callback); | ||
return () => { | ||
const index = callbacks.indexOf(callback); | ||
if (index !== -1) | ||
callbacks.splice(index, 1); | ||
}; | ||
for (let exception of except) { | ||
if (typeof exception === "string" && exception === eventType) { | ||
// Bail out of the event forwarding and run the normal Svelte $on() code | ||
const callbacks = component.$$.callbacks[eventType] || | ||
(component.$$.callbacks[eventType] = []); | ||
callbacks.push(callback); | ||
return () => { | ||
const index = callbacks.indexOf(callback); | ||
if (index !== -1) | ||
callbacks.splice(index, 1); | ||
}; | ||
} | ||
if (typeof exception === "object" && exception["name"] === eventType) { | ||
let oldCallback = callback; | ||
callback = (...props) => { | ||
if (!(typeof exception === "object" && exception["shouldExclude"]())) { | ||
oldCallback(...props); | ||
} | ||
}; | ||
} | ||
} | ||
@@ -24,0 +35,0 @@ if ($on) { |
@@ -18,3 +18,3 @@ { | ||
"repository": "github:rgossiaux/svelte-headlessui", | ||
"version": "1.0.0-beta.4", | ||
"version": "1.0.0-beta.5", | ||
"peerDependencies": { | ||
@@ -21,0 +21,0 @@ "svelte": "^3.44.0" |
# svelte-headlessui | ||
This is an unofficial, complete Svelte port of the Headless UI component library (https://headlessui.dev/). It contains **fully accessible, feature-rich, unstyled** UI components. | ||
This is an unofficial, complete Svelte port of the Headless UI component library (https://headlessui.dev/). It contains **fully accessible, feature-rich, unstyled** UI components. | ||
@@ -9,8 +9,8 @@ ## Who is this for? | ||
* You want unstyled yet sophisticated customizable UI components that fully follow the WAI-ARIA specs. You want a component library to handle all the messy details (keyboard navigation, focus management, aria-* attributes, and many many more), but you want to style your components yourself and not be constrained by existing design systems like Material UI. | ||
* You want to use the commercial Tailwind UI component library (https://tailwindui.com/) in your Svelte project, and want a drop-in replacement for the React components which power Tailwind UI. | ||
- You want unstyled yet sophisticated customizable UI components that fully follow the WAI-ARIA specs. You want a component library to handle all the messy details (keyboard navigation, focus management, aria-\* attributes, and many many more), but you want to style your components yourself and not be constrained by existing design systems like Material UI. | ||
- You want to use the commercial Tailwind UI component library (https://tailwindui.com/) in your Svelte project, and want a drop-in replacement for the React components which power Tailwind UI. | ||
This project is intended to keep an API as close as possible to the React API for the base Headless UI project, with only a few small differences. While one of the primary goals is to enable using Tailwind UI in a Svelte project with as little effort as possible, **neither Tailwind UI nor Tailwind CSS is required** to use these components. | ||
This project is intended to keep an API as close as possible to the React API for the base Headless UI project, with only a few small differences. While one of the primary goals is to enable using Tailwind UI in a Svelte project with as little effort as possible, **neither Tailwind UI nor Tailwind CSS is required** to use these components. | ||
This project is an **unofficial** port. I have no affiliation with Tailwind Labs and cannot offer commercial support for this project. With that said, I intend to keep it as up to date as possible with the upstream Headless UI project, including porting new components when they are released. | ||
This project is an **unofficial** port. I have no affiliation with Tailwind Labs and cannot offer commercial support for this project. With that said, I intend to keep it as up to date as possible with the upstream Headless UI project, including porting new components when they are released. | ||
@@ -26,4 +26,6 @@ ## Installation | ||
For now, until I write separate documentation, you can refer to the [Headless UI React documentation](https://headlessui.dev/). The API is nearly identical to the React API there, with the following differences: | ||
* Components do not have . in their names; use `ListboxButton` instead of `Listbox.Button` | ||
* Event handlers are done Svelte-style with custom events: | ||
- Components do not have . in their names; use `ListboxButton` instead of `Listbox.Button` | ||
- Event handlers are done Svelte-style with custom events: | ||
``` | ||
@@ -43,3 +45,4 @@ // React version | ||
* Instead of render props, we use Svelte's [slot props](https://svelte.dev/tutorial/slot-props): | ||
- Instead of render props, we use Svelte's [slot props](https://svelte.dev/tutorial/slot-props): | ||
``` | ||
@@ -56,4 +59,6 @@ // React version | ||
``` | ||
* When porting React code, HTML attributes use their real names instead of the camelCased React versions. In particular, use `class=` instead of `className=`. | ||
* When porting React code, use `{#each}` instead of `.map` (don't forget to add a key!): | ||
- When porting React code, HTML attributes use their real names instead of the camelCased React versions. In particular, use `class=` instead of `className=`. | ||
- When porting React code, use `{#each}` instead of `.map` (don't forget to add a key!): | ||
``` | ||
@@ -65,3 +70,3 @@ // React version | ||
... | ||
<!--- Svelte version ---> | ||
@@ -72,12 +77,15 @@ {#each people as person (person.id)} | ||
``` | ||
Similarly, React's `{value && (<Component />)}` style syntax becomes `{#if value} <Component /> {/if}` in Svelte, of course. | ||
* While the `as` prop is supported, `as={Fragment}` support is not possible due to limitations in Svelte itself. You'll have to settle for rendering a `div` or similar. Usually this won't cause any problems, especially if you are writing your own code, but if you are porting React Headless UI code that both uses `as={Fragment}` and `z-index`, your div could possibly create a new [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context), causing the `z-index` to not work correctly. If this happens, you can fix by copying the `z-index` (and a relevant `position`) to the component that used to render as a `Fragment`. | ||
Furthermore, specific to Svelte, you may | ||
* Pass [actions](https://svelte.dev/tutorial/actions) to any component in this library using the `use` prop, with the syntax `use={[[action1, action1options], [action2], [action3, action3options], ...]}`, and they will be forwarded to the underlying DOM element. | ||
* Add your own event listeners with modifiers, which will be forwarded to the underyling DOM element. Modifiers are separated with the `!` character instead of the normal `|`: `on:click!capture={(e) => ...}` | ||
- While the `as` prop is supported, `as={Fragment}` support is not possible due to limitations in Svelte itself. You'll have to settle for rendering a `div` or similar. Usually this won't cause any problems, especially if you are writing your own code, but if you are porting React Headless UI code that both uses `as={Fragment}` and `z-index`, your div could possibly create a new [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context), causing the `z-index` to not work correctly. If this happens, you can fix by copying the `z-index` (and a relevant `position`) to the component that used to render as a `Fragment`. | ||
Furthermore, specific to Svelte, you may | ||
- Pass [actions](https://svelte.dev/tutorial/actions) to any component in this library using the `use` prop, with the syntax `use={[[action1, action1options], [action2], [action3, action3options], ...]}`, and they will be forwarded to the underlying DOM element. | ||
- Add your own event listeners with modifiers, which will be forwarded to the underyling DOM element. Modifiers are separated with the `!` character instead of the normal `|`: `on:click!capture={(e) => ...}` | ||
## Credits | ||
Credit for everything good about this library goes to Tailwind Labs for writing the original React/Vue versions. All bugs should be assumed to be my fault in the port (though as the codebases are so similar, bugs in upstream will likely affect this library too). | ||
Credit for everything good about this library goes to Tailwind Labs for writing the original React/Vue versions. All bugs should be assumed to be my fault in the port (though as the codebases are so similar, bugs in upstream will likely affect this library too). | ||
@@ -84,0 +92,0 @@ Additional thanks to https://github.com/hperrin/svelte-material-ui; this well-engineered Svelte library was the source of the action and event forwarding code, with minor modifications. |
@@ -49,3 +49,3 @@ export declare function getMenuButton(): HTMLElement | null; | ||
state: ListboxState; | ||
orientation?: 'horizontal' | 'vertical'; | ||
orientation?: "horizontal" | "vertical"; | ||
}, listbox?: HTMLElement | null): void; | ||
@@ -175,3 +175,3 @@ export declare function assertListboxButton(options: { | ||
active: number; | ||
orientation?: 'vertical' | 'horizontal'; | ||
orientation?: "vertical" | "horizontal"; | ||
}, list?: HTMLElement | null, tabs?: HTMLElement[], panels?: HTMLElement[]): void; | ||
@@ -178,0 +178,0 @@ export declare function assertActiveElement(element: HTMLElement | null): void; |
@@ -1,4 +0,4 @@ | ||
import { isFocusableElement, FocusableMode } from '../utils/focus-management'; | ||
import { isFocusableElement, FocusableMode } from "../utils/focus-management"; | ||
function assertNever(x) { | ||
throw new Error('Unexpected object: ' + x); | ||
throw new Error("Unexpected object: " + x); | ||
} | ||
@@ -36,25 +36,25 @@ // --- | ||
// Ensure menu button have these properties | ||
expect(button).toHaveAttribute('id'); | ||
expect(button).toHaveAttribute('aria-haspopup'); | ||
expect(button).toHaveAttribute("id"); | ||
expect(button).toHaveAttribute("aria-haspopup"); | ||
switch (options.state) { | ||
case MenuState.Visible: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
expect(button).toHaveAttribute('aria-expanded', 'true'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
expect(button).toHaveAttribute("aria-expanded", "true"); | ||
break; | ||
case MenuState.InvisibleHidden: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
break; | ||
case MenuState.InvisibleUnmounted: | ||
expect(button).not.toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).not.toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
@@ -85,4 +85,4 @@ break; | ||
// Ensure link between button & menu is correct | ||
expect(button).toHaveAttribute('aria-controls', menu.getAttribute('id')); | ||
expect(menu).toHaveAttribute('aria-labelledby', button.getAttribute('id')); | ||
expect(button).toHaveAttribute("aria-controls", menu.getAttribute("id")); | ||
expect(menu).toHaveAttribute("aria-labelledby", button.getAttribute("id")); | ||
} | ||
@@ -101,3 +101,3 @@ catch (err) { | ||
// Ensure link between menu & menu item is correct | ||
expect(menu).toHaveAttribute('aria-activedescendant', item.getAttribute('id')); | ||
expect(menu).toHaveAttribute("aria-activedescendant", item.getAttribute("id")); | ||
} | ||
@@ -114,3 +114,3 @@ catch (err) { | ||
// Ensure we don't have an active menu | ||
expect(menu).not.toHaveAttribute('aria-activedescendant'); | ||
expect(menu).not.toHaveAttribute("aria-activedescendant"); | ||
} | ||
@@ -129,4 +129,4 @@ catch (err) { | ||
assertHidden(menu); | ||
expect(menu).toHaveAttribute('aria-labelledby'); | ||
expect(menu).toHaveAttribute('role', 'menu'); | ||
expect(menu).toHaveAttribute("aria-labelledby"); | ||
expect(menu).toHaveAttribute("role", "menu"); | ||
if (options.textContent) | ||
@@ -142,4 +142,4 @@ expect(menu).toHaveTextContent(options.textContent); | ||
assertVisible(menu); | ||
expect(menu).toHaveAttribute('aria-labelledby'); | ||
expect(menu).toHaveAttribute('role', 'menu'); | ||
expect(menu).toHaveAttribute("aria-labelledby"); | ||
expect(menu).toHaveAttribute("role", "menu"); | ||
if (options.textContent) | ||
@@ -169,7 +169,7 @@ expect(menu).toHaveTextContent(options.textContent); | ||
// time, we just require them. | ||
expect(item).toHaveAttribute('id'); | ||
expect(item).toHaveAttribute("id"); | ||
// Check that we have the correct values for certain attributes | ||
expect(item).toHaveAttribute('role', 'menuitem'); | ||
if (!item.getAttribute('aria-disabled')) | ||
expect(item).toHaveAttribute('tabindex', '-1'); | ||
expect(item).toHaveAttribute("role", "menuitem"); | ||
if (!item.getAttribute("aria-disabled")) | ||
expect(item).toHaveAttribute("tabindex", "-1"); | ||
// Ensure menu button has the following attributes | ||
@@ -220,3 +220,3 @@ if (options) { | ||
export function assertListbox(options, listbox = getListbox()) { | ||
let { orientation = 'vertical' } = options; | ||
let { orientation = "vertical" } = options; | ||
try { | ||
@@ -228,5 +228,5 @@ switch (options.state) { | ||
assertHidden(listbox); | ||
expect(listbox).toHaveAttribute('aria-labelledby'); | ||
expect(listbox).toHaveAttribute('aria-orientation', orientation); | ||
expect(listbox).toHaveAttribute('role', 'listbox'); | ||
expect(listbox).toHaveAttribute("aria-labelledby"); | ||
expect(listbox).toHaveAttribute("aria-orientation", orientation); | ||
expect(listbox).toHaveAttribute("role", "listbox"); | ||
if (options.textContent) | ||
@@ -242,5 +242,5 @@ expect(listbox).toHaveTextContent(options.textContent); | ||
assertVisible(listbox); | ||
expect(listbox).toHaveAttribute('aria-labelledby'); | ||
expect(listbox).toHaveAttribute('aria-orientation', orientation); | ||
expect(listbox).toHaveAttribute('role', 'listbox'); | ||
expect(listbox).toHaveAttribute("aria-labelledby"); | ||
expect(listbox).toHaveAttribute("aria-orientation", orientation); | ||
expect(listbox).toHaveAttribute("role", "listbox"); | ||
if (options.textContent) | ||
@@ -269,25 +269,25 @@ expect(listbox).toHaveTextContent(options.textContent); | ||
// Ensure menu button have these properties | ||
expect(button).toHaveAttribute('id'); | ||
expect(button).toHaveAttribute('aria-haspopup'); | ||
expect(button).toHaveAttribute("id"); | ||
expect(button).toHaveAttribute("aria-haspopup"); | ||
switch (options.state) { | ||
case ListboxState.Visible: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
expect(button).toHaveAttribute('aria-expanded', 'true'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
expect(button).toHaveAttribute("aria-expanded", "true"); | ||
break; | ||
case ListboxState.InvisibleHidden: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
break; | ||
case ListboxState.InvisibleUnmounted: | ||
expect(button).not.toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).not.toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
@@ -316,3 +316,3 @@ break; | ||
// Ensure menu button have these properties | ||
expect(label).toHaveAttribute('id'); | ||
expect(label).toHaveAttribute("id"); | ||
if (options.textContent) { | ||
@@ -341,4 +341,4 @@ expect(label).toHaveTextContent(options.textContent); | ||
// Ensure link between button & listbox is correct | ||
expect(button).toHaveAttribute('aria-controls', listbox.getAttribute('id')); | ||
expect(listbox).toHaveAttribute('aria-labelledby', button.getAttribute('id')); | ||
expect(button).toHaveAttribute("aria-controls", listbox.getAttribute("id")); | ||
expect(listbox).toHaveAttribute("aria-labelledby", button.getAttribute("id")); | ||
} | ||
@@ -356,3 +356,3 @@ catch (err) { | ||
return expect(listbox).not.toBe(null); | ||
expect(listbox).toHaveAttribute('aria-labelledby', label.getAttribute('id')); | ||
expect(listbox).toHaveAttribute("aria-labelledby", label.getAttribute("id")); | ||
} | ||
@@ -371,3 +371,3 @@ catch (err) { | ||
// Ensure link between button & label is correct | ||
expect(button).toHaveAttribute('aria-labelledby', `${label.id} ${button.id}`); | ||
expect(button).toHaveAttribute("aria-labelledby", `${label.id} ${button.id}`); | ||
} | ||
@@ -386,3 +386,3 @@ catch (err) { | ||
// Ensure link between listbox & listbox item is correct | ||
expect(listbox).toHaveAttribute('aria-activedescendant', item.getAttribute('id')); | ||
expect(listbox).toHaveAttribute("aria-activedescendant", item.getAttribute("id")); | ||
} | ||
@@ -399,3 +399,3 @@ catch (err) { | ||
// Ensure we don't have an active listbox | ||
expect(listbox).not.toHaveAttribute('aria-activedescendant'); | ||
expect(listbox).not.toHaveAttribute("aria-activedescendant"); | ||
} | ||
@@ -410,3 +410,3 @@ catch (err) { | ||
for (let item of items) | ||
expect(item).not.toHaveAttribute('aria-selected'); | ||
expect(item).not.toHaveAttribute("aria-selected"); | ||
} | ||
@@ -424,7 +424,7 @@ catch (err) { | ||
// time, we just require them. | ||
expect(item).toHaveAttribute('id'); | ||
expect(item).toHaveAttribute("id"); | ||
// Check that we have the correct values for certain attributes | ||
expect(item).toHaveAttribute('role', 'option'); | ||
if (!item.getAttribute('aria-disabled')) | ||
expect(item).toHaveAttribute('tabindex', '-1'); | ||
expect(item).toHaveAttribute("role", "option"); | ||
if (!item.getAttribute("aria-disabled")) | ||
expect(item).toHaveAttribute("tabindex", "-1"); | ||
// Ensure listbox button has the following attributes | ||
@@ -442,5 +442,5 @@ if (!options) | ||
case true: | ||
return expect(item).toHaveAttribute('aria-selected', 'true'); | ||
return expect(item).toHaveAttribute("aria-selected", "true"); | ||
case false: | ||
return expect(item).not.toHaveAttribute('aria-selected'); | ||
return expect(item).not.toHaveAttribute("aria-selected"); | ||
default: | ||
@@ -473,4 +473,4 @@ assertNever(options.selected); | ||
return expect(switchElement).not.toBe(null); | ||
expect(switchElement).toHaveAttribute('role', 'switch'); | ||
expect(switchElement).toHaveAttribute('tabindex', '0'); | ||
expect(switchElement).toHaveAttribute("role", "switch"); | ||
expect(switchElement).toHaveAttribute("tabindex", "0"); | ||
if (options.textContent) { | ||
@@ -490,6 +490,6 @@ expect(switchElement).toHaveTextContent(options.textContent); | ||
case SwitchState.On: | ||
expect(switchElement).toHaveAttribute('aria-checked', 'true'); | ||
expect(switchElement).toHaveAttribute("aria-checked", "true"); | ||
break; | ||
case SwitchState.Off: | ||
expect(switchElement).toHaveAttribute('aria-checked', 'false'); | ||
expect(switchElement).toHaveAttribute("aria-checked", "false"); | ||
break; | ||
@@ -528,24 +528,24 @@ default: | ||
// Ensure disclosure button have these properties | ||
expect(button).toHaveAttribute('id'); | ||
expect(button).toHaveAttribute("id"); | ||
switch (options.state) { | ||
case DisclosureState.Visible: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
expect(button).toHaveAttribute('aria-expanded', 'true'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
expect(button).toHaveAttribute("aria-expanded", "true"); | ||
break; | ||
case DisclosureState.InvisibleHidden: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
break; | ||
case DisclosureState.InvisibleUnmounted: | ||
expect(button).not.toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).not.toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
@@ -630,24 +630,24 @@ break; | ||
// Ensure popover button have these properties | ||
expect(button).toHaveAttribute('id'); | ||
expect(button).toHaveAttribute("id"); | ||
switch (options.state) { | ||
case PopoverState.Visible: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
expect(button).toHaveAttribute('aria-expanded', 'true'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
expect(button).toHaveAttribute("aria-expanded", "true"); | ||
break; | ||
case PopoverState.InvisibleHidden: | ||
expect(button).toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
break; | ||
case PopoverState.InvisibleUnmounted: | ||
expect(button).not.toHaveAttribute('aria-controls'); | ||
if (button.hasAttribute('disabled')) { | ||
expect(button).not.toHaveAttribute('aria-expanded'); | ||
expect(button).not.toHaveAttribute("aria-controls"); | ||
if (button.hasAttribute("disabled")) { | ||
expect(button).not.toHaveAttribute("aria-expanded"); | ||
} | ||
else { | ||
expect(button).toHaveAttribute('aria-expanded', 'false'); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
} | ||
@@ -710,12 +710,13 @@ break; | ||
return expect(element).not.toBe(null); | ||
if (element.hasAttribute('aria-labelledby')) { | ||
let ids = element.getAttribute('aria-labelledby').split(' '); | ||
expect(ids.map(id => document.getElementById(id)?.textContent).join(' ')).toEqual(value); | ||
if (element.hasAttribute("aria-labelledby")) { | ||
let ids = element.getAttribute("aria-labelledby").split(" "); | ||
expect(ids.map((id) => document.getElementById(id)?.textContent).join(" ")).toEqual(value); | ||
return; | ||
} | ||
if (element.hasAttribute('aria-label')) { | ||
expect(element).toHaveAttribute('aria-label', value); | ||
if (element.hasAttribute("aria-label")) { | ||
expect(element).toHaveAttribute("aria-label", value); | ||
return; | ||
} | ||
if (element.hasAttribute('id') && document.querySelectorAll(`[for="${element.id}"]`).length > 0) { | ||
if (element.hasAttribute("id") && | ||
document.querySelectorAll(`[for="${element.id}"]`).length > 0) { | ||
expect(document.querySelector(`[for="${element.id}"]`)).toHaveTextContent(value); | ||
@@ -730,3 +731,3 @@ return; | ||
return expect(element).not.toBe(null); | ||
let id = element.getAttribute('aria-describedby'); | ||
let id = element.getAttribute("aria-describedby"); | ||
expect(document.getElementById(id)?.textContent).toEqual(value); | ||
@@ -771,4 +772,4 @@ } | ||
assertHidden(dialog); | ||
expect(dialog).toHaveAttribute('role', 'dialog'); | ||
expect(dialog).not.toHaveAttribute('aria-modal', 'true'); | ||
expect(dialog).toHaveAttribute("role", "dialog"); | ||
expect(dialog).not.toHaveAttribute("aria-modal", "true"); | ||
if (options.textContent) | ||
@@ -784,4 +785,4 @@ expect(dialog).toHaveTextContent(options.textContent); | ||
assertVisible(dialog); | ||
expect(dialog).toHaveAttribute('role', 'dialog'); | ||
expect(dialog).toHaveAttribute('aria-modal', 'true'); | ||
expect(dialog).toHaveAttribute("role", "dialog"); | ||
expect(dialog).toHaveAttribute("aria-modal", "true"); | ||
if (options.textContent) | ||
@@ -814,4 +815,4 @@ expect(dialog).toHaveTextContent(options.textContent); | ||
assertHidden(title); | ||
expect(title).toHaveAttribute('id'); | ||
expect(dialog).toHaveAttribute('aria-labelledby', title.id); | ||
expect(title).toHaveAttribute("id"); | ||
expect(dialog).toHaveAttribute("aria-labelledby", title.id); | ||
if (options.textContent) | ||
@@ -829,4 +830,4 @@ expect(title).toHaveTextContent(options.textContent); | ||
assertVisible(title); | ||
expect(title).toHaveAttribute('id'); | ||
expect(dialog).toHaveAttribute('aria-labelledby', title.id); | ||
expect(title).toHaveAttribute("id"); | ||
expect(dialog).toHaveAttribute("aria-labelledby", title.id); | ||
if (options.textContent) | ||
@@ -859,4 +860,4 @@ expect(title).toHaveTextContent(options.textContent); | ||
assertHidden(description); | ||
expect(description).toHaveAttribute('id'); | ||
expect(dialog).toHaveAttribute('aria-describedby', description.id); | ||
expect(description).toHaveAttribute("id"); | ||
expect(dialog).toHaveAttribute("aria-describedby", description.id); | ||
if (options.textContent) | ||
@@ -874,4 +875,4 @@ expect(description).toHaveTextContent(options.textContent); | ||
assertVisible(description); | ||
expect(description).toHaveAttribute('id'); | ||
expect(dialog).toHaveAttribute('aria-describedby', description.id); | ||
expect(description).toHaveAttribute("id"); | ||
expect(dialog).toHaveAttribute("aria-describedby", description.id); | ||
if (options.textContent) | ||
@@ -947,4 +948,4 @@ expect(description).toHaveTextContent(options.textContent); | ||
return expect(radioGroup).not.toBe(null); | ||
expect(label).toHaveAttribute('id'); | ||
expect(radioGroup).toHaveAttribute('aria-labelledby', label.id); | ||
expect(label).toHaveAttribute("id"); | ||
expect(radioGroup).toHaveAttribute("aria-labelledby", label.id); | ||
if (options.textContent) | ||
@@ -972,42 +973,42 @@ expect(label).toHaveTextContent(options.textContent); | ||
// --- | ||
export function assertTabs({ active, orientation = 'horizontal', }, list = getTabList(), tabs = getTabs(), panels = getPanels()) { | ||
export function assertTabs({ active, orientation = "horizontal", }, list = getTabList(), tabs = getTabs(), panels = getPanels()) { | ||
try { | ||
if (list === null) | ||
return expect(list).not.toBe(null); | ||
expect(list).toHaveAttribute('role', 'tablist'); | ||
expect(list).toHaveAttribute('aria-orientation', orientation); | ||
let activeTab = tabs.find(tab => tab.dataset.headlessuiIndex === '' + active); | ||
let activePanel = panels.find(panel => panel.dataset.headlessuiIndex === '' + active); | ||
expect(list).toHaveAttribute("role", "tablist"); | ||
expect(list).toHaveAttribute("aria-orientation", orientation); | ||
let activeTab = tabs.find((tab) => tab.dataset.headlessuiIndex === "" + active); | ||
let activePanel = panels.find((panel) => panel.dataset.headlessuiIndex === "" + active); | ||
for (let tab of tabs) { | ||
expect(tab).toHaveAttribute('id'); | ||
expect(tab).toHaveAttribute('role', 'tab'); | ||
expect(tab).toHaveAttribute('type', 'button'); | ||
expect(tab).toHaveAttribute("id"); | ||
expect(tab).toHaveAttribute("role", "tab"); | ||
expect(tab).toHaveAttribute("type", "button"); | ||
if (tab === activeTab) { | ||
expect(tab).toHaveAttribute('aria-selected', 'true'); | ||
expect(tab).toHaveAttribute('tabindex', '0'); | ||
expect(tab).toHaveAttribute("aria-selected", "true"); | ||
expect(tab).toHaveAttribute("tabindex", "0"); | ||
} | ||
else { | ||
expect(tab).toHaveAttribute('aria-selected', 'false'); | ||
expect(tab).toHaveAttribute('tabindex', '-1'); | ||
expect(tab).toHaveAttribute("aria-selected", "false"); | ||
expect(tab).toHaveAttribute("tabindex", "-1"); | ||
} | ||
if (tab.hasAttribute('aria-controls')) { | ||
let controlsId = tab.getAttribute('aria-controls'); | ||
if (tab.hasAttribute("aria-controls")) { | ||
let controlsId = tab.getAttribute("aria-controls"); | ||
let panel = document.getElementById(controlsId); | ||
expect(panel).not.toBe(null); | ||
expect(panels).toContain(panel); | ||
expect(panel).toHaveAttribute('aria-labelledby', tab.id); | ||
expect(panel).toHaveAttribute("aria-labelledby", tab.id); | ||
} | ||
} | ||
for (let panel of panels) { | ||
expect(panel).toHaveAttribute('id'); | ||
expect(panel).toHaveAttribute('role', 'tabpanel'); | ||
let controlledById = panel.getAttribute('aria-labelledby'); | ||
expect(panel).toHaveAttribute("id"); | ||
expect(panel).toHaveAttribute("role", "tabpanel"); | ||
let controlledById = panel.getAttribute("aria-labelledby"); | ||
let tab = document.getElementById(controlledById); | ||
expect(tabs).toContain(tab); | ||
expect(tab).toHaveAttribute('aria-controls', panel.id); | ||
expect(tab).toHaveAttribute("aria-controls", panel.id); | ||
if (panel === activePanel) { | ||
expect(panel).toHaveAttribute('tabindex', '0'); | ||
expect(panel).toHaveAttribute("tabindex", "0"); | ||
} | ||
else { | ||
expect(panel).toHaveAttribute('tabindex', '-1'); | ||
expect(panel).toHaveAttribute("tabindex", "-1"); | ||
} | ||
@@ -1058,4 +1059,4 @@ } | ||
return expect(element).not.toBe(null); | ||
expect(element).toHaveAttribute('hidden'); | ||
expect(element).toHaveStyle({ display: 'none' }); | ||
expect(element).toHaveAttribute("hidden"); | ||
expect(element).toHaveStyle({ display: "none" }); | ||
} | ||
@@ -1071,4 +1072,4 @@ catch (err) { | ||
return expect(element).not.toBe(null); | ||
expect(element).not.toHaveAttribute('hidden'); | ||
expect(element).not.toHaveStyle({ display: 'none' }); | ||
expect(element).not.toHaveAttribute("hidden"); | ||
expect(element).not.toHaveStyle({ display: "none" }); | ||
} | ||
@@ -1075,0 +1076,0 @@ catch (err) { |
import { tick } from "svelte"; | ||
import { fireEvent } from '@testing-library/svelte'; | ||
import { fireEvent } from "@testing-library/svelte"; | ||
export let Keys = { | ||
Space: { key: ' ', keyCode: 32, charCode: 32 }, | ||
Enter: { key: 'Enter', keyCode: 13, charCode: 13 }, | ||
Escape: { key: 'Escape', keyCode: 27, charCode: 27 }, | ||
Backspace: { key: 'Backspace', keyCode: 8 }, | ||
ArrowLeft: { key: 'ArrowLeft', keyCode: 37 }, | ||
ArrowUp: { key: 'ArrowUp', keyCode: 38 }, | ||
ArrowRight: { key: 'ArrowRight', keyCode: 39 }, | ||
ArrowDown: { key: 'ArrowDown', keyCode: 40 }, | ||
Home: { key: 'Home', keyCode: 36 }, | ||
End: { key: 'End', keyCode: 35 }, | ||
PageUp: { key: 'PageUp', keyCode: 33 }, | ||
PageDown: { key: 'PageDown', keyCode: 34 }, | ||
Tab: { key: 'Tab', keyCode: 9, charCode: 9 }, | ||
Space: { key: " ", keyCode: 32, charCode: 32 }, | ||
Enter: { key: "Enter", keyCode: 13, charCode: 13 }, | ||
Escape: { key: "Escape", keyCode: 27, charCode: 27 }, | ||
Backspace: { key: "Backspace", keyCode: 8 }, | ||
ArrowLeft: { key: "ArrowLeft", keyCode: 37 }, | ||
ArrowUp: { key: "ArrowUp", keyCode: 38 }, | ||
ArrowRight: { key: "ArrowRight", keyCode: 39 }, | ||
ArrowDown: { key: "ArrowDown", keyCode: 40 }, | ||
Home: { key: "Home", keyCode: 36 }, | ||
End: { key: "End", keyCode: 35 }, | ||
PageUp: { key: "PageUp", keyCode: 33 }, | ||
PageDown: { key: "PageDown", keyCode: 34 }, | ||
Tab: { key: "Tab", keyCode: 9, charCode: 9 }, | ||
}; | ||
@@ -22,3 +22,3 @@ export function shift(event) { | ||
export function word(input) { | ||
return input.split('').map(key => ({ key })); | ||
return input.split("").map((key) => ({ key })); | ||
} | ||
@@ -29,3 +29,3 @@ let Default = Symbol(); | ||
[Default]: { | ||
keydown: new Set(['keypress']), | ||
keydown: new Set(["keypress"]), | ||
keypress: new Set([]), | ||
@@ -35,13 +35,13 @@ keyup: new Set([]), | ||
[Keys.Enter.key]: { | ||
keydown: new Set(['keypress', 'click']), | ||
keypress: new Set(['click']), | ||
keydown: new Set(["keypress", "click"]), | ||
keypress: new Set(["click"]), | ||
keyup: new Set([]), | ||
}, | ||
[Keys.Space.key]: { | ||
keydown: new Set(['keypress', 'click']), | ||
keydown: new Set(["keypress", "click"]), | ||
keypress: new Set([]), | ||
keyup: new Set(['click']), | ||
keyup: new Set(["click"]), | ||
}, | ||
[Keys.Tab.key]: { | ||
keydown: new Set(['keypress', 'blur', 'focus']), | ||
keydown: new Set(["keypress", "blur", "focus"]), | ||
keypress: new Set([]), | ||
@@ -116,4 +116,4 @@ keyup: new Set([]), | ||
for (let action of actions) { | ||
let checks = action.name.split('And'); | ||
if (checks.some(check => skip.has(check))) | ||
let checks = action.name.split("And"); | ||
if (checks.some((check) => skip.has(check))) | ||
continue; | ||
@@ -276,20 +276,19 @@ let result = await action(element, { | ||
let focusableSelector = [ | ||
'[contentEditable=true]', | ||
'[tabindex]', | ||
'a[href]', | ||
'area[href]', | ||
'button:not([disabled])', | ||
'iframe', | ||
'input:not([disabled])', | ||
'select:not([disabled])', | ||
'textarea:not([disabled])', | ||
"[contentEditable=true]", | ||
"[tabindex]", | ||
"a[href]", | ||
"area[href]", | ||
"button:not([disabled])", | ||
"iframe", | ||
"input:not([disabled])", | ||
"select:not([disabled])", | ||
"textarea:not([disabled])", | ||
] | ||
.map(process.env.NODE_ENV === 'test' | ||
.map(process.env.NODE_ENV === "test" | ||
? // TODO: Remove this once JSDOM fixes the issue where an element that is | ||
// "hidden" can be the document.activeElement, because this is not possible | ||
// in real browsers. | ||
// TODO: Remove this once JSDOM fixes the issue where an element that is | ||
selector => `${selector}:not([tabindex='-1']):not([style*='display: none'])` | ||
: selector => `${selector}:not([tabindex='-1'])`) | ||
.join(','); | ||
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])` | ||
: (selector) => `${selector}:not([tabindex='-1'])`) | ||
.join(","); | ||
function getFocusableElements(container = document.body) { | ||
@@ -296,0 +295,0 @@ if (!container) |
@@ -1,2 +0,2 @@ | ||
export function suppressConsoleLogs(cb, type = 'error') { | ||
export function suppressConsoleLogs(cb, type = "error") { | ||
return (...args) => { | ||
@@ -3,0 +3,0 @@ let spy = jest.spyOn(global.console, type).mockImplementation(jest.fn()); |
@@ -8,2 +8,4 @@ import { SvelteComponentTyped } from "svelte"; | ||
onKeydown?: HandlerType; | ||
onSubmit?: HandlerType; | ||
onClick?: HandlerType; | ||
} | ||
@@ -10,0 +12,0 @@ declare type SingleComponent = string | [SvelteComponent, ComponentProps, TestRendererProps]; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
245
91
316164
4936