-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathindex.svelte.ts
111 lines (96 loc) · 3.1 KB
/
index.svelte.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { Map as ReactiveMap } from 'svelte/reactivity';
import type { FloatingContext } from '../useFloating/index.svelte.js';
import type { ElementProps } from '../useInteractions/index.svelte.js';
type AriaRole = 'tooltip' | 'dialog' | 'alertdialog' | 'menu' | 'listbox' | 'grid' | 'tree';
type ComponentRole = 'select' | 'label' | 'combobox';
interface UseRoleOptions {
/**
* Whether the Hook is enabled, including all internal Effects and event
* handlers.
* @default true
*/
enabled?: boolean;
/**
* The role of the floating element.
* @default 'dialog'
*/
role?: AriaRole | ComponentRole;
}
const componentRoleToAriaRoleMap = new ReactiveMap<AriaRole | ComponentRole, AriaRole | false>([
['select', 'listbox'],
['combobox', 'listbox'],
['label', false]
]);
function useRole(context: FloatingContext, options: UseRoleOptions = {}): ElementProps {
const enabled = $derived(options.enabled ?? true);
const role = $derived(options.role ?? 'dialog');
const ariaRole = $derived(
(componentRoleToAriaRoleMap.get(role) ?? role) as AriaRole | false | undefined
);
// FIXME: Uncomment the commented code once useId and useFloatingParentNodeId are implemented.
const referenceId = '123abc';
const parentId = undefined;
// const referenceId = useId();
// const parentId = useFloatingParentNodeId();
const isNested = parentId != null;
const elementProps: ElementProps = $derived.by(() => {
if (!enabled) {
return {};
}
const floatingProps = {
id: context.floatingId,
...(ariaRole && { role: ariaRole })
};
if (ariaRole === 'tooltip' || role === 'label') {
return {
reference: {
[`aria-${role === 'label' ? 'labelledby' : 'describedby'}`]: context.open
? context.floatingId
: undefined
},
floating: floatingProps
};
}
return {
reference: {
'aria-expanded': context.open ? 'true' : 'false',
'aria-haspopup': ariaRole === 'alertdialog' ? 'dialog' : ariaRole,
'aria-controls': context.open ? context.floatingId : undefined,
...(ariaRole === 'listbox' && { role: 'combobox' }),
...(ariaRole === 'menu' && { id: referenceId }),
...(ariaRole === 'menu' && isNested && { role: 'menuitem' }),
...(role === 'select' && { 'aria-autocomplete': 'none' }),
...(role === 'combobox' && { 'aria-autocomplete': 'list' })
},
floating: {
...floatingProps,
...(ariaRole === 'menu' && { 'aria-labelledby': referenceId })
},
item({ active, selected }) {
const commonProps = {
role: 'option',
...(active && { id: `${context.floatingId}-option` })
};
// For `menu`, we are unable to tell if the item is a `menuitemradio`
// or `menuitemcheckbox`. For backwards-compatibility reasons, also
// avoid defaulting to `menuitem` as it may overwrite custom role props.
switch (role) {
case 'select':
return {
...commonProps,
'aria-selected': active && selected
};
case 'combobox': {
return {
...commonProps,
...(active && { 'aria-selected': true })
};
}
}
return {};
}
};
});
return elementProps;
}
export { useRole, type UseRoleOptions };