Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 3 / Component Roadmap #1597

Open
18 of 26 tasks
sagalbot opened this issue Feb 20, 2022 · 32 comments
Open
18 of 26 tasks

Vue 3 / Component Roadmap #1597

sagalbot opened this issue Feb 20, 2022 · 32 comments
Assignees

Comments

@sagalbot
Copy link
Owner

sagalbot commented Feb 20, 2022

Vue 3 support is currently on the beta release channel. This issue will track the state of progress to the v4 release of the component with support for Vue 3. This issue will be updated when progress has been made or new tasks are discovered.

To install the v4 beta with Vue 3 support:

yarn add vue-select@beta

# or npm

npm install vue-select@beta

This release has been out in the wild for some time now. At this point, things are pretty stable, but I can't guarantee there won't be more breaking changes. More on that at the very bottom.

Breaking Changes in v4 beta branch

  • value renamed to modelValue to match the Vue 3 v-model syntax
  • @input event renamed to update:modelValue
  • scss files have been removed and replaced with CSS properties

Remaining Tasks

Now that Vue 3 is the default install, the pressure is on to get support moved off the beta channel and into master for a v4.0 release!

Todo

These items must be completed in order to merge beta into master.

Build / Ecosystem

  • remove webpack dependency, switch to vite
    • update imports to include file extensions
  • remove jest dependencies, switch to vitest
  • write docs
    • v4 upgrade guide
    • breaking change list
  • update doc platform
    • pick vitepress or vuepress-next or something else entirely? nuxt? separate repo?
    • install nuxt 3
    • install @nuxt/content
    • migrate markdown files
  • versioned docs for v3 / v4

Vue 3 Updates

  • add emits property
  • replace this.$on
  • update scoped slot calls
  • update template ref syntax in v-for
  • new function to determine if component is reducing values

Public API Breaking Changes

  • : update v-model syntax
    • value prop -> modelValue
    • input event ->
  • drop SASS to CSS with custom properties
    • deprecated on 3.x
    • remove dependencies
    • update docs

How You Can Help

The current beta distribution has primarily been a community effort driven by contributors. A huge thanks to the folks that pushed up the new initial PRs, triaged bugs and helped with issues to get to this point. I appreciate it!

There's quite a bit to do here, and I'm excited to get moving. If you want to see this stuff move faster, the number one thing you can do is sponsor me through GitHub. I freelance full time, so the opportunity costs for me to work on vue-select instead of work for my clients is pretty high. Sponsorship contributions really help!

Outside of sponsoring, please get in touch before working on anything big so we can coordinate efforts - either through discussions or twitter. As always, I'm thrilled to welcome contributors to work on the component, and very grateful for those that do!


Wishlist

There are a few areas that the component really needs to improve on separate from Vue 3 support that may require breaking changes:

focus management
The open/closed state is currently tied to whether the input has focus. This is bad for accessibility and general UX of forms. This needs to be fixed.

state management
Having to manage selected values internally brings with it lots of code, and lots of room for bugs. A much simpler and easier to maintain approach would be to only emit events, don't manage state internally. This would really shrink the codebase, but in order for the component to be able to select values, the consumer needs to provide a v-model/:modelValue binding, otherwise the component won't know what's selected.

prop creep
The number of props available continues to grow, which creates challenges for maintenance and edge cases. Many of these props have been added to alter visual opinions, as well as modify default behaviour. With a more robust slot implementation (and probably provide/inject wrapper components like Headless UI) many of the style related props can be removed. Behaviour modification is more challenging.

I'd like to compress the breaking changes for those areas into the v4 release so that the component API is good to go for the next few years, and is in line with the way Vue is evolving. Converting the build to Vite/Vitest is part of that path. I'm going to focus on the critical path items, and if there's time, I'll assess the viability whether breaking changes are required in those areas for this release, or if it's better to address them in a future release. If you've got opinions about this, I'd love to hear them.

@sagalbot sagalbot self-assigned this Feb 20, 2022
@sagalbot sagalbot pinned this issue Feb 20, 2022
@matthewknill
Copy link

Hey really like this package! Just wondering if there's been any progress on supporting Vue 3?

@sagalbot
Copy link
Owner Author

@matthewknill the above is up to date at the moment. The vue-select@beta channel currently works with Vue 3.

@amchconsult
Copy link

it still not working as CDN global. please, can you give me an example?

thank you

@mattl1598
Copy link

also having issues with the cdn version

@franciscobressa
Copy link

Issues with CDN...

@Izet0004
Copy link

Anyone managed to make it work with cdn?

@amchconsult
Copy link

As CDN for this library on vue 3 still not working, mean while I found this component.

https://github.com/iendeavor/vue-next-select
https://morioh.com/p/7854e5675a05

Hope It helpful for someone too.

@Ima-Acikima
Copy link

Ima-Acikima commented Jul 27, 2022

Hi, currently event @input / @change doesnt triggered

@sagalbot
Copy link
Owner Author

@Ima-Acikima, the input/change events have been removed in favour of update:modelValue inline with the v-model changes for vue 3.

@walmon
Copy link

walmon commented Aug 3, 2022

@sagalbot just like @Ima-Acikima value change events are not triggering on my end either using @update:modelValue.

This is a tough one because I do see the event triggered in Vue Devtools. Interesting enough, I am actually able to hook to option:selected and other events.

I'm using "vue-select": "^4.0.0-beta.3", but I'm starting to suspect that it might be something related to using @vue/compat on my setup.

I'll debug it further and if I find a fix I'll PR it.


EDIT:

Found the issue and created a PR here #1668

@sagalbot
Copy link
Owner Author

@walmon thanks for the diagnosis and PR! Just on vacation today and will review in the next few days.

@ankitsinghaniyaz
Copy link

Is there a documentation showing how I can use this?

@daniellesniak
Copy link

daniellesniak commented Sep 17, 2022

@ankitsinghaniyaz

I made it work with Vue 3 Composition API.

Firstly you need to install it:

yarn add vue-select@beta
# or
npm install vue-select@beta

Now you can use it like this:

import vSelect from "vue-select";

[...]

<vSelect
  :options="['Canada', 'USA', 'Poland']"
  :clearable="false"
  v-model="selectedCountry"
  @update:modelValue="onCountryChange"
/>

@amchconsult

This comment was marked as resolved.

@karladler
Copy link

What is the vue-select-3 package? It seems to link to the same documentation as vue-select ...
https://www.npmjs.com/package/vue-select-3

@sagalbot
Copy link
Owner Author

sagalbot commented Nov 4, 2022

@amchconsult define dead for me

@sagalbot
Copy link
Owner Author

sagalbot commented Nov 7, 2022

@karladler that package is not associated with this one. Just a fork that someone published to NPM.

@dschmidt dschmidt mentioned this issue Nov 12, 2022
29 tasks
@rwfresh
Copy link

rwfresh commented Jan 10, 2023

@sagalbot thanks for creating and maintaining this library! I'm working on large Vue 2 app, upgrading to Vue 3.

Are there updated types available for Vue 3 select beta? Anything specific to compositionAPI? I'm getting some type issues with @types/vue-select v3.16.2

In my components definition ( components: { vSelect }, ) I am getting the following lint error: "No overload matches this call. Type 'VueSelectContstructor' is not assignable to type 'Component<any, any, ComputedOptions, MethodOptions, any, any, any>'

Thanks!

@rwfresh
Copy link

rwfresh commented Jan 20, 2023

Has anyone successfully migrated vue 2 version of this to vue 3?

@karladler
Copy link

Has anyone successfully migrated vue 2 version of this to vue 3?

yes, works fine for me.

@doutatsu
Copy link

What's the state of v4 development? Last update was 6 months ago and I am not sure if the project is still being worked on or if I should look for alternatives at this point

@doutatsu
Copy link

Looks like this library is not maintained anymore after all - anybody has recommendations for alternatives?

@mattl1598
Copy link

i went with Tom Select when no progress was made on this.
https://github.com/orchidjs/tom-select
Its not perfect but is framework agnostic and works quite well.

@doutatsu
Copy link

doutatsu commented Jun 3, 2023

@mattl1598 That actually looks fantastic, I haven't heard of it either! Thanks a bunch

@suzumejakku
Copy link

Hi @sagalbot ,
Is this library not maintained anymore ? v4 works for me in Vue 3, but I am not too keen on using a library which is not maintained. If you are looking for help, maybe you should advertise it on the main page ?

Thanks by the way for this great tool !

@GoudekettingRM
Copy link

GoudekettingRM commented Jan 26, 2024

I'm not sure if this is a vue3 issue, but I'm using "vue-select": "^4.0.0-beta.6" (nuxt@3.9.3) and I have a multiple select with vue-select. Every time I select an option from the list, it scrolls back to the top of the dropdown list. As you can imagine, this makes it very annoying to use.

I figured out what triggers it, but I'm not sure how to fix it. I wrote a wrapper for vue select to ensure styling etc are the same across my app. Therefore I have a model-value that comes in as a prop and I emit an update of that model value when something gets selected. If I comment out the emit (in handleSelect), it does not scroll back to the top every select. I'm sure I'm doing something wrong, but I cannot seem to figure out how to do it correctly 😅 Any help is very appreciated.

<template>
  <div>
    <v-select
      v-model="selected"
      :disabled="disabled"
      :name="name"
      label="label"
      :options="options"
      :multiple="multiple"
      :close-on-select="!multiple"
      :taggable="taggable"
      :push-tags="taggable"
      :placeholder="placeholder"
      :class="selectFieldClass"
      :style="{ backgroundColor: props.backgroundColor }"
      @input="handleSelect"
      @option:selected="handleSelect"
    >
      <template #option="option">
        <slot name="option" :option="option">
          {{ option.label }}
        </slot>
      </template>
      <template #no-options="{ search, searching }">
        <slot name="no-options" :search="search" :searching="searching">
          <div v-if="searching" class="vs__dropdown-option">
            No results found for <i>{{ search }}</i
            >.
          </div>
        </slot>
      </template>
      <template #selected-option="option">
        <slot name="selected-option" :option="option">
          {{ option.label }}
        </slot>
      </template>
    </v-select>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref } from 'vue';
import 'vue-select/dist/vue-select.css';

export interface IOption {
  value: string | number;
  label: string;
}

const props = withDefaults(
  defineProps<{
    modelValue: string | number | string[] | number[];
    options: (string | number | IOption)[];
    name: string;
    placeholder?: string;
    taggable?: boolean;
    disabled?: boolean;
    multiple?: boolean;
    backgroundColor?: string;
  }>(),
  {
    taggable: false,
    disabled: false,
    multiple: false,
    placeholder: 'Select a specific career',
    backgroundColor: '#526071',
  }
);

type TMultiple = string[] | number[] | IOption[];
type TSingle = string | number | IOption;
const selected = ref<TSingle | TMultiple | null>(null);

const selectFieldClass = computed(() => {
  return props.multiple ? 'vue-select--multiple' : 'vue-select--single';
});

const emit = defineEmits(['update:modelValue']);
const handleSelect = () => {
  if (props.multiple) {
    const arrayTyped = selected.value as TMultiple;

    const valuesToEmit =
      arrayTyped[0] &&
      (typeof arrayTyped[0] === 'string' || typeof arrayTyped[0] === 'number')
        ? arrayTyped
        : arrayTyped.map((option) => (option as IOption).value);
    emit('update:modelValue', valuesToEmit);
  } else {
    const singleTyped = selected.value as TSingle;

    const valueToEmit =
      typeof singleTyped === 'string' || typeof singleTyped === 'number'
        ? singleTyped
        : singleTyped?.value;
    emit('update:modelValue', valueToEmit);
  }
};

onBeforeMount(() => {
  selected.value =
    props.options.find((option) => {
      if (typeof option === 'string' || typeof option === 'number') {
        return option === props.modelValue;
      }
      return option.value === props.modelValue;
    }) || null;
});
</script>

@triosw
Copy link

triosw commented Apr 16, 2024

I have the exact same issue of @GoudekettingRM: "vue-select": "^4.0.0-beta.6" with a wrapper to ensure styling and default behaviours, dropdown scrolls to top every time an option is selected (model value updated).
Here is a snippet (my modelValue needs to be a string/array of string containing the value of the selected option/options)

<template>
    <div :class="wrapperClasses">
        <vue-select ref="vueSelect"
                :model-value="vueSelectValue"
                :options="convertedValidOptions"
                :disabled="disabled"
                :multiple="multiple"
                :clearable="clearable"
                :placeholder="placeholder"
                :append-to-body="appendToBody"
                :selectable="isOptionSelectable"
                :get-option-key="opt => opt.value || opt.code || opt.label || JSON.stringify(opt)"
                
                @update:modelValue="onVueSelectInput"
                @search="onSearch"
                >
            <template #no-options>
                <slot name="no-options">{{ 'No options found' }}</slot>
            </template>
            
            <template #option="option">
                <slot name="option" v-bind="option">
                    {{ option.label }}
                </slot>
            </template>
            
            <template #selected-option="option">
                <slot name="selected-option" v-bind="option">
                    {{ option.label }}
                </slot>
            </template>
        </vue-select>
    </div>
</template>


<script>
export default {
    name: 'custom-vue-select',
    props: {
        modelValue: {
            type: [String, Array],
        },
        options: {
            type: Array,
        },
        codeAccessor: {
            type: [String, Function],
            default: 'code',
        },
        descAccessor: {
            type: [String, Function],
            default: 'description',
        },
        showCodeInOptions: {
            type: Boolean,
            default: true,
        },
        showDescInOptions: {
            type: Boolean,
            default: true,
        },

        disabled: {
            type: Boolean,
            default: false,
        },
        multiple: {
            type: Boolean,
        },
        clearable: {
            type: Boolean,
            default: true,
        },
        placeholder: {
            type: String,
            default: '',
        },
        appendToBody: {
            type: Boolean,
            default: false,
        },
        
        isOptionSelectable: {
            type: Function,
            default: option => true,
        },
    },
    data() {
        return {
        };
    },
    computed: {
        wrapperClasses() {
            return {
                'pn--select': true,
                'select-single': !this.multiple,
                'select-multiple': this.multiple,
                'append-to-body': this.appendToBody,
                'valorized': this.modelValue?.length,
            };
        },
        
        convertedValidOptions(){
            return (this.options || [])
                    .map(x => {
                        if (!x) {
                            return null;
                        }
                        let code = typeof this.codeAccessor == 'function' ? this.codeAccessor(x) : x[this.codeAccessor];
                        let description = typeof this.descAccessor == 'function' ? this.descAccessor(x) : x[this.descAccessor];
                        let label = [
                            `${this.showCodeInOptions ? code || ' ' : ''}`,
                            `${this.showDescInOptions ? description || ' ' : ''}`,
                        ].filter(x => x).join(' - ').trim()
                        return {
                            data: x,
                            code,
                            description,
                            value: code,
                            label,
                        };
                    })
                    .filter(x => x && x.value)
                    ;
        },
        convertedValidOptionsMap(){
            return this.convertedValidOptions
                    .reduce((a, x) => {
                        a[x.value] = x;
                        return a;
                    }, {})
                    ;
        },
        vueSelectValue(){
            if (this.multiple) {
                return (this.modelValue || [])
                        .map(x => this.convertedValidOptionsMap[x])
                        .filter(x => x)
                        ;
            }
            return this.convertedValidOptionsMap[this.modelValue] || null;
        },
    },
    methods: {
        onSearch(filterText, setLoading){
            // Debounce
            this._onSearchTimeout && clearTimeout(this._onSearchTimeout);
            this._onSearchTimeout = setTimeout(() => {
                this.$emit('search', filterText, setLoading);
            }, 250);
        },
        onVueSelectInput(vueSelectValue){
            var value = '';
            if (this.multiple){
                value = (vueSelectValue || []).map(x => x.value || x.label); // x.label is for "taggable" behavior
            } else {
                value = vueSelectValue?.value || vueSelectValue?.label || ''; // x.label is for "taggable" behavior
            }
            this.$emit('update:modelValue', value);
        },
    },
    components: {
        VueSelect,
    },
};
</script>

<style lang="scss">
/* My custom style */
</style>

Any help will be very appreciated (and sorry for the english)

@GoudekettingRM

I figured out what triggers it, but I'm not sure how to fix it.

Is cause we "manually update" the :model-value?
Have you found a solution?

@sagalbot / @everybody reading
Is this component/version still mantained?
I have seen few commits on version 4.x in 2024 and wondered about the roadmap

Edit: replaced tabs identation with spaces

@GoudekettingRM
Copy link

@triosw I did a pretty hacky thing, but it seems to do the trick for me. Basically I store the scroll position when then click, and then on the next tick, restore it.

This is my handleSelect method (i.e. that what gets triggered once you click something in the list):

const handleSelect = () => {
  const scrollPosition = captureScrollPosition();

  const value = props.multiple ? getMultipleValues() : getSingleValue();

  emit('update:modelValue', value);
  restoreScrollPosition(scrollPosition);
};

The capture and restore functions are pretty straightforward:

const vSelectRef = ref<Ref | null>(null);

const captureScrollPosition = () => {
  if (vSelectRef.value) {
    const dropdown = vSelectRef.value.$el.querySelector('.vs__dropdown-menu');
    return dropdown.scrollTop;
  }
  return 0;
};

const restoreScrollPosition = (scrollPosition: number) => {
  nextTick(() => {
    if (vSelectRef.value) {
      const dropdown = vSelectRef.value.$el.querySelector('.vs__dropdown-menu');
      dropdown.scrollTop = scrollPosition;
    }
  });
};

the vSelectRef is just a normal ref on the vueselect tag:

<v-select
  ref="vSelectRef"
  ...
>    

Hope that helps.

@triosw
Copy link

triosw commented Apr 17, 2024

Thank you very muck @GoudekettingRM, that helped a lot (and worked for me too)!
I also added a "reset scroll position" (to be called at @open) to force dropdown.scrollTop = 0 (same behaviour i had with vue-select 2.x)

_resetDropdownScrollPosition(){
    this.$refs.vueSelect && this.$nextTick()
            .then(() => {
                let dropdown = this.$refs.vueSelect.$el.querySelector('.vs__dropdown-menu');
                dropdown.scrollTop = 0;
            })
            ;
},

A question still remains:

@sagalbot / @everybody reading
Is this component/version still mantained?
I have seen few commits on version 4.x in 2024 and wondered about the roadmap

@GoudekettingRM
Copy link

GoudekettingRM commented Apr 18, 2024

Glad I could be of help @triosw.

Is this component/version still mantained?

There's very little activity here, so I'm skeptical that it is. We started switching to NextJS because the support and the community for that is just bigger and further ahead in development.

@doutatsu
Copy link

+1. It doesn't seem like the library is maintained anymore, but I haven't personally found a better alternative just yet. I am looking at switching to this - https://www.shadcn-vue.com/docs/components/combobox.html - albeit it's not as comprehensive

@matthewknill
Copy link

This library looks good: https://github.com/vueform/multiselect?tab=readme-ov-file and also looks quite actively maintained. This library is still working for me but I will likely change if I find any issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests