Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ coverage
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea
*.suo
*.ntvs*
Expand Down
46 changes: 46 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
// Enable the ESlint flat config support.
"eslint.experimental.useFlatConfig": true,

// Disable the default formatter, use ESLint instead.
"prettier.enable": false,
"editor.formatOnSave": false,

// Auto fix ESLint errors on save.
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},

// Silent the stylistic rules in you IDE, but still auto fix them.
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "format/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
],

// Enable eslint for all supported languages.
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml"
],

// Vue Official extension should not have the hybrid mode.
"vue.server.hybridMode": false,
}
44 changes: 42 additions & 2 deletions docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ title: 'Events'

# Events

Currently, Vue 3 Select Component doesn't emit any custom events, except for the native `v-model` one.

If you have the need for custom events, please open an issue on the [GitHub repository](https://github.com/TotomInc/vue3-select-component) with your use case and we will be happy to investigate it.

## `@option-selected`

Emitted when an option is selected, in the same tick where the `v-model` is updated.

```vue
<template>
<VueSelect
v-model="selectedValue"
:options="options"
@option-selected="(option) => console.log(option.label, option.value)"
/>
</template>
```

**Note**: this is emitted on the same tick as the v-model is updated, before a DOM re-render.

::: info
If you want to keep track of the selected option, it is recommended to use a `computed` combined with the `v-model`, instead of this event ([see this issue comment](https://github.com/TotomInc/vue3-select-component/issues/7#issuecomment-2083422621)).

```ts
const options = [{ label: "France", value: "FR" }, { label: "Spain", value: "ES" }];
const activeValue = ref<string>();
const selectedOption = computed(() => options.find((option) => option.value === activeValue.value));
```
:::

## `@option-deselected`

Emitted when an option is deselected, in the same tick where the `v-model` is updated.

```vue
<template>
<VueSelect
v-model="selectedValue"
:options="options"
@option-deselected="(option) => console.log(option.label, option.value)"
/>
</template>
```

**Note**: this is emitted on the same tick as the v-model is updated, before a DOM re-render.
28 changes: 19 additions & 9 deletions src/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ const props = withDefaults(
},
);

const emit = defineEmits<{
(e: "optionSelected", option: GenericOption): void;
(e: "optionDeselected", option: GenericOption | null): void;
}>();

/**
* The value of the selected option. When `isMulti` prop is set to `true`, this
* should be an array of `OptionValue`.
Expand Down Expand Up @@ -164,14 +169,16 @@ const closeMenu = () => {
search.value = "";
};

const setOption = (value: OptionValue) => {
const setOption = (option: GenericOption) => {
if (props.isMulti) {
(selected.value as OptionValue[]).push(value);
(selected.value as OptionValue[]).push(option.value);
}
else {
selected.value = value;
selected.value = option.value;
}

emit("optionSelected", option);

search.value = "";

if (props.closeOnSelect) {
Expand All @@ -183,18 +190,21 @@ const setOption = (value: OptionValue) => {
}
};

const removeOption = (value: OptionValue) => {
const removeOption = (option: GenericOption) => {
if (props.isMulti) {
selected.value = (selected.value as OptionValue[]).filter((v) => v !== value);
selected.value = (selected.value as OptionValue[]).filter((value) => value !== option.value);
emit("optionDeselected", option);
}
};

const clear = () => {
if (props.isMulti) {
selected.value = [];
emit("optionDeselected", null);
}
else {
selected.value = undefined as OptionValue;
emit("optionDeselected", selectedOptions.value[0]);
}

menuOpen.value = false;
Expand All @@ -219,13 +229,13 @@ const handleNavigation = (e: KeyboardEvent) => {

if (e.key === "Enter") {
e.preventDefault();
setOption(filteredOptions.value[focusedOption.value].value);
setOption(filteredOptions.value[focusedOption.value]);
}

// When pressing space with menu open but no search, select the focused option.
if (e.code === "Space" && search.value.length === 0) {
e.preventDefault();
setOption(filteredOptions.value[focusedOption.value].value);
setOption(filteredOptions.value[focusedOption.value]);
}

if (e.key === "Escape") {
Expand Down Expand Up @@ -342,7 +352,7 @@ onBeforeUnmount(() => {
:key="i"
type="button"
class="multi-value"
@click="removeOption(option.value)"
@click="removeOption(option)"
>
{{ getMultiValueLabel(option) }}
<XMarkIcon />
Expand Down Expand Up @@ -421,7 +431,7 @@ onBeforeUnmount(() => {
:index="i"
:is-focused="focusedOption === i"
:is-selected="option.value === selected"
@select="setOption(option.value)"
@select="setOption(option)"
>
<slot name="option" :option="option">
{{ getOptionLabel(option) }}
Expand Down