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.
Files changed (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -1
  3. package/dist/composable-ui.js +1114 -0
  4. package/dist/composable-ui.umd.cjs +1 -0
  5. package/dist/types/components/combobox/comboboxHiddenInput.vue.d.ts +8 -0
  6. package/dist/types/components/combobox/comboboxInput.vue.d.ts +9 -0
  7. package/dist/types/components/combobox/comboboxOption.vue.d.ts +22 -0
  8. package/dist/types/components/combobox/comboboxOptions.vue.d.ts +11 -0
  9. package/dist/types/components/injectionKeys.d.ts +43 -0
  10. package/dist/types/components/listbox/listbox.vue.d.ts +26 -0
  11. package/dist/types/components/listbox/listboxOption.vue.d.ts +22 -0
  12. package/dist/types/components/menu/menuItem.vue.d.ts +26 -0
  13. package/dist/types/components/menu/menuItems.vue.d.ts +11 -0
  14. package/dist/types/components/popover/popoverDialog.vue.d.ts +11 -0
  15. package/dist/types/components/tabs/tab.vue.d.ts +19 -0
  16. package/dist/types/components/tabs/tabContainer.vue.d.ts +18 -0
  17. package/dist/types/components/tabs/tabList.vue.d.ts +17 -0
  18. package/dist/types/components/tabs/tabPanel.vue.d.ts +15 -0
  19. package/dist/types/components/tooltip.vue.d.ts +36 -0
  20. package/dist/types/composables/formControl.d.ts +48 -0
  21. package/dist/types/composables/list.d.ts +103 -0
  22. package/dist/types/composables/listOption.d.ts +30 -0
  23. package/dist/types/composables/popover.d.ts +68 -0
  24. package/{src/main.ts → dist/types/main.d.ts} +3 -44
  25. package/dist/types/utils/element.d.ts +3 -0
  26. package/dist/types/utils/id.d.ts +1 -0
  27. package/package.json +19 -6
  28. package/.github/workflows/deploy.yml +0 -21
  29. package/.github/workflows/docs.yml +0 -62
  30. package/.vscode/extensions.json +0 -7
  31. package/docs/.vitepress/config.mts +0 -40
  32. package/docs/.vitepress/theme/index.js +0 -4
  33. package/docs/.vitepress/theme/style.scss +0 -155
  34. package/docs/components/combobox.md +0 -174
  35. package/docs/components/demos/combobox.vue +0 -142
  36. package/docs/components/demos/combobox_autocomplete.vue +0 -75
  37. package/docs/components/demos/combobox_tags.vue +0 -127
  38. package/docs/components/demos/input.vue +0 -35
  39. package/docs/components/demos/listbox.vue +0 -64
  40. package/docs/components/demos/menu.vue +0 -93
  41. package/docs/components/demos/popover.vue +0 -16
  42. package/docs/components/demos/tabs.vue +0 -59
  43. package/docs/components/demos/tooltip.vue +0 -27
  44. package/docs/components/listbox.md +0 -9
  45. package/docs/components/menu.md +0 -121
  46. package/docs/components/popover.md +0 -82
  47. package/docs/components/tabs.md +0 -9
  48. package/docs/components/tooltip.md +0 -78
  49. package/docs/composables/form-control.md +0 -130
  50. package/docs/composables/list-option.md +0 -47
  51. package/docs/composables/list.md +0 -192
  52. package/docs/composables/popover.md +0 -90
  53. package/docs/index.md +0 -24
  54. package/docs/intro.md +0 -0
  55. package/src/components/combobox/combobox.vue +0 -229
  56. package/src/components/combobox/comboboxFormInput.vue +0 -33
  57. package/src/components/combobox/comboboxOption.vue +0 -52
  58. package/src/components/combobox/comboboxOptions.vue +0 -17
  59. package/src/components/injectionKeys.ts +0 -50
  60. package/src/components/listbox/listbox.vue +0 -91
  61. package/src/components/listbox/listboxOption.vue +0 -37
  62. package/src/components/menu/menu.vue +0 -100
  63. package/src/components/menu/menuItem.vue +0 -86
  64. package/src/components/menu/menuItems.vue +0 -51
  65. package/src/components/popover/popover.vue +0 -68
  66. package/src/components/popover/popoverDialog.vue +0 -35
  67. package/src/components/tabs/tab.vue +0 -35
  68. package/src/components/tabs/tabContainer.vue +0 -50
  69. package/src/components/tabs/tabList.vue +0 -52
  70. package/src/components/tabs/tabPanel.vue +0 -36
  71. package/src/components/tooltip.vue +0 -115
  72. package/src/composables/formControl.ts +0 -144
  73. package/src/composables/list.ts +0 -326
  74. package/src/composables/listOption.ts +0 -80
  75. package/src/composables/popover.ts +0 -306
  76. package/src/utils/element.ts +0 -19
  77. package/src/utils/id.ts +0 -5
  78. package/src/vite-env.d.ts +0 -1
  79. package/tsconfig.json +0 -27
  80. package/tsconfig.node.json +0 -10
  81. 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>