vue-composable-ui 0.0.1 → 1.0.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/LICENSE +1 -1
- package/README.md +5 -1
- package/dist/composable-ui.js +1114 -0
- package/dist/composable-ui.umd.cjs +1 -0
- package/dist/types/components/combobox/comboboxHiddenInput.vue.d.ts +8 -0
- package/dist/types/components/combobox/comboboxInput.vue.d.ts +9 -0
- package/dist/types/components/combobox/comboboxOption.vue.d.ts +22 -0
- package/dist/types/components/combobox/comboboxOptions.vue.d.ts +11 -0
- package/dist/types/components/injectionKeys.d.ts +43 -0
- package/dist/types/components/listbox/listbox.vue.d.ts +26 -0
- package/dist/types/components/listbox/listboxOption.vue.d.ts +22 -0
- package/dist/types/components/menu/menuItem.vue.d.ts +26 -0
- package/dist/types/components/menu/menuItems.vue.d.ts +11 -0
- package/dist/types/components/popover/popoverDialog.vue.d.ts +11 -0
- package/dist/types/components/tabs/tab.vue.d.ts +19 -0
- package/dist/types/components/tabs/tabContainer.vue.d.ts +18 -0
- package/dist/types/components/tabs/tabList.vue.d.ts +17 -0
- package/dist/types/components/tabs/tabPanel.vue.d.ts +15 -0
- package/dist/types/components/tooltip.vue.d.ts +36 -0
- package/dist/types/composables/formControl.d.ts +48 -0
- package/dist/types/composables/list.d.ts +103 -0
- package/dist/types/composables/listOption.d.ts +30 -0
- package/dist/types/composables/popover.d.ts +68 -0
- package/{src/main.ts → dist/types/main.d.ts} +3 -44
- package/dist/types/utils/element.d.ts +3 -0
- package/dist/types/utils/id.d.ts +1 -0
- package/package.json +19 -6
- package/.github/workflows/deploy.yml +0 -21
- package/.github/workflows/docs.yml +0 -62
- package/.vscode/extensions.json +0 -7
- package/docs/.vitepress/config.mts +0 -40
- package/docs/.vitepress/theme/index.js +0 -4
- package/docs/.vitepress/theme/style.scss +0 -155
- package/docs/components/combobox.md +0 -174
- package/docs/components/demos/combobox.vue +0 -142
- package/docs/components/demos/combobox_autocomplete.vue +0 -75
- package/docs/components/demos/combobox_tags.vue +0 -127
- package/docs/components/demos/input.vue +0 -35
- package/docs/components/demos/listbox.vue +0 -64
- package/docs/components/demos/menu.vue +0 -93
- package/docs/components/demos/popover.vue +0 -16
- package/docs/components/demos/tabs.vue +0 -59
- package/docs/components/demos/tooltip.vue +0 -27
- package/docs/components/listbox.md +0 -9
- package/docs/components/menu.md +0 -121
- package/docs/components/popover.md +0 -82
- package/docs/components/tabs.md +0 -9
- package/docs/components/tooltip.md +0 -78
- package/docs/composables/form-control.md +0 -130
- package/docs/composables/list-option.md +0 -47
- package/docs/composables/list.md +0 -192
- package/docs/composables/popover.md +0 -90
- package/docs/index.md +0 -24
- package/docs/intro.md +0 -0
- package/src/components/combobox/combobox.vue +0 -229
- package/src/components/combobox/comboboxFormInput.vue +0 -33
- package/src/components/combobox/comboboxOption.vue +0 -52
- package/src/components/combobox/comboboxOptions.vue +0 -17
- package/src/components/injectionKeys.ts +0 -50
- package/src/components/listbox/listbox.vue +0 -91
- package/src/components/listbox/listboxOption.vue +0 -37
- package/src/components/menu/menu.vue +0 -100
- package/src/components/menu/menuItem.vue +0 -86
- package/src/components/menu/menuItems.vue +0 -51
- package/src/components/popover/popover.vue +0 -68
- package/src/components/popover/popoverDialog.vue +0 -35
- package/src/components/tabs/tab.vue +0 -35
- package/src/components/tabs/tabContainer.vue +0 -50
- package/src/components/tabs/tabList.vue +0 -52
- package/src/components/tabs/tabPanel.vue +0 -36
- package/src/components/tooltip.vue +0 -115
- package/src/composables/formControl.ts +0 -144
- package/src/composables/list.ts +0 -326
- package/src/composables/listOption.ts +0 -80
- package/src/composables/popover.ts +0 -306
- package/src/utils/element.ts +0 -19
- package/src/utils/id.ts +0 -5
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -27
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -28
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# usePopover
|
|
2
|
-
|
|
3
|
-
Popover is a floating container that appears next to its activator element above the main document flow. It can be used as a basic component for dropdown menus, comboboxes, tooltips, and more.
|
|
4
|
-
|
|
5
|
-
The composable abstracts the logic of positioning a popover relative to its activating element and captures clicks to automatically close the popover.
|
|
6
|
-
|
|
7
|
-
No event listeners are attached to the activating element to give the user full control over the way the popover is triggered.
|
|
8
|
-
|
|
9
|
-
## Type Declarations
|
|
10
|
-
|
|
11
|
-
### Composable
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
interface UsePopoverOptions {
|
|
15
|
-
/**
|
|
16
|
-
* The direction of the popover dialog
|
|
17
|
-
*
|
|
18
|
-
* @defaultValue `"down"`
|
|
19
|
-
*/
|
|
20
|
-
direction?: "up" | "down" | "left" | "right";
|
|
21
|
-
/**
|
|
22
|
-
* The alignment of the popover dialog
|
|
23
|
-
*
|
|
24
|
-
* @defaultValue `"left"`
|
|
25
|
-
*/
|
|
26
|
-
align?: "top" | "bottom" | "left" | "right" | "center" | "stretch";
|
|
27
|
-
/**
|
|
28
|
-
* The CSS position of the popover dialog
|
|
29
|
-
*
|
|
30
|
-
* @defaultValue `"fixed"`
|
|
31
|
-
*/
|
|
32
|
-
position?: "fixed" | "absolute";
|
|
33
|
-
/**
|
|
34
|
-
* Close the popover on a click outside of the dialog
|
|
35
|
-
*
|
|
36
|
-
* @defaultValue `true`
|
|
37
|
-
*/
|
|
38
|
-
closeOnOutsideClick?: boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Close the popover on a click inside of the dialog
|
|
41
|
-
*
|
|
42
|
-
* @defaultValue `false`
|
|
43
|
-
*/
|
|
44
|
-
closeOnInsideClick?: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
49
|
-
* @param activatorEl A template ref or a CSS selector
|
|
50
|
-
* @param popupEl A template ref or a CSS selector
|
|
51
|
-
* @param options Popover options
|
|
52
|
-
*/
|
|
53
|
-
export declare function usePopover(
|
|
54
|
-
activatorEl: TemplateRefOrSelector,
|
|
55
|
-
popupEl: TemplateRefOrSelector,
|
|
56
|
-
options?: UsePopoverOptions
|
|
57
|
-
): UsePopoverReturn;
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Return Type
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
interface FocusOptions {
|
|
64
|
-
/**
|
|
65
|
-
* Focus the popover/activator
|
|
66
|
-
*
|
|
67
|
-
* @defaultValue `true`
|
|
68
|
-
*/
|
|
69
|
-
focus?: boolean;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface UsePopoverReturn {
|
|
73
|
-
/**
|
|
74
|
-
* The popover state
|
|
75
|
-
*/
|
|
76
|
-
isOpen: Ref<boolean>;
|
|
77
|
-
/**
|
|
78
|
-
* Open the popover
|
|
79
|
-
*/
|
|
80
|
-
open: (options?: FocusOptions) => void;
|
|
81
|
-
/**
|
|
82
|
-
* Close the popover
|
|
83
|
-
*/
|
|
84
|
-
close: (options?: FocusOptions) => void;
|
|
85
|
-
/**
|
|
86
|
-
* Toggle the popover
|
|
87
|
-
*/
|
|
88
|
-
toggle: (options?: FocusOptions) => void;
|
|
89
|
-
}
|
|
90
|
-
```
|
package/docs/index.md
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# https://vitepress.dev/reference/default-theme-home-page
|
|
3
|
-
layout: home
|
|
4
|
-
|
|
5
|
-
hero:
|
|
6
|
-
name: "Vue Composable UI"
|
|
7
|
-
text: "A truly headless UI framework"
|
|
8
|
-
tagline: My great project tagline
|
|
9
|
-
actions:
|
|
10
|
-
- theme: brand
|
|
11
|
-
text: Introduction
|
|
12
|
-
link: /intro
|
|
13
|
-
- theme: alt
|
|
14
|
-
text: API Examples
|
|
15
|
-
link: /api-examples
|
|
16
|
-
|
|
17
|
-
features:
|
|
18
|
-
- title: Feature A
|
|
19
|
-
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
|
20
|
-
- title: Feature B
|
|
21
|
-
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
|
22
|
-
- title: Feature C
|
|
23
|
-
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
|
24
|
-
---
|
package/docs/intro.md
DELETED
|
File without changes
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<template v-if="name">
|
|
3
|
-
<ComboboxFormInput
|
|
4
|
-
v-if="isMultiselectable"
|
|
5
|
-
v-for="(value, id) in formInputs"
|
|
6
|
-
:name="`${name}[${id}]`"
|
|
7
|
-
:value="value"
|
|
8
|
-
/>
|
|
9
|
-
<ComboboxFormInput v-else :name="name" :value="formInputs" />
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<slot
|
|
13
|
-
name="input"
|
|
14
|
-
:popoverId="popoverId"
|
|
15
|
-
:attrs="inputAttrs"
|
|
16
|
-
:isOpen="isOpen"
|
|
17
|
-
:toggle="toggle"
|
|
18
|
-
>
|
|
19
|
-
<input v-bind="{ ...$attrs, ...inputAttrs }" autocomplete="off" />
|
|
20
|
-
</slot>
|
|
21
|
-
|
|
22
|
-
<slot />
|
|
23
|
-
</template>
|
|
24
|
-
|
|
25
|
-
<script setup lang="ts">
|
|
26
|
-
import { ref, computed, provide, watch, nextTick } from "vue";
|
|
27
|
-
import { useSequentialId } from "../../utils/id";
|
|
28
|
-
import { usePopover } from "../../composables/popover";
|
|
29
|
-
import { useList } from "../../composables/list";
|
|
30
|
-
import { useFormControl, FormValidator } from "../../composables/formControl";
|
|
31
|
-
import ComboboxFormInput from "./comboboxFormInput.vue";
|
|
32
|
-
import { injectCombobox } from "../injectionKeys";
|
|
33
|
-
|
|
34
|
-
defineOptions({ inheritAttrs: false });
|
|
35
|
-
|
|
36
|
-
const props = withDefaults(
|
|
37
|
-
defineProps<{
|
|
38
|
-
displayValue?: (value: any) => string;
|
|
39
|
-
|
|
40
|
-
id?: string;
|
|
41
|
-
activatorId?: string;
|
|
42
|
-
|
|
43
|
-
name?: string;
|
|
44
|
-
formValue?: (item: any) => Record<string, string> | string;
|
|
45
|
-
|
|
46
|
-
validators?: FormValidator[];
|
|
47
|
-
showValidationError?: boolean;
|
|
48
|
-
}>(),
|
|
49
|
-
{
|
|
50
|
-
displayValue: (value: any) =>
|
|
51
|
-
Array.isArray(value)
|
|
52
|
-
? value.map((i) => i.toString()).join(", ")
|
|
53
|
-
: value.toString(),
|
|
54
|
-
formValue: (item: any) => item,
|
|
55
|
-
showValidationError: true,
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const formInputs = computed(() => {
|
|
60
|
-
if (isMultiselectable.value) {
|
|
61
|
-
return (model.value as any[]).map((i) => props.formValue(i));
|
|
62
|
-
} else {
|
|
63
|
-
return props.formValue(model.value);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const model = defineModel();
|
|
68
|
-
const modelIsValid = defineModel("isValid");
|
|
69
|
-
const modelValidationMessage = defineModel("validationMessage");
|
|
70
|
-
|
|
71
|
-
const comboboxId = props.id ?? useSequentialId("combobox");
|
|
72
|
-
const activatorId = props.activatorId ?? comboboxId;
|
|
73
|
-
const popoverId = `${comboboxId}-popover`;
|
|
74
|
-
|
|
75
|
-
// Popover
|
|
76
|
-
const { isOpen, toggle, open, close } = usePopover(
|
|
77
|
-
`#${activatorId}`,
|
|
78
|
-
`#${popoverId}`,
|
|
79
|
-
{
|
|
80
|
-
direction: "down",
|
|
81
|
-
align: "stretch",
|
|
82
|
-
position: "fixed",
|
|
83
|
-
closeOnOutsideClick: true,
|
|
84
|
-
closeOnInsideClick: false,
|
|
85
|
-
}
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// List
|
|
89
|
-
const {
|
|
90
|
-
items,
|
|
91
|
-
activeItem,
|
|
92
|
-
isMultiselectable,
|
|
93
|
-
selectActiveItem,
|
|
94
|
-
activateSelectedItem,
|
|
95
|
-
activateFirstItem,
|
|
96
|
-
activateLastItem,
|
|
97
|
-
activateNextItem,
|
|
98
|
-
activatePrevItem,
|
|
99
|
-
} = useList(model);
|
|
100
|
-
|
|
101
|
-
// Form Control
|
|
102
|
-
const { focus, isValid, validationMessage } = useFormControl(
|
|
103
|
-
`#${comboboxId}`,
|
|
104
|
-
props.validators,
|
|
105
|
-
props.showValidationError
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
watch(isValid, () => (modelIsValid.value = isValid.value));
|
|
109
|
-
watch(
|
|
110
|
-
validationMessage,
|
|
111
|
-
() => (modelValidationMessage.value = validationMessage.value)
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
watch(items, () => {
|
|
115
|
-
if (!activeItem.value && items.value.length) {
|
|
116
|
-
if (
|
|
117
|
-
(isMultiselectable.value && (model.value as any[]).length) ||
|
|
118
|
-
(!isMultiselectable.value && model.value)
|
|
119
|
-
) {
|
|
120
|
-
activateSelectedItem({ focus: false });
|
|
121
|
-
} else {
|
|
122
|
-
activateFirstItem({ focus: false });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const inputValue = ref("");
|
|
128
|
-
|
|
129
|
-
function updateInputValue() {
|
|
130
|
-
inputValue.value = props.displayValue(model.value);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
watch(model, updateInputValue, { deep: true, immediate: true });
|
|
134
|
-
|
|
135
|
-
function onChange() {
|
|
136
|
-
updateInputValue();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function onInput(evt: KeyboardEvent) {
|
|
140
|
-
inputValue.value = (evt.target as HTMLInputElement).value;
|
|
141
|
-
open({ focus: false });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function onKeyDown(evt: KeyboardEvent) {
|
|
145
|
-
if (!isOpen.value) {
|
|
146
|
-
switch (evt.code) {
|
|
147
|
-
case "Space":
|
|
148
|
-
case "Enter":
|
|
149
|
-
open({ focus: false });
|
|
150
|
-
break;
|
|
151
|
-
|
|
152
|
-
case "End":
|
|
153
|
-
case "ArrowUp":
|
|
154
|
-
open({ focus: false });
|
|
155
|
-
nextTick(() => activateLastItem({ focus: false }));
|
|
156
|
-
break;
|
|
157
|
-
|
|
158
|
-
case "Home":
|
|
159
|
-
case "ArrowDown":
|
|
160
|
-
open({ focus: false });
|
|
161
|
-
nextTick(() => activateFirstItem({ focus: false }));
|
|
162
|
-
break;
|
|
163
|
-
|
|
164
|
-
default:
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
switch (evt.code) {
|
|
169
|
-
case "Space":
|
|
170
|
-
selectActiveItem();
|
|
171
|
-
if (!isMultiselectable.value) {
|
|
172
|
-
close();
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case "Enter":
|
|
176
|
-
selectActiveItem();
|
|
177
|
-
close();
|
|
178
|
-
break;
|
|
179
|
-
case "Tab":
|
|
180
|
-
selectActiveItem();
|
|
181
|
-
close();
|
|
182
|
-
return;
|
|
183
|
-
case "ArrowUp":
|
|
184
|
-
activatePrevItem({ focus: false, loop: false });
|
|
185
|
-
break;
|
|
186
|
-
case "ArrowDown":
|
|
187
|
-
activateNextItem({ focus: false, loop: false });
|
|
188
|
-
break;
|
|
189
|
-
case "Home":
|
|
190
|
-
activateFirstItem({ focus: false });
|
|
191
|
-
break;
|
|
192
|
-
case "End":
|
|
193
|
-
activateLastItem({ focus: false });
|
|
194
|
-
break;
|
|
195
|
-
case "Escape":
|
|
196
|
-
close();
|
|
197
|
-
break;
|
|
198
|
-
default:
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
evt.preventDefault();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const inputAttrs = computed(() => ({
|
|
207
|
-
value: inputValue.value,
|
|
208
|
-
oninput: onInput,
|
|
209
|
-
onchange: onChange,
|
|
210
|
-
onkeydown: onKeyDown,
|
|
211
|
-
onclick: () => open({ focus: false }),
|
|
212
|
-
id: comboboxId,
|
|
213
|
-
"aria-controls": isOpen.value ? popoverId : undefined,
|
|
214
|
-
"aria-expanded": isOpen.value,
|
|
215
|
-
"aria-activedescendant": activeItem.value?.element.value?.id,
|
|
216
|
-
"aria-haspopup": "listbox" as "listbox",
|
|
217
|
-
role: "combobox",
|
|
218
|
-
}));
|
|
219
|
-
|
|
220
|
-
provide(injectCombobox, {
|
|
221
|
-
popoverId,
|
|
222
|
-
isOpen,
|
|
223
|
-
isMultiselectable,
|
|
224
|
-
close,
|
|
225
|
-
focus,
|
|
226
|
-
});
|
|
227
|
-
</script>
|
|
228
|
-
|
|
229
|
-
<style scoped></style>
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<input
|
|
3
|
-
v-for="(value, name) in inputs"
|
|
4
|
-
:name="name"
|
|
5
|
-
:value="value"
|
|
6
|
-
type="hidden"
|
|
7
|
-
/>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<script setup lang="ts">
|
|
11
|
-
import { computed } from "vue";
|
|
12
|
-
|
|
13
|
-
const props = defineProps<{
|
|
14
|
-
name: string;
|
|
15
|
-
value: any;
|
|
16
|
-
}>();
|
|
17
|
-
|
|
18
|
-
const inputs = computed(() => {
|
|
19
|
-
if (typeof props.value === "object") {
|
|
20
|
-
const fields: Record<string, any> = {};
|
|
21
|
-
for (const [key, value] of Object.entries(props.value)) {
|
|
22
|
-
fields[`${props.name}[${key}]`] = value;
|
|
23
|
-
}
|
|
24
|
-
return fields;
|
|
25
|
-
} else {
|
|
26
|
-
return {
|
|
27
|
-
[props.name]: props.value,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<style scoped></style>
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
ref="itemEl"
|
|
4
|
-
:id="itemId"
|
|
5
|
-
@click="onClick"
|
|
6
|
-
@mouseenter="activate({ focus: false })"
|
|
7
|
-
:data-active="isActive"
|
|
8
|
-
:aria-selected="isSelected"
|
|
9
|
-
:aria-disabled="disabled || undefined"
|
|
10
|
-
role="option"
|
|
11
|
-
>
|
|
12
|
-
<slot :isSelected="isSelected" :isActive="isActive" />
|
|
13
|
-
</div>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<script setup lang="ts">
|
|
17
|
-
import { ref, inject } from "vue";
|
|
18
|
-
import { useSequentialId } from "../../utils/id";
|
|
19
|
-
import { useListOption } from "../../composables/listOption";
|
|
20
|
-
import { injectCombobox } from "../injectionKeys";
|
|
21
|
-
|
|
22
|
-
const props = defineProps<{
|
|
23
|
-
id?: string;
|
|
24
|
-
value: any;
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
}>();
|
|
27
|
-
|
|
28
|
-
const itemId = props.id ?? useSequentialId("combobox-option");
|
|
29
|
-
|
|
30
|
-
const itemEl = ref<HTMLElement>();
|
|
31
|
-
|
|
32
|
-
const { isSelected, isActive, activate, select } = useListOption(
|
|
33
|
-
itemEl,
|
|
34
|
-
props.value,
|
|
35
|
-
props.disabled
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const combobox = inject(injectCombobox);
|
|
39
|
-
if (!combobox) {
|
|
40
|
-
throw new Error("Combobox injectable context is not provided");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function onClick() {
|
|
44
|
-
select();
|
|
45
|
-
if (!combobox!.isMultiselectable.value) {
|
|
46
|
-
combobox!.close({ focus: false });
|
|
47
|
-
}
|
|
48
|
-
combobox!.focus();
|
|
49
|
-
}
|
|
50
|
-
</script>
|
|
51
|
-
|
|
52
|
-
<style scoped></style>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="combobox!.isOpen.value" :id="combobox!.popoverId" role="listbox">
|
|
3
|
-
<slot />
|
|
4
|
-
</div>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import { inject } from "vue";
|
|
9
|
-
import { injectCombobox } from "../injectionKeys";
|
|
10
|
-
|
|
11
|
-
const combobox = inject(injectCombobox);
|
|
12
|
-
if (!combobox) {
|
|
13
|
-
throw new Error("Combobox injectable context is not provided");
|
|
14
|
-
}
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<style scoped></style>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef, InjectionKey } from "vue";
|
|
2
|
-
|
|
3
|
-
import type { UseListReturn } from "../composables/list";
|
|
4
|
-
import type { UsePopoverReturn } from "../composables/popover";
|
|
5
|
-
import type { useFormControlReturn } from "../composables/formControl";
|
|
6
|
-
|
|
7
|
-
export const injectTabs: InjectionKey<{
|
|
8
|
-
getIds: (value: any) => ComputedRef<{
|
|
9
|
-
tabId: string | undefined;
|
|
10
|
-
panelId: string | undefined;
|
|
11
|
-
}>;
|
|
12
|
-
selectActiveItem: UseListReturn["selectActiveItem"];
|
|
13
|
-
activateFirstItem: UseListReturn["activateFirstItem"];
|
|
14
|
-
activateLastItem: UseListReturn["activateLastItem"];
|
|
15
|
-
activateNextItem: UseListReturn["activateNextItem"];
|
|
16
|
-
activatePrevItem: UseListReturn["activatePrevItem"];
|
|
17
|
-
orientation: "horizontal" | "vertical";
|
|
18
|
-
}> = Symbol();
|
|
19
|
-
|
|
20
|
-
export const injectPopover: InjectionKey<{
|
|
21
|
-
popoverId: string;
|
|
22
|
-
activatorId: string;
|
|
23
|
-
role: "dialog" | "listbox" | "menu" | "tree" | "grid";
|
|
24
|
-
isOpen: UsePopoverReturn["isOpen"];
|
|
25
|
-
close: UsePopoverReturn["close"];
|
|
26
|
-
}> = Symbol();
|
|
27
|
-
|
|
28
|
-
export const injectMenu: InjectionKey<{
|
|
29
|
-
menuId: string;
|
|
30
|
-
activatorId: string;
|
|
31
|
-
isOpen: UsePopoverReturn["isOpen"];
|
|
32
|
-
close: UsePopoverReturn["close"];
|
|
33
|
-
|
|
34
|
-
activeItemId: ComputedRef<string | undefined>;
|
|
35
|
-
isMultiselectable: UseListReturn["isMultiselectable"];
|
|
36
|
-
activateFirstItem: UseListReturn["activateFirstItem"];
|
|
37
|
-
activateLastItem: UseListReturn["activateLastItem"];
|
|
38
|
-
activateNextItem: UseListReturn["activateNextItem"];
|
|
39
|
-
activatePrevItem: UseListReturn["activatePrevItem"];
|
|
40
|
-
}> = Symbol();
|
|
41
|
-
|
|
42
|
-
export const injectCombobox: InjectionKey<{
|
|
43
|
-
popoverId: string;
|
|
44
|
-
isOpen: UsePopoverReturn["isOpen"];
|
|
45
|
-
close: UsePopoverReturn["close"];
|
|
46
|
-
|
|
47
|
-
isMultiselectable: UseListReturn["isMultiselectable"];
|
|
48
|
-
|
|
49
|
-
focus: useFormControlReturn["focus"];
|
|
50
|
-
}> = Symbol();
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
@keydown="onKeyDown"
|
|
4
|
-
:aria-activedescendant="activeItemId"
|
|
5
|
-
:aria-orientation="orientation"
|
|
6
|
-
:aria-multiselectable="multiselectable"
|
|
7
|
-
role="listbox"
|
|
8
|
-
tabindex="0"
|
|
9
|
-
>
|
|
10
|
-
<slot />
|
|
11
|
-
</div>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script setup lang="ts">
|
|
15
|
-
import { computed } from "vue";
|
|
16
|
-
import { useList } from "../../composables/list";
|
|
17
|
-
|
|
18
|
-
const props = withDefaults(
|
|
19
|
-
defineProps<{
|
|
20
|
-
orientation?: "horizontal" | "vertical";
|
|
21
|
-
multiselectable?: boolean;
|
|
22
|
-
loop?: boolean;
|
|
23
|
-
}>(),
|
|
24
|
-
{
|
|
25
|
-
orientation: "vertical",
|
|
26
|
-
multiselectable: false,
|
|
27
|
-
loop: false,
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const model = defineModel();
|
|
32
|
-
|
|
33
|
-
const {
|
|
34
|
-
isMultiselectable,
|
|
35
|
-
activeItem,
|
|
36
|
-
selectActiveItem,
|
|
37
|
-
activateFirstItem,
|
|
38
|
-
activateLastItem,
|
|
39
|
-
activateNextItem,
|
|
40
|
-
activatePrevItem,
|
|
41
|
-
} = useList(model);
|
|
42
|
-
|
|
43
|
-
const activeItemId = computed(() => activeItem.value?.element.value?.id);
|
|
44
|
-
|
|
45
|
-
function onKeyDown(evt: KeyboardEvent) {
|
|
46
|
-
switch (evt.code) {
|
|
47
|
-
case "Space":
|
|
48
|
-
selectActiveItem();
|
|
49
|
-
break;
|
|
50
|
-
case "ArrowUp":
|
|
51
|
-
if (props.orientation === "horizontal") return;
|
|
52
|
-
activatePrevItem({ focus: false, loop: props.loop });
|
|
53
|
-
if (!isMultiselectable.value) {
|
|
54
|
-
selectActiveItem();
|
|
55
|
-
}
|
|
56
|
-
break;
|
|
57
|
-
case "ArrowDown":
|
|
58
|
-
if (props.orientation === "horizontal") return;
|
|
59
|
-
activateNextItem({ focus: false, loop: props.loop });
|
|
60
|
-
if (!isMultiselectable.value) {
|
|
61
|
-
selectActiveItem();
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
case "ArrowLeft":
|
|
65
|
-
if (props.orientation === "vertical") return;
|
|
66
|
-
activatePrevItem({ focus: false, loop: props.loop });
|
|
67
|
-
if (!isMultiselectable.value) {
|
|
68
|
-
selectActiveItem();
|
|
69
|
-
}
|
|
70
|
-
break;
|
|
71
|
-
case "ArrowRight":
|
|
72
|
-
if (props.orientation === "vertical") return;
|
|
73
|
-
activateNextItem({ focus: false, loop: props.loop });
|
|
74
|
-
if (!isMultiselectable.value) {
|
|
75
|
-
selectActiveItem();
|
|
76
|
-
}
|
|
77
|
-
break;
|
|
78
|
-
case "Home":
|
|
79
|
-
activateFirstItem({ focus: false });
|
|
80
|
-
break;
|
|
81
|
-
case "End":
|
|
82
|
-
activateLastItem({ focus: false });
|
|
83
|
-
break;
|
|
84
|
-
default:
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
evt.preventDefault();
|
|
88
|
-
}
|
|
89
|
-
</script>
|
|
90
|
-
|
|
91
|
-
<style scoped></style>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
ref="itemEl"
|
|
4
|
-
:id="itemId"
|
|
5
|
-
@click="select"
|
|
6
|
-
:aria-disabled="disabled"
|
|
7
|
-
:aria-selected="isSelected"
|
|
8
|
-
:data-active="isActive"
|
|
9
|
-
role="option"
|
|
10
|
-
tabindex="-1"
|
|
11
|
-
>
|
|
12
|
-
<slot :isSelected="isSelected" :isActive="isActive" />
|
|
13
|
-
</div>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<script setup lang="ts">
|
|
17
|
-
import { ref } from "vue";
|
|
18
|
-
import { useSequentialId } from "../../utils/id";
|
|
19
|
-
import { useListOption } from "../../composables/listOption";
|
|
20
|
-
|
|
21
|
-
const props = defineProps<{
|
|
22
|
-
id?: string;
|
|
23
|
-
value?: any;
|
|
24
|
-
disabled?: boolean;
|
|
25
|
-
}>();
|
|
26
|
-
|
|
27
|
-
const itemId = props.id ?? useSequentialId("list-option");
|
|
28
|
-
|
|
29
|
-
const itemEl = ref<HTMLElement>();
|
|
30
|
-
const { isSelected, isActive, select } = useListOption(
|
|
31
|
-
itemEl,
|
|
32
|
-
props.value,
|
|
33
|
-
props.disabled
|
|
34
|
-
);
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<style scoped></style>
|