@pzerelles/headlessui-svelte 2.0.0-next.1 → 2.1.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/Button.svelte +65 -0
- package/dist/button/Button.svelte.d.ts +39 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/checkbox/Checkbox.svelte +60 -46
- package/dist/checkbox/Checkbox.svelte.d.ts +1 -1
- package/dist/close-button/CloseButton.svelte +10 -0
- package/dist/close-button/CloseButton.svelte.d.ts +25 -0
- package/dist/close-button/index.d.ts +1 -0
- package/dist/close-button/index.js +1 -0
- package/dist/combobox/Combobox.svelte +6 -0
- package/dist/combobox/Combobox.svelte.d.ts +50 -0
- package/dist/description/Description.svelte +50 -32
- package/dist/description/Description.svelte.d.ts +14 -5
- package/dist/field/Field.svelte +9 -9
- package/dist/fieldset/Fieldset.svelte +9 -9
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.d.ts +2 -0
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.js +18 -0
- package/dist/hooks/document-overflow/handle-ios-locking.d.ts +6 -0
- package/dist/hooks/document-overflow/handle-ios-locking.js +134 -0
- package/dist/hooks/document-overflow/overflow-store.d.ts +19 -0
- package/dist/hooks/document-overflow/overflow-store.js +76 -0
- package/dist/hooks/document-overflow/prevent-scroll.d.ts +2 -0
- package/dist/hooks/document-overflow/prevent-scroll.js +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.d.ts +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.js +27 -0
- package/dist/hooks/use-active-press.svelte.d.ts +14 -0
- package/dist/{actions/activePress.svelte.js → hooks/use-active-press.svelte.js} +33 -39
- package/dist/hooks/use-by-comparator.d.ts +2 -0
- package/dist/hooks/use-by-comparator.js +15 -0
- package/dist/hooks/use-controllable.svelte.d.ts +6 -0
- package/dist/hooks/use-controllable.svelte.js +34 -0
- package/dist/hooks/use-did-element-move.svelte.d.ts +6 -0
- package/dist/hooks/use-did-element-move.svelte.js +27 -0
- package/dist/hooks/use-disabled.d.ts +3 -0
- package/dist/hooks/use-disabled.js +9 -0
- package/dist/hooks/use-element-size.svelte.d.ts +7 -0
- package/dist/hooks/use-element-size.svelte.js +36 -0
- package/dist/hooks/use-flags.svelte.d.ts +8 -0
- package/dist/hooks/use-flags.svelte.js +18 -0
- package/dist/hooks/use-focus-ring.svelte.d.ts +10 -0
- package/dist/hooks/use-focus-ring.svelte.js +24 -0
- package/dist/hooks/use-hover.svelte.d.ts +26 -0
- package/dist/hooks/use-hover.svelte.js +124 -0
- package/dist/hooks/use-id.d.ts +1 -0
- package/dist/hooks/use-id.js +1 -0
- package/dist/hooks/use-inert-others.svelte.d.ts +32 -0
- package/dist/hooks/use-inert-others.svelte.js +114 -0
- package/dist/hooks/use-is-top-layer.svelte.d.ts +29 -0
- package/dist/hooks/use-is-top-layer.svelte.js +82 -0
- package/dist/hooks/use-on-disappear.svelte.d.ts +12 -0
- package/dist/hooks/use-on-disappear.svelte.js +38 -0
- package/dist/hooks/use-outside-click.svelte.d.ts +10 -0
- package/dist/hooks/use-outside-click.svelte.js +150 -0
- package/dist/hooks/use-reducer.d.ts +4 -0
- package/dist/hooks/use-reducer.js +11 -0
- package/dist/hooks/use-resolve-button-type.svelte.d.ts +10 -0
- package/dist/hooks/use-resolve-button-type.svelte.js +19 -0
- package/dist/hooks/use-scroll-lock.svelte.d.ts +5 -0
- package/dist/hooks/use-scroll-lock.svelte.js +24 -0
- package/dist/hooks/use-sync-refs.d.ts +7 -0
- package/dist/hooks/use-sync-refs.js +22 -0
- package/dist/hooks/use-text-value.svelte.d.ts +3 -0
- package/dist/hooks/use-text-value.svelte.js +20 -0
- package/dist/hooks/use-tracked-pointer.d.ts +4 -0
- package/dist/hooks/use-tracked-pointer.js +26 -0
- package/dist/hooks/use-transition.svelte.d.ts +20 -0
- package/dist/hooks/use-transition.svelte.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/FocusSentinel.svelte +45 -0
- package/dist/internal/FocusSentinel.svelte.d.ts +17 -0
- package/dist/internal/FormFields.svelte +2 -4
- package/dist/internal/FormFields.svelte.d.ts +5 -6
- package/dist/internal/FormResolver.svelte +11 -16
- package/dist/internal/FormResolver.svelte.d.ts +2 -3
- package/dist/internal/Hidden.svelte +8 -8
- package/dist/internal/Hidden.svelte.d.ts +28 -19
- package/dist/internal/HoistFormFields.svelte.d.ts +1 -1
- package/dist/internal/Portal.svelte.d.ts +1 -1
- package/dist/internal/floating.svelte.d.ts +57 -0
- package/dist/internal/floating.svelte.js +477 -0
- package/dist/internal/frozen.svelte.d.ts +6 -0
- package/dist/internal/frozen.svelte.js +18 -0
- package/dist/internal/id.d.ts +8 -0
- package/dist/internal/id.js +11 -0
- package/dist/internal/open-closed.d.ts +14 -0
- package/dist/internal/open-closed.js +17 -0
- package/dist/internal/portal-force-root.svelte.d.ts +6 -0
- package/dist/internal/portal-force-root.svelte.js +11 -0
- package/dist/label/Label.svelte +53 -32
- package/dist/label/Label.svelte.d.ts +14 -5
- package/dist/legend/Legend.svelte.d.ts +1 -2
- package/dist/listbox/Listbox.svelte +451 -0
- package/dist/listbox/Listbox.svelte.d.ts +107 -0
- package/dist/listbox/ListboxButton.svelte +141 -0
- package/dist/listbox/ListboxButton.svelte.d.ts +41 -0
- package/dist/listbox/ListboxOption.svelte +138 -0
- package/dist/listbox/ListboxOption.svelte.d.ts +39 -0
- package/dist/listbox/ListboxOptions.svelte +267 -0
- package/dist/listbox/ListboxOptions.svelte.d.ts +39 -0
- package/dist/listbox/ListboxSelectedOption.svelte +25 -0
- package/dist/listbox/ListboxSelectedOption.svelte.d.ts +30 -0
- package/dist/listbox/index.d.ts +5 -0
- package/dist/listbox/index.js +5 -0
- package/dist/portal/InternalPortal.svelte +108 -0
- package/dist/portal/InternalPortal.svelte.d.ts +34 -0
- package/dist/portal/Portal.svelte +11 -0
- package/dist/portal/Portal.svelte.d.ts +23 -0
- package/dist/portal/PortalGroup.svelte +15 -0
- package/dist/portal/PortalGroup.svelte.d.ts +31 -0
- package/dist/switch/Switch.svelte +149 -0
- package/dist/switch/Switch.svelte.d.ts +44 -0
- package/dist/switch/SwitchGroup.svelte +38 -0
- package/dist/switch/SwitchGroup.svelte.d.ts +27 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +2 -0
- package/dist/tabs/Button.svelte +65 -0
- package/dist/tabs/Button.svelte.d.ts +39 -0
- package/dist/tabs/Tab.svelte +161 -0
- package/dist/tabs/Tab.svelte.d.ts +36 -0
- package/dist/tabs/TabGroup.svelte +244 -0
- package/dist/tabs/TabGroup.svelte.d.ts +54 -0
- package/dist/tabs/TabList.svelte +18 -0
- package/dist/tabs/TabList.svelte.d.ts +28 -0
- package/dist/tabs/TabPanel.svelte +63 -0
- package/dist/tabs/TabPanel.svelte.d.ts +34 -0
- package/dist/tabs/TabPanels.svelte +13 -0
- package/dist/tabs/TabPanels.svelte.d.ts +27 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +5 -0
- package/dist/test-utils/accessability-assertions.d.ts +271 -0
- package/dist/test-utils/accessability-assertions.js +1572 -0
- package/dist/test-utils/fake-pointer.d.ts +24 -0
- package/dist/test-utils/fake-pointer.js +48 -0
- package/dist/test-utils/interactions.d.ts +61 -0
- package/dist/test-utils/interactions.js +453 -0
- package/dist/test-utils/suppress-console-logs.d.ts +7 -0
- package/dist/test-utils/suppress-console-logs.js +17 -0
- package/dist/utils/StableCollection.svelte +43 -0
- package/dist/utils/StableCollection.svelte.d.ts +19 -0
- package/dist/utils/calculate-active-index.d.ts +25 -0
- package/dist/utils/calculate-active-index.js +74 -0
- package/dist/utils/close.d.ts +2 -0
- package/dist/utils/close.js +3 -0
- package/dist/utils/default-map.d.ts +5 -0
- package/dist/utils/default-map.js +15 -0
- package/dist/utils/disposables.d.ts +14 -12
- package/dist/utils/disposables.js +13 -10
- package/dist/utils/dom.d.ts +0 -2
- package/dist/utils/dom.js +2 -4
- package/dist/utils/env.d.ts +17 -0
- package/dist/utils/env.js +39 -0
- package/dist/utils/focus-management.d.ts +44 -0
- package/dist/utils/focus-management.js +242 -0
- package/dist/utils/focusVisible.svelte.d.ts +3 -3
- package/dist/utils/focusVisible.svelte.js +52 -41
- package/dist/utils/get-text-value.d.ts +1 -0
- package/dist/utils/get-text-value.js +71 -0
- package/dist/utils/id.d.ts +1 -1
- package/dist/utils/match.d.ts +1 -0
- package/dist/utils/match.js +13 -0
- package/dist/utils/once.d.ts +1 -0
- package/dist/utils/once.js +9 -0
- package/dist/utils/owner.d.ts +1 -0
- package/dist/utils/owner.js +8 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +17 -0
- package/dist/utils/ref.svelte.d.ts +4 -0
- package/dist/utils/ref.svelte.js +4 -0
- package/dist/utils/render.d.ts +31 -0
- package/dist/utils/render.js +56 -0
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.js +20 -0
- package/dist/utils/types.d.ts +27 -0
- package/dist/utils/types.js +6 -0
- package/package.json +28 -21
- package/dist/actions/activePress.svelte.d.ts +0 -8
- package/dist/actions/focusRing.svelte.d.ts +0 -9
- package/dist/actions/focusRing.svelte.js +0 -34
- package/dist/utils/disabled.d.ts +0 -3
- package/dist/utils/disabled.js +0 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare enum Focus {
|
|
2
|
+
/** Focus the first non-disabled item. */
|
|
3
|
+
First = 0,
|
|
4
|
+
/** Focus the previous non-disabled item. */
|
|
5
|
+
Previous = 1,
|
|
6
|
+
/** Focus the next non-disabled item. */
|
|
7
|
+
Next = 2,
|
|
8
|
+
/** Focus the last non-disabled item. */
|
|
9
|
+
Last = 3,
|
|
10
|
+
/** Focus a specific item based on the `id` of the item. */
|
|
11
|
+
Specific = 4,
|
|
12
|
+
/** Focus no items at all. */
|
|
13
|
+
Nothing = 5
|
|
14
|
+
}
|
|
15
|
+
export declare function calculateActiveIndex<TItem>(action: {
|
|
16
|
+
focus: Focus.Specific;
|
|
17
|
+
id: string;
|
|
18
|
+
} | {
|
|
19
|
+
focus: Exclude<Focus, Focus.Specific>;
|
|
20
|
+
}, resolvers: {
|
|
21
|
+
resolveItems(): TItem[];
|
|
22
|
+
resolveActiveIndex(): number | null;
|
|
23
|
+
resolveId(item: TItem, index: number, items: TItem[]): string;
|
|
24
|
+
resolveDisabled(item: TItem, index: number, items: TItem[]): boolean;
|
|
25
|
+
}): number | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function assertNever(x) {
|
|
2
|
+
throw new Error("Unexpected object: " + x);
|
|
3
|
+
}
|
|
4
|
+
export var Focus;
|
|
5
|
+
(function (Focus) {
|
|
6
|
+
/** Focus the first non-disabled item. */
|
|
7
|
+
Focus[Focus["First"] = 0] = "First";
|
|
8
|
+
/** Focus the previous non-disabled item. */
|
|
9
|
+
Focus[Focus["Previous"] = 1] = "Previous";
|
|
10
|
+
/** Focus the next non-disabled item. */
|
|
11
|
+
Focus[Focus["Next"] = 2] = "Next";
|
|
12
|
+
/** Focus the last non-disabled item. */
|
|
13
|
+
Focus[Focus["Last"] = 3] = "Last";
|
|
14
|
+
/** Focus a specific item based on the `id` of the item. */
|
|
15
|
+
Focus[Focus["Specific"] = 4] = "Specific";
|
|
16
|
+
/** Focus no items at all. */
|
|
17
|
+
Focus[Focus["Nothing"] = 5] = "Nothing";
|
|
18
|
+
})(Focus || (Focus = {}));
|
|
19
|
+
export function calculateActiveIndex(action, resolvers) {
|
|
20
|
+
const items = resolvers.resolveItems();
|
|
21
|
+
if (items.length <= 0)
|
|
22
|
+
return null;
|
|
23
|
+
const currentActiveIndex = resolvers.resolveActiveIndex();
|
|
24
|
+
let activeIndex = currentActiveIndex ?? -1;
|
|
25
|
+
switch (action.focus) {
|
|
26
|
+
case Focus.First: {
|
|
27
|
+
for (let i = 0; i < items.length; ++i) {
|
|
28
|
+
if (!resolvers.resolveDisabled(items[i], i, items)) {
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return currentActiveIndex;
|
|
33
|
+
}
|
|
34
|
+
case Focus.Previous: {
|
|
35
|
+
// If nothing is active, focus the last relevant item
|
|
36
|
+
if (activeIndex === -1)
|
|
37
|
+
activeIndex = items.length;
|
|
38
|
+
for (let i = activeIndex - 1; i >= 0; --i) {
|
|
39
|
+
if (!resolvers.resolveDisabled(items[i], i, items)) {
|
|
40
|
+
return i;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return currentActiveIndex;
|
|
44
|
+
}
|
|
45
|
+
case Focus.Next: {
|
|
46
|
+
for (let i = activeIndex + 1; i < items.length; ++i) {
|
|
47
|
+
if (!resolvers.resolveDisabled(items[i], i, items)) {
|
|
48
|
+
return i;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return currentActiveIndex;
|
|
52
|
+
}
|
|
53
|
+
case Focus.Last: {
|
|
54
|
+
for (let i = items.length - 1; i >= 0; --i) {
|
|
55
|
+
if (!resolvers.resolveDisabled(items[i], i, items)) {
|
|
56
|
+
return i;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return currentActiveIndex;
|
|
60
|
+
}
|
|
61
|
+
case Focus.Specific: {
|
|
62
|
+
for (let i = 0; i < items.length; ++i) {
|
|
63
|
+
if (resolvers.resolveId(items[i], i, items) === action.id) {
|
|
64
|
+
return i;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return currentActiveIndex;
|
|
68
|
+
}
|
|
69
|
+
case Focus.Nothing:
|
|
70
|
+
return null;
|
|
71
|
+
default:
|
|
72
|
+
assertNever(action);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class DefaultMap extends Map {
|
|
2
|
+
factory;
|
|
3
|
+
constructor(factory) {
|
|
4
|
+
super();
|
|
5
|
+
this.factory = factory;
|
|
6
|
+
}
|
|
7
|
+
get(key) {
|
|
8
|
+
let value = super.get(key);
|
|
9
|
+
if (value === undefined) {
|
|
10
|
+
value = this.factory(key);
|
|
11
|
+
this.set(key, value);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { microTask } from "./microTask.js";
|
|
2
|
+
export type Disposables = {
|
|
3
|
+
addEventListener: <TEventName extends keyof WindowEventMap>(element: HTMLElement | Window | Document, name: TEventName, listener: (event: WindowEventMap[TEventName]) => any, options?: boolean | AddEventListenerOptions) => () => void;
|
|
4
|
+
requestAnimationFrame: (...args: Parameters<typeof requestAnimationFrame>) => () => void;
|
|
5
|
+
nextFrame: (...args: Parameters<typeof requestAnimationFrame>) => () => void;
|
|
6
|
+
setTimeout: (...args: Parameters<typeof setTimeout>) => () => void;
|
|
7
|
+
microTask: (...args: Parameters<typeof microTask>) => () => void;
|
|
8
|
+
style: (node: HTMLElement, property: string, value: string) => () => void;
|
|
9
|
+
group: (cb: (d: Disposables) => void) => () => void;
|
|
10
|
+
add: (cb: () => void) => () => void;
|
|
11
|
+
dispose: () => void;
|
|
12
|
+
};
|
|
2
13
|
/**
|
|
3
14
|
* Disposables are a way to manage event handlers and functions like
|
|
4
15
|
* `setTimeout` and `requestAnimationFrame` that need to be cleaned up when they
|
|
@@ -11,14 +22,5 @@ export type Disposables = ReturnType<typeof disposables>;
|
|
|
11
22
|
* `dispose` function on the collection itself that can be used to clean up all
|
|
12
23
|
* pending disposables in that collection.
|
|
13
24
|
*/
|
|
14
|
-
export declare function disposables():
|
|
15
|
-
|
|
16
|
-
requestAnimationFrame(callback: FrameRequestCallback): () => void;
|
|
17
|
-
nextFrame(callback: FrameRequestCallback): () => void;
|
|
18
|
-
setTimeout(callback: (args: void) => void, ms?: number | undefined): () => void;
|
|
19
|
-
microTask(cb: () => void): () => void;
|
|
20
|
-
style(node: HTMLElement, property: string, value: string): () => void;
|
|
21
|
-
group(cb: (d: typeof this) => void): () => void;
|
|
22
|
-
add(cb: () => void): () => void;
|
|
23
|
-
dispose(): void;
|
|
24
|
-
};
|
|
25
|
+
export declare function disposables(): Disposables;
|
|
26
|
+
export { disposables as useDisposables };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import { microTask } from "./microTask.js";
|
|
2
3
|
/**
|
|
3
4
|
* Disposables are a way to manage event handlers and functions like
|
|
@@ -12,14 +13,15 @@ import { microTask } from "./microTask.js";
|
|
|
12
13
|
* pending disposables in that collection.
|
|
13
14
|
*/
|
|
14
15
|
export function disposables() {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
17
|
+
const _disposables = [];
|
|
18
|
+
const api = {
|
|
17
19
|
addEventListener(element, name, listener, options) {
|
|
18
20
|
element.addEventListener(name, listener, options);
|
|
19
21
|
return api.add(() => element.removeEventListener(name, listener, options));
|
|
20
22
|
},
|
|
21
23
|
requestAnimationFrame(...args) {
|
|
22
|
-
|
|
24
|
+
const raf = requestAnimationFrame(...args);
|
|
23
25
|
return api.add(() => cancelAnimationFrame(raf));
|
|
24
26
|
},
|
|
25
27
|
nextFrame(...args) {
|
|
@@ -28,11 +30,11 @@ export function disposables() {
|
|
|
28
30
|
});
|
|
29
31
|
},
|
|
30
32
|
setTimeout(...args) {
|
|
31
|
-
|
|
33
|
+
const timer = setTimeout(...args);
|
|
32
34
|
return api.add(() => clearTimeout(timer));
|
|
33
35
|
},
|
|
34
36
|
microTask(...args) {
|
|
35
|
-
|
|
37
|
+
const task = { current: true };
|
|
36
38
|
microTask(() => {
|
|
37
39
|
if (task.current) {
|
|
38
40
|
args[0]();
|
|
@@ -43,14 +45,14 @@ export function disposables() {
|
|
|
43
45
|
});
|
|
44
46
|
},
|
|
45
47
|
style(node, property, value) {
|
|
46
|
-
|
|
48
|
+
const previous = node.style.getPropertyValue(property);
|
|
47
49
|
Object.assign(node.style, { [property]: value });
|
|
48
50
|
return this.add(() => {
|
|
49
51
|
Object.assign(node.style, { [property]: previous });
|
|
50
52
|
});
|
|
51
53
|
},
|
|
52
54
|
group(cb) {
|
|
53
|
-
|
|
55
|
+
const d = disposables();
|
|
54
56
|
cb(d);
|
|
55
57
|
return this.add(() => d.dispose());
|
|
56
58
|
},
|
|
@@ -60,19 +62,20 @@ export function disposables() {
|
|
|
60
62
|
_disposables.push(cb);
|
|
61
63
|
}
|
|
62
64
|
return () => {
|
|
63
|
-
|
|
65
|
+
const idx = _disposables.indexOf(cb);
|
|
64
66
|
if (idx >= 0) {
|
|
65
|
-
for (
|
|
67
|
+
for (const dispose of _disposables.splice(idx, 1)) {
|
|
66
68
|
dispose();
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
72
|
},
|
|
71
73
|
dispose() {
|
|
72
|
-
for (
|
|
74
|
+
for (const dispose of _disposables.splice(0)) {
|
|
73
75
|
dispose();
|
|
74
76
|
}
|
|
75
77
|
},
|
|
76
78
|
};
|
|
77
79
|
return api;
|
|
78
80
|
}
|
|
81
|
+
export { disposables as useDisposables };
|
package/dist/utils/dom.d.ts
CHANGED
package/dist/utils/dom.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
return el?.ownerDocument ?? document;
|
|
3
|
-
};
|
|
1
|
+
import { getOwnerDocument } from "./owner.js";
|
|
4
2
|
export const getOwnerWindow = (el) => {
|
|
5
3
|
if (el && "window" in el && el.window === el) {
|
|
6
4
|
return el;
|
|
7
5
|
}
|
|
8
6
|
const doc = getOwnerDocument(el);
|
|
9
|
-
return doc
|
|
7
|
+
return doc?.defaultView || window;
|
|
10
8
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type RenderEnv = "client" | "server";
|
|
2
|
+
type HandoffState = "pending" | "complete";
|
|
3
|
+
declare class Env {
|
|
4
|
+
current: RenderEnv;
|
|
5
|
+
handoffState: HandoffState;
|
|
6
|
+
currentId: number;
|
|
7
|
+
set(env: RenderEnv): void;
|
|
8
|
+
reset(): void;
|
|
9
|
+
nextId(): number;
|
|
10
|
+
get isServer(): boolean;
|
|
11
|
+
get isClient(): boolean;
|
|
12
|
+
private detect;
|
|
13
|
+
handoff(): void;
|
|
14
|
+
get isHandoffComplete(): boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare let env: Env;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class Env {
|
|
2
|
+
current = this.detect();
|
|
3
|
+
handoffState = "pending";
|
|
4
|
+
currentId = 0;
|
|
5
|
+
set(env) {
|
|
6
|
+
if (this.current === env)
|
|
7
|
+
return;
|
|
8
|
+
this.handoffState = "pending";
|
|
9
|
+
this.currentId = 0;
|
|
10
|
+
this.current = env;
|
|
11
|
+
}
|
|
12
|
+
reset() {
|
|
13
|
+
this.set(this.detect());
|
|
14
|
+
}
|
|
15
|
+
nextId() {
|
|
16
|
+
return ++this.currentId;
|
|
17
|
+
}
|
|
18
|
+
get isServer() {
|
|
19
|
+
return this.current === "server";
|
|
20
|
+
}
|
|
21
|
+
get isClient() {
|
|
22
|
+
return this.current === "client";
|
|
23
|
+
}
|
|
24
|
+
detect() {
|
|
25
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
26
|
+
return "server";
|
|
27
|
+
}
|
|
28
|
+
return "client";
|
|
29
|
+
}
|
|
30
|
+
handoff() {
|
|
31
|
+
if (this.handoffState === "pending") {
|
|
32
|
+
this.handoffState = "complete";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
get isHandoffComplete() {
|
|
36
|
+
return this.handoffState === "complete";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export let env = new Env();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare enum Focus {
|
|
2
|
+
/** Focus the first non-disabled element */
|
|
3
|
+
First = 1,
|
|
4
|
+
/** Focus the previous non-disabled element */
|
|
5
|
+
Previous = 2,
|
|
6
|
+
/** Focus the next non-disabled element */
|
|
7
|
+
Next = 4,
|
|
8
|
+
/** Focus the last non-disabled element */
|
|
9
|
+
Last = 8,
|
|
10
|
+
/** Wrap tab around */
|
|
11
|
+
WrapAround = 16,
|
|
12
|
+
/** Prevent scrolling the focusable elements into view */
|
|
13
|
+
NoScroll = 32,
|
|
14
|
+
/** Focus the first focusable element with the `data-autofocus` attribute. */
|
|
15
|
+
AutoFocus = 64
|
|
16
|
+
}
|
|
17
|
+
export declare enum FocusResult {
|
|
18
|
+
/** Something went wrong while trying to focus. */
|
|
19
|
+
Error = 0,
|
|
20
|
+
/** When `Focus.WrapAround` is enabled, going from position `N` to `N+1` where `N` is the last index in the array, then we overflow. */
|
|
21
|
+
Overflow = 1,
|
|
22
|
+
/** Focus was successful. */
|
|
23
|
+
Success = 2,
|
|
24
|
+
/** When `Focus.WrapAround` is enabled, going from position `N` to `N-1` where `N` is the first index in the array, then we underflow. */
|
|
25
|
+
Underflow = 3
|
|
26
|
+
}
|
|
27
|
+
export declare function getFocusableElements(container?: HTMLElement | null): HTMLElement[];
|
|
28
|
+
export declare function getAutoFocusableElements(container?: HTMLElement | null): HTMLElement[];
|
|
29
|
+
export declare enum FocusableMode {
|
|
30
|
+
/** The element itself must be focusable. */
|
|
31
|
+
Strict = 0,
|
|
32
|
+
/** The element should be inside of a focusable element. */
|
|
33
|
+
Loose = 1
|
|
34
|
+
}
|
|
35
|
+
export declare function isFocusableElement(element: HTMLElement, mode?: FocusableMode): boolean;
|
|
36
|
+
export declare function restoreFocusIfNecessary(element: HTMLElement | null): void;
|
|
37
|
+
export declare function focusElement(element: HTMLElement | null): void;
|
|
38
|
+
export declare function sortByDomNode<T>(nodes: T[], resolveKey?: (item: T) => HTMLElement | null | undefined): T[];
|
|
39
|
+
export declare function focusFrom(current: HTMLElement | null, focus: Focus): FocusResult;
|
|
40
|
+
export declare function focusIn(container: HTMLElement | HTMLElement[], focus: Focus, { sorted, relativeTo, skipElements, }?: Partial<{
|
|
41
|
+
sorted: boolean;
|
|
42
|
+
relativeTo: HTMLElement | null;
|
|
43
|
+
skipElements: HTMLElement[];
|
|
44
|
+
}>): FocusResult;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { disposables } from "./disposables.js";
|
|
2
|
+
import { match } from "./match.js";
|
|
3
|
+
import { getOwnerDocument } from "./owner.js";
|
|
4
|
+
// Credit:
|
|
5
|
+
// - https://stackoverflow.com/a/30753870
|
|
6
|
+
const focusableSelector = [
|
|
7
|
+
"[contentEditable=true]",
|
|
8
|
+
"[tabindex]",
|
|
9
|
+
"a[href]",
|
|
10
|
+
"area[href]",
|
|
11
|
+
"button:not([disabled])",
|
|
12
|
+
"iframe",
|
|
13
|
+
"input:not([disabled])",
|
|
14
|
+
"select:not([disabled])",
|
|
15
|
+
"textarea:not([disabled])",
|
|
16
|
+
]
|
|
17
|
+
.map(process.env.NODE_ENV === "test"
|
|
18
|
+
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
|
19
|
+
// "hidden" can be the document.activeElement, because this is not possible
|
|
20
|
+
// in real browsers.
|
|
21
|
+
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
|
22
|
+
: (selector) => `${selector}:not([tabindex='-1'])`)
|
|
23
|
+
.join(",");
|
|
24
|
+
const autoFocusableSelector = [
|
|
25
|
+
// In a perfect world this was just `autofocus`, but React doesn't pass `autofocus` to the DOM...
|
|
26
|
+
"[data-autofocus]",
|
|
27
|
+
]
|
|
28
|
+
.map(process.env.NODE_ENV === "test"
|
|
29
|
+
? // TODO: Remove this once JSDOM fixes the issue where an element that is
|
|
30
|
+
// "hidden" can be the document.activeElement, because this is not possible
|
|
31
|
+
// in real browsers.
|
|
32
|
+
(selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`
|
|
33
|
+
: (selector) => `${selector}:not([tabindex='-1'])`)
|
|
34
|
+
.join(",");
|
|
35
|
+
export var Focus;
|
|
36
|
+
(function (Focus) {
|
|
37
|
+
/** Focus the first non-disabled element */
|
|
38
|
+
Focus[Focus["First"] = 1] = "First";
|
|
39
|
+
/** Focus the previous non-disabled element */
|
|
40
|
+
Focus[Focus["Previous"] = 2] = "Previous";
|
|
41
|
+
/** Focus the next non-disabled element */
|
|
42
|
+
Focus[Focus["Next"] = 4] = "Next";
|
|
43
|
+
/** Focus the last non-disabled element */
|
|
44
|
+
Focus[Focus["Last"] = 8] = "Last";
|
|
45
|
+
/** Wrap tab around */
|
|
46
|
+
Focus[Focus["WrapAround"] = 16] = "WrapAround";
|
|
47
|
+
/** Prevent scrolling the focusable elements into view */
|
|
48
|
+
Focus[Focus["NoScroll"] = 32] = "NoScroll";
|
|
49
|
+
/** Focus the first focusable element with the `data-autofocus` attribute. */
|
|
50
|
+
Focus[Focus["AutoFocus"] = 64] = "AutoFocus";
|
|
51
|
+
})(Focus || (Focus = {}));
|
|
52
|
+
export var FocusResult;
|
|
53
|
+
(function (FocusResult) {
|
|
54
|
+
/** Something went wrong while trying to focus. */
|
|
55
|
+
FocusResult[FocusResult["Error"] = 0] = "Error";
|
|
56
|
+
/** When `Focus.WrapAround` is enabled, going from position `N` to `N+1` where `N` is the last index in the array, then we overflow. */
|
|
57
|
+
FocusResult[FocusResult["Overflow"] = 1] = "Overflow";
|
|
58
|
+
/** Focus was successful. */
|
|
59
|
+
FocusResult[FocusResult["Success"] = 2] = "Success";
|
|
60
|
+
/** When `Focus.WrapAround` is enabled, going from position `N` to `N-1` where `N` is the first index in the array, then we underflow. */
|
|
61
|
+
FocusResult[FocusResult["Underflow"] = 3] = "Underflow";
|
|
62
|
+
})(FocusResult || (FocusResult = {}));
|
|
63
|
+
var Direction;
|
|
64
|
+
(function (Direction) {
|
|
65
|
+
Direction[Direction["Previous"] = -1] = "Previous";
|
|
66
|
+
Direction[Direction["Next"] = 1] = "Next";
|
|
67
|
+
})(Direction || (Direction = {}));
|
|
68
|
+
export function getFocusableElements(container = document.body) {
|
|
69
|
+
if (container == null)
|
|
70
|
+
return [];
|
|
71
|
+
return Array.from(container.querySelectorAll(focusableSelector)).sort(
|
|
72
|
+
// We want to move `tabIndex={0}` to the end of the list, this is what the browser does as well.
|
|
73
|
+
(a, z) => Math.sign((a.tabIndex || Number.MAX_SAFE_INTEGER) - (z.tabIndex || Number.MAX_SAFE_INTEGER)));
|
|
74
|
+
}
|
|
75
|
+
export function getAutoFocusableElements(container = document.body) {
|
|
76
|
+
if (container == null)
|
|
77
|
+
return [];
|
|
78
|
+
return Array.from(container.querySelectorAll(autoFocusableSelector)).sort(
|
|
79
|
+
// We want to move `tabIndex={0}` to the end of the list, this is what the browser does as well.
|
|
80
|
+
(a, z) => Math.sign((a.tabIndex || Number.MAX_SAFE_INTEGER) - (z.tabIndex || Number.MAX_SAFE_INTEGER)));
|
|
81
|
+
}
|
|
82
|
+
export var FocusableMode;
|
|
83
|
+
(function (FocusableMode) {
|
|
84
|
+
/** The element itself must be focusable. */
|
|
85
|
+
FocusableMode[FocusableMode["Strict"] = 0] = "Strict";
|
|
86
|
+
/** The element should be inside of a focusable element. */
|
|
87
|
+
FocusableMode[FocusableMode["Loose"] = 1] = "Loose";
|
|
88
|
+
})(FocusableMode || (FocusableMode = {}));
|
|
89
|
+
export function isFocusableElement(element, mode = FocusableMode.Strict) {
|
|
90
|
+
if (element === getOwnerDocument(element)?.body)
|
|
91
|
+
return false;
|
|
92
|
+
return match(mode, {
|
|
93
|
+
[FocusableMode.Strict]() {
|
|
94
|
+
return element.matches(focusableSelector);
|
|
95
|
+
},
|
|
96
|
+
[FocusableMode.Loose]() {
|
|
97
|
+
let next = element;
|
|
98
|
+
while (next !== null) {
|
|
99
|
+
if (next.matches(focusableSelector))
|
|
100
|
+
return true;
|
|
101
|
+
next = next.parentElement;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export function restoreFocusIfNecessary(element) {
|
|
108
|
+
const ownerDocument = getOwnerDocument(element);
|
|
109
|
+
disposables().nextFrame(() => {
|
|
110
|
+
if (ownerDocument && !isFocusableElement(ownerDocument.activeElement, FocusableMode.Strict)) {
|
|
111
|
+
focusElement(element);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// The method of triggering an action, this is used to determine how we should
|
|
116
|
+
// restore focus after an action has been performed.
|
|
117
|
+
var ActivationMethod;
|
|
118
|
+
(function (ActivationMethod) {
|
|
119
|
+
/* If the action was triggered by a keyboard event. */
|
|
120
|
+
ActivationMethod[ActivationMethod["Keyboard"] = 0] = "Keyboard";
|
|
121
|
+
/* If the action was triggered by a mouse / pointer / ... event.*/
|
|
122
|
+
ActivationMethod[ActivationMethod["Mouse"] = 1] = "Mouse";
|
|
123
|
+
})(ActivationMethod || (ActivationMethod = {}));
|
|
124
|
+
// We want to be able to set and remove the `data-headlessui-mouse` attribute on the `html` element.
|
|
125
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
126
|
+
document.addEventListener("keydown", (event) => {
|
|
127
|
+
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
document.documentElement.dataset.headlessuiFocusVisible = "";
|
|
131
|
+
}, true);
|
|
132
|
+
document.addEventListener("click", (event) => {
|
|
133
|
+
// Event originated from an actual mouse click
|
|
134
|
+
if (event.detail === ActivationMethod.Mouse) {
|
|
135
|
+
delete document.documentElement.dataset.headlessuiFocusVisible;
|
|
136
|
+
}
|
|
137
|
+
// Event originated from a keyboard event that triggered the `click` event
|
|
138
|
+
else if (event.detail === ActivationMethod.Keyboard) {
|
|
139
|
+
document.documentElement.dataset.headlessuiFocusVisible = "";
|
|
140
|
+
}
|
|
141
|
+
}, true);
|
|
142
|
+
}
|
|
143
|
+
export function focusElement(element) {
|
|
144
|
+
element?.focus({ preventScroll: true });
|
|
145
|
+
}
|
|
146
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select
|
|
147
|
+
const selectableSelector = ["textarea", "input"].join(",");
|
|
148
|
+
function isSelectableElement(element) {
|
|
149
|
+
return element?.matches?.(selectableSelector) ?? false;
|
|
150
|
+
}
|
|
151
|
+
export function sortByDomNode(nodes, resolveKey = (i) => i) {
|
|
152
|
+
return nodes.slice().sort((aItem, zItem) => {
|
|
153
|
+
const a = resolveKey(aItem);
|
|
154
|
+
const z = resolveKey(zItem);
|
|
155
|
+
if (!a || !z)
|
|
156
|
+
return 0;
|
|
157
|
+
const position = a.compareDocumentPosition(z);
|
|
158
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING)
|
|
159
|
+
return -1;
|
|
160
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING)
|
|
161
|
+
return 1;
|
|
162
|
+
return 0;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
export function focusFrom(current, focus) {
|
|
166
|
+
return focusIn(getFocusableElements(), focus, { relativeTo: current });
|
|
167
|
+
}
|
|
168
|
+
export function focusIn(container, focus, { sorted = true, relativeTo = null, skipElements = [], } = {}) {
|
|
169
|
+
const ownerDocument = Array.isArray(container)
|
|
170
|
+
? container.length > 0
|
|
171
|
+
? container[0].ownerDocument
|
|
172
|
+
: document
|
|
173
|
+
: container.ownerDocument;
|
|
174
|
+
let elements = Array.isArray(container)
|
|
175
|
+
? sorted
|
|
176
|
+
? sortByDomNode(container)
|
|
177
|
+
: container
|
|
178
|
+
: focus & Focus.AutoFocus
|
|
179
|
+
? getAutoFocusableElements(container)
|
|
180
|
+
: getFocusableElements(container);
|
|
181
|
+
if (skipElements.length > 0 && elements.length > 1) {
|
|
182
|
+
elements = elements.filter((element) => !skipElements.some((skipElement) => skipElement != null && "current" in skipElement
|
|
183
|
+
? skipElement?.current === element // Handle MutableRefObject
|
|
184
|
+
: skipElement === element // Handle HTMLElement directly
|
|
185
|
+
));
|
|
186
|
+
}
|
|
187
|
+
relativeTo = relativeTo ?? ownerDocument.activeElement;
|
|
188
|
+
const direction = (() => {
|
|
189
|
+
if (focus & (Focus.First | Focus.Next))
|
|
190
|
+
return Direction.Next;
|
|
191
|
+
if (focus & (Focus.Previous | Focus.Last))
|
|
192
|
+
return Direction.Previous;
|
|
193
|
+
throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last");
|
|
194
|
+
})();
|
|
195
|
+
const startIndex = (() => {
|
|
196
|
+
if (focus & Focus.First)
|
|
197
|
+
return 0;
|
|
198
|
+
if (focus & Focus.Previous)
|
|
199
|
+
return Math.max(0, elements.indexOf(relativeTo)) - 1;
|
|
200
|
+
if (focus & Focus.Next)
|
|
201
|
+
return Math.max(0, elements.indexOf(relativeTo)) + 1;
|
|
202
|
+
if (focus & Focus.Last)
|
|
203
|
+
return elements.length - 1;
|
|
204
|
+
throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last");
|
|
205
|
+
})();
|
|
206
|
+
const focusOptions = focus & Focus.NoScroll ? { preventScroll: true } : {};
|
|
207
|
+
let offset = 0;
|
|
208
|
+
const total = elements.length;
|
|
209
|
+
let next = undefined;
|
|
210
|
+
do {
|
|
211
|
+
// Guard against infinite loops
|
|
212
|
+
if (offset >= total || offset + total <= 0)
|
|
213
|
+
return FocusResult.Error;
|
|
214
|
+
let nextIdx = startIndex + offset;
|
|
215
|
+
if (focus & Focus.WrapAround) {
|
|
216
|
+
nextIdx = (nextIdx + total) % total;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
if (nextIdx < 0)
|
|
220
|
+
return FocusResult.Underflow;
|
|
221
|
+
if (nextIdx >= total)
|
|
222
|
+
return FocusResult.Overflow;
|
|
223
|
+
}
|
|
224
|
+
next = elements[nextIdx];
|
|
225
|
+
// Try the focus the next element, might not work if it is "hidden" to the user.
|
|
226
|
+
next?.focus(focusOptions);
|
|
227
|
+
// Try the next one in line
|
|
228
|
+
offset += direction;
|
|
229
|
+
} while (next !== ownerDocument.activeElement);
|
|
230
|
+
// By default if you <Tab> to a text input or a textarea, the browser will
|
|
231
|
+
// select all the text once the focus is inside these DOM Nodes. However,
|
|
232
|
+
// since we are manually moving focus this behavior is not happening. This
|
|
233
|
+
// code will make sure that the text gets selected as-if you did it manually.
|
|
234
|
+
// Note: We only do this when going forward / backward. Not for the
|
|
235
|
+
// Focus.First or Focus.Last actions. This is similar to the `autoFocus`
|
|
236
|
+
// behavior on an input where the input will get focus but won't be
|
|
237
|
+
// selected.
|
|
238
|
+
if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) {
|
|
239
|
+
next.select();
|
|
240
|
+
}
|
|
241
|
+
return FocusResult.Success;
|
|
242
|
+
}
|
|
@@ -4,7 +4,7 @@ export interface FocusVisibleProps {
|
|
|
4
4
|
/** Whether the element is a text input. */
|
|
5
5
|
isTextInput?: boolean;
|
|
6
6
|
/** Whether the element will be auto focused. */
|
|
7
|
-
|
|
7
|
+
autofocus?: boolean;
|
|
8
8
|
}
|
|
9
9
|
export interface FocusVisibleResult {
|
|
10
10
|
/** Whether keyboard focus is visible globally. */
|
|
@@ -13,7 +13,7 @@ export interface FocusVisibleResult {
|
|
|
13
13
|
interface GlobalListenerData {
|
|
14
14
|
focus: () => void;
|
|
15
15
|
}
|
|
16
|
-
export declare
|
|
16
|
+
export declare const hasSetupGlobalListeners: Map<Window, GlobalListenerData>;
|
|
17
17
|
/**
|
|
18
18
|
* EXPERIMENTAL
|
|
19
19
|
* Adds a window (i.e. iframe) to the list of windows that are being tracked for focus visible.
|
|
@@ -31,7 +31,7 @@ export declare let hasSetupGlobalListeners: Map<Window, GlobalListenerData>;
|
|
|
31
31
|
* @param element @default document.body - The element provided will be used to get the window to add.
|
|
32
32
|
* @returns A function to remove the event listeners and cleanup the state.
|
|
33
33
|
*/
|
|
34
|
-
export declare function addWindowFocusTracking(element?: HTMLElement
|
|
34
|
+
export declare function addWindowFocusTracking(element?: HTMLElement): () => void;
|
|
35
35
|
/**
|
|
36
36
|
* If true, keyboard focus is visible.
|
|
37
37
|
*/
|