Skip to content

Commit 21fe3db

Browse files
nats-afsTotomInc
authored andcommitted
feat(slots): 281 placeholder slot
1 parent bf1d379 commit 21fe3db

File tree

8 files changed

+81
-8
lines changed

8 files changed

+81
-8
lines changed

docs/slots.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,19 @@ Customize the rendered template when there are no matching options and the `tagg
177177
</VueSelect>
178178
</template>
179179
```
180+
181+
## placeholder
182+
183+
**Type**: `slotProps: { text: string }`
184+
185+
Customize the rendered template for the placeholder.
186+
187+
```vue
188+
<template>
189+
<VueSelect v-model="option" :options="options">
190+
<template #placeholder>
191+
<span>Custom placeholder</span>
192+
</template>
193+
</VueSelect>
194+
</template>
195+
```

playground/PlaygroundLayout.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const links = [
88
{ value: "/single-select", label: "Single Select" },
99
{ value: "/multi-select", label: "Multi Select" },
1010
{ value: "/multi-select-taggable", label: "Multi Select Taggable" },
11+
{ value: "/custom-placeholder", label: "Custom Placeholder" },
1112
{ value: "/extra-option-properties", label: "Extra Option Properties" },
1213
{ value: "/custom-option-label-value", label: "Custom Option Label/Value" },
1314
{ value: "/custom-search-filter", label: "Custom Search Filter" },
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script setup lang="ts">
2+
import type { Option } from "../../src";
3+
import { ref } from "vue";
4+
import VueSelect from "../../src";
5+
6+
const selected = ref<string | null>(null);
7+
const options: Option<string>[] = [
8+
{ label: "Alice's Adventures in Wonderland", value: "alice" },
9+
{ label: "A Wizard of Earthsea", value: "wizard" },
10+
{ label: "Harry Potter and the Philosopher's Stone", value: "harry_potter_1" },
11+
{ label: "Harry Potter and the Chamber of Secrets", value: "harry_potter_2" },
12+
];
13+
</script>
14+
15+
<template>
16+
<VueSelect
17+
v-model="selected"
18+
:options="options"
19+
:is-multi="false"
20+
placeholder="Pick a book"
21+
>
22+
<template #placeholder>
23+
<span>Custom placeholder</span>
24+
</template>
25+
</VueSelect>
26+
27+
<p class="selected-value">
28+
Selected book value: {{ selected || "none" }}
29+
</p>
30+
</template>

playground/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from "vue-router";
33

44
import ControlledMenu from "./demos/ControlledMenu.vue";
55
import CustomOptionLabelValue from "./demos/CustomOptionLabelValue.vue";
6+
import CustomPlaceholder from "./demos/CustomPlaceholder.vue";
67
import CustomSearchFilter from "./demos/CustomSearchFilter.vue";
78
import ExtraOptionProperties from "./demos/ExtraOptionProperties.vue";
89
import MenuHeader from "./demos/MenuHeader.vue";
@@ -20,6 +21,7 @@ const router = createRouter({
2021
{ path: "/single-select", component: SingleSelect },
2122
{ path: "/multi-select", component: MultiSelect },
2223
{ path: "/multi-select-taggable", component: MultiSelectTaggable },
24+
{ path: "/custom-placeholder", component: CustomPlaceholder },
2325
{ path: "/extra-option-properties", component: ExtraOptionProperties },
2426
{ path: "/custom-option-label-value", component: CustomOptionLabelValue },
2527
{ path: "/custom-search-filter", component: CustomSearchFilter },

src/Placeholder.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
<script setup lang="ts">
1+
<script setup lang="ts" generic="GenericOption extends Option<OptionValue>, OptionValue = string">
2+
import type { Option } from "./types/option";
3+
import type { Slots } from "./types/slots";
4+
25
defineProps<{
36
text: string;
7+
placeholderSlot?: Slots<GenericOption, OptionValue>["placeholder"];
48
}>();
59
</script>
610

711
<template>
812
<div class="input-placeholder">
9-
{{ text }}
13+
<component :is="placeholderSlot" v-if="placeholderSlot" />
14+
15+
<template v-else>
16+
{{ text }}
17+
</template>
1018
</div>
1119
</template>
1220

src/Select.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Option } from "./types/option";
22
import { mount } from "@vue/test-utils";
3-
43
import { describe, expect, it } from "vitest";
4+
import { h } from "vue";
5+
56
import VueSelect from "./Select.vue";
67

78
const options = [
@@ -40,6 +41,18 @@ describe("input + menu interactions behavior", () => {
4041
expect(wrapper.find(".input-placeholder").text()).toBe("Select an option");
4142
});
4243

44+
it("should display the placeholder slot", async () => {
45+
const wrapper = mount(VueSelect, {
46+
props: { modelValue: null, options, placeholder: "placeholder prop" },
47+
slots: {
48+
placeholder: () => h("div", { class: "custom-placeholder" }, "Custom placeholder"),
49+
},
50+
});
51+
52+
expect(wrapper.find(".custom-placeholder").exists()).toBe(true);
53+
expect(wrapper.get(".custom-placeholder").text()).toBe("Custom placeholder");
54+
});
55+
4356
it("should not open the menu when focusing the input", async () => {
4457
const wrapper = mount(VueSelect, { props: { modelValue: null, options } });
4558

src/Select.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,13 @@ watch(
314314
:aria-controls="`vue-select-${uid}-listbox`"
315315
aria-haspopup="true"
316316
>
317-
<Placeholder
318-
v-if="!selectedOptions[0] && !search.length"
319-
:text="placeholder"
320-
:class="props.classes?.placeholder"
321-
/>
317+
<template v-if="!selectedOptions[0] && !search.length">
318+
<Placeholder
319+
:text="placeholder"
320+
:placeholder-slot="slots.placeholder"
321+
:class="props.classes?.placeholder"
322+
/>
323+
</template>
322324

323325
<div
324326
v-else-if="!props.isMulti && selectedOptions[0]"

src/types/slots.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type Slots<GenericOption extends Option<OptionValue>, OptionValue> = {
1515
"option"?: (props: { option: GenericOption; index: number; isFocused: boolean; isSelected: boolean; isDisabled: boolean }) => any;
1616
"no-options"?: () => any;
1717
"taggable-no-options"?: () => any;
18+
"placeholder"?: () => any;
1819
};
1920

2021
export type IndicatorsSlots<GenericOption extends Option<OptionValue>, OptionValue> = Pick<

0 commit comments

Comments
 (0)