Skip to content

Commit 82eb0ba

Browse files
committed
feat(select): add isTaggable prop
* feat: add tagging to create an option when it doesn't exist Fixes #119 * feat(select): improve props docs * fix(select): update isTaggable implementation * feat(playground): add isTaggable demo * feat(docs): add multiple select taggable (:isTaggable) demo * feat(select): expose createOption for taggable-no-options slot * fix(select): menu-option white-space * feat(playground): rewrite playground with multiple demos * chore: run linter * feat(playground): add more playground demos * fix(select): remove createOption, add onClick on isTaggable div * fix: update tests
1 parent 82c95a3 commit 82eb0ba

23 files changed

+702
-124
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ It also leverages the power of generics to provide types for additional properti
8282

8383
```vue
8484
<script setup lang="ts">
85+
import type { Option } from "vue3-select-component";
8586
import { ref } from "vue";
86-
import VueSelect, { type Option } from "vue3-select-component";
87+
import VueSelect from "vue3-select-component";
8788
8889
type UserOption = Option<number> & { username: string };
8990

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default defineConfig({
4646
items: [
4747
{ text: "Single select", link: "/demo/single-select" },
4848
{ text: "Multiple Select", link: "/demo/multiple-select" },
49+
{ text: "Multiple Select Taggable", link: "/demo/multiple-select-taggable" },
4950
{ text: "Custom option slot", link: "/demo/custom-option-slot" },
5051
{ text: "Custom tag slot", link: "/demo/custom-tag-slot" },
5152
{ text: "Custom value/label properties", link: "/demo/custom-option-label-value" },

docs/demo/multiple-select-taggable.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
title: 'Multiple Select Taggable'
3+
---
4+
5+
# Multiple Select Taggable
6+
7+
The following example demonstrates how to use the `VueSelect` component to create a multiple select dropdown with taggable options _(options that are created by the user)_.
8+
9+
::: warning
10+
Setting `is-multi` to `true` will change the `v-model` to become an array of any `any[]`. Make sure to update your `v-model` accordingly.
11+
:::
12+
13+
<script setup>
14+
import { ref } from "vue";
15+
16+
import VueSelect from "../../src";
17+
18+
const selected = ref([]);
19+
20+
const options = ref([
21+
{ label: "JavaScript", value: "javascript" },
22+
{ label: "TypeScript", value: "typescript" },
23+
{ label: "Swift", value: "swift" },
24+
{ label: "Kotlin", value: "kotlin" },
25+
]);
26+
27+
const handleCreateOption = (value) => {
28+
options.value.push({ label: value, value });
29+
selected.value.push(value);
30+
};
31+
</script>
32+
33+
<VueSelect
34+
v-model="selected"
35+
:is-multi="true"
36+
:is-taggable="true"
37+
:options="options"
38+
@option-created="(value) => handleCreateOption(value)"
39+
/>
40+
41+
Selected value(s): **{{ selected.length ? selected.join(", ") : "none" }}**
42+
43+
## Demo source-code
44+
45+
```vue
46+
<script setup lang="ts">
47+
import type { Option } from "vue3-select-component";
48+
import { ref } from "vue";
49+
import VueSelect from "vue3-select-component";
50+
51+
// When setting `is-multi` to `true`, the `v-model` should be an array of strings.
52+
const selected = ref<string[]>([]);
53+
54+
const options = ref<Option<string>>([
55+
{ label: "JavaScript", value: "javascript" },
56+
{ label: "TypeScript", value: "typescript" },
57+
{ label: "Swift", value: "swift" },
58+
{ label: "Kotlin", value: "kotlin" },
59+
]);
60+
61+
const handleCreateOption = (value) => {
62+
options.value.push({ label: value, value });
63+
selected.value.push(value);
64+
};
65+
</script>
66+
67+
<template>
68+
<VueSelect
69+
v-model="selected"
70+
:is-multi="true"
71+
:is-taggable="true"
72+
:options="options"
73+
@option-created="(value) => handleCreateOption(value)"
74+
/>
75+
</template>
76+
```

docs/props.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ Whether the select should have a search input to filter the options.
114114

115115
Whether the select should allow multiple selections. If `true`, the `v-model` should be an array of string `string[]`.
116116

117+
## isTaggable
118+
119+
**Type**: `boolean`
120+
121+
**Default**: `false`
122+
123+
Whether the select should allow creating a new option if it doesn't exist. When `true`, if the user searches for an option that isn't part of the list, the menu will display a text to ask if the user wants to create this option.
124+
125+
::: info
126+
It is up to the user to intercept the new option (using `@option-created` event) and manipulate its array of options provided with the `:options` prop. It is also recommended to slugify the value received and ensure it is unique.
127+
For more details, see the [Multiple Select Taggable](/demo/multiple-select-taggable) demo.
128+
:::
129+
117130
## isLoading
118131

119132
**Type**: `boolean`
@@ -122,6 +135,14 @@ Whether the select should allow multiple selections. If `true`, the `v-model` sh
122135

123136
Whether the select should display a loading state. When `true`, the select will show a loading spinner or custom loading content provided via the `loading` slot.
124137

138+
## isMenuOpen
139+
140+
**Type**: `boolean`
141+
142+
**Default**: `undefined`
143+
144+
A prop to control the menu open state programmatically. When set to `true`, the menu will be open. When set to `false`, the menu will be closed.
145+
125146
## shouldAutofocusOption
126147

127148
**Type**: `boolean`
@@ -226,10 +247,21 @@ Resolves option data to a string to compare options and specify value attributes
226247

227248
This function can be used if you don't want to use the standard `option.value` as the value of the option.
228249

229-
## isMenuOpen
250+
## Events
230251

231-
**Type**: `boolean`
252+
### option-created
232253

233-
**Default**: `undefined`
254+
Emitted when a new option is created with the `:taggable="true"` prop.
234255

235-
A prop to control the menu open state programmatically. When set to `true`, the menu will be open. When set to `false`, the menu will be closed.
256+
**Payload**: `string` - The search content value.
257+
258+
```vue
259+
<template>
260+
<VueSelect
261+
v-model="selectedValue"
262+
:options="options"
263+
:taggable="true"
264+
@option-created="(value) => console.log('New option created:', value)"
265+
/>
266+
</template>
267+
```

docs/slots.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,23 @@ Customize the rendered template when the select component is in a loading state.
147147
</VueSelect>
148148
</template>
149149
```
150+
151+
## taggable-no-options
152+
153+
**Type**: `slotProps: { option: string }`
154+
155+
Customize the rendered template when there are no matching options and the `taggable` prop is set to `true`. You can use the slot props to retrieve the current search value.
156+
157+
```vue
158+
<template>
159+
<VueSelect
160+
v-model="option"
161+
:options="options"
162+
:taggable="true"
163+
>
164+
<template #taggable-no-options="{ option }">
165+
Press enter to add {{ option }} option
166+
</template>
167+
</VueSelect>
168+
</template>
169+
```

package-lock.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"vitepress": "1.6.3",
6767
"vitest": "3.0.5",
6868
"vue": "3.5.13",
69+
"vue-router": "4.5.0",
6970
"vue-tsc": "2.2.0"
7071
}
7172
}

playground/Playground.vue

Lines changed: 0 additions & 114 deletions
This file was deleted.

0 commit comments

Comments
 (0)