CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue-select

Everything you wish the HTML select element could do, wrapped up into a lightweight, extensible Vue component.

Pending
Overview
Eval results
Files

search-filtering.mddocs/

Search and Filtering

Built-in search functionality with customizable filtering logic, supporting real-time option filtering and custom match algorithms.

Capabilities

Search Configuration

Properties that control search input behavior and appearance.

/**
 * Enable/disable search input functionality
 */
searchable: Boolean  // default: true

/**
 * Clear search text when an option is selected
 */
clearSearchOnSelect: Boolean  // default: true

/**
 * Function to determine if search should clear on blur
 * Returns true when single selection and clearSearchOnSelect is true
 */
clearSearchOnBlur: Function

/**
 * Query selector for finding search input in custom search slot
 */
searchInputQuerySelector: String  // default: '[type=search]'

Filtering System

Advanced filtering configuration for customizing how options are matched against search text.

/**
 * Enable/disable filtering options by search text
 * Should not be used with taggable
 */
filterable: Boolean  // default: true

/**
 * Custom filter matching logic for individual options
 * @param option - The option to test
 * @param label - The option's label text  
 * @param search - Current search text
 * @returns Whether option matches search
 */
filterBy: Function  // default: case-insensitive indexOf

/**
 * Complete filter implementation for option lists
 * @param options - Array of all options
 * @param search - Current search text
 * @returns Filtered array of options
 */
filter: Function  // default: loops through options using filterBy

Search State

Data properties and computed values that track search state.

// Data properties
data: {
  /**
   * Current search input value
   */
  search: String,  // default: ''
  
  /**
   * Whether user is currently composing text (IME input)
   */
  isComposing: Boolean  // default: false
}

// Computed properties  
computed: {
  /**
   * Whether search input has a non-empty value
   */
  searching: Boolean,
  
  /**
   * Computed placeholder text for search input
   */
  searchPlaceholder: String | undefined,
  
  /**
   * Options filtered by search text and including taggable options
   */
  filteredOptions: Array,
  
  /**
   * Reference to the search input DOM element
   */
  searchEl: HTMLInputElement
}

Search Events

Events related to search input interactions.

/**
 * Emitted when search input gains focus
 */
'search:focus': () => void

/**
 * Emitted when search input loses focus  
 */
'search:blur': () => void

/**
 * Emitted when search text changes (from ajax mixin)
 * Provides search text and loading toggle function for AJAX
 * @param searchText - Current search input value
 * @param toggleLoading - Function to toggle loading state
 */
'search': (searchText: String, toggleLoading: Function) => void

Search Methods

Methods for managing search input state and focus.

/**
 * Handle search input focus - opens dropdown
 */
onSearchFocus(): void

/**
 * Handle search input blur - closes dropdown
 */
onSearchBlur(): void

/**
 * Handle search input keydown events
 * @param e - Keyboard event
 * @returns Keydown handler function
 */
onSearchKeyDown(e: KeyboardEvent): Function

/**
 * Handle search input keypress events  
 * @param e - Keyboard event
 */
onSearchKeyPress(e: KeyboardEvent): void

Usage Examples

Basic Search

<template>
  <v-select 
    v-model="selected"
    :options="options"
    searchable
    placeholder="Search options..."
  />
</template>

<script>
export default {
  data() {
    return {
      selected: null,
      options: [
        'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
        'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon'
      ]
    };
  }
};
</script>

Custom Filter Logic

<template>
  <v-select 
    v-model="selected"
    :options="products"
    :filterBy="customFilter"
    label="name"
    placeholder="Search products..."
  />
</template>

<script>
export default {
  data() {
    return {
      selected: null,
      products: [
        { id: 1, name: 'MacBook Pro', category: 'Electronics', price: 1999 },
        { id: 2, name: 'iPhone 13', category: 'Electronics', price: 799 },
        { id: 3, name: 'Nike Air Max', category: 'Shoes', price: 120 }
      ]
    };
  },
  methods: {
    customFilter(option, label, search) {
      // Search in name, category, and price
      const searchLower = search.toLowerCase();
      return (
        option.name.toLowerCase().includes(searchLower) ||
        option.category.toLowerCase().includes(searchLower) ||
        option.price.toString().includes(search)
      );
    }
  }
};
</script>

Complete Custom Filter Implementation

<template>
  <v-select 
    v-model="selected"
    :options="users"
    :filter="customFilterImplementation"
    label="displayName"
    placeholder="Advanced user search..."
  />
</template>

<script>
export default {
  data() {
    return {
      selected: null,
      users: [
        { id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
        { id: 2, firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
        { id: 3, firstName: 'Bob', lastName: 'Johnson', email: 'bob@example.com' }
      ]
    };
  },
  computed: {
    usersWithDisplayName() {
      return this.users.map(user => ({
        ...user,
        displayName: `${user.firstName} ${user.lastName}`
      }));
    }
  },
  methods: {
    customFilterImplementation(options, search) {
      if (!search) return options;
      
      const searchTerms = search.toLowerCase().split(' ');
      
      return options.filter(user => {
        const searchableText = `${user.firstName} ${user.lastName} ${user.email}`.toLowerCase();
        return searchTerms.every(term => searchableText.includes(term));
      });
    }
  }
};
</script>

AJAX Search Integration

<template>
  <v-select 
    v-model="selected"
    :options="searchResults"
    :loading="isLoading"
    @search="onSearch"
    label="title"
    placeholder="Search GitHub repositories..."
  />
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      selected: null,
      searchResults: [],
      isLoading: false
    };
  },
  methods: {
    async onSearch(search, toggleLoading) {
      if (search.length < 2) {
        this.searchResults = [];
        return;
      }
      
      toggleLoading(true);
      
      try {
        const response = await axios.get('https://api.github.com/search/repositories', {
          params: { q: search, sort: 'stars', order: 'desc' }
        });
        this.searchResults = response.data.items.slice(0, 10);
      } catch (error) {
        console.error('Search error:', error);
        this.searchResults = [];
      } finally {
        toggleLoading(false);
      }
    }
  }
};
</script>

Search State Control

<template>
  <div>
    <v-select 
      v-model="selected"
      :options="options"
      :clearSearchOnSelect="false"
      ref="vSelect"
      placeholder="Search with persistent text..."
    />
    <button @click="clearSearch">Clear Search</button>
    <button @click="focusSearch">Focus Search</button>
    <p>Current search: "{{ currentSearch }}"</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selected: null,
      options: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
      currentSearch: ''
    };
  },
  watch: {
    '$refs.vSelect.search'(newSearch) {
      this.currentSearch = newSearch;
    }
  },
  methods: {
    clearSearch() {
      this.$refs.vSelect.search = '';
    },
    focusSearch() {
      this.$refs.vSelect.searchEl.focus();
    }
  }
};
</script>

Non-Filterable Search (Display Only)

<template>
  <v-select 
    v-model="selected"
    :options="allOptions"
    :filterable="false"
    searchable
    @search="onSearch"
    placeholder="Search triggers external action..."
  />
</template>

<script>
export default {
  data() {
    return {
      selected: null,
      allOptions: ['Static Option 1', 'Static Option 2', 'Static Option 3']
    };
  },
  methods: {
    onSearch(searchText) {
      // Handle search without filtering options
      // Useful for AJAX search where server handles filtering
      console.log('Search triggered:', searchText);
      // Could trigger external API call, analytics, etc.
    }
  }
};
</script>

Install with Tessl CLI

npx tessl i tessl/npm-vue-select

docs

ajax-loading.md

customization.md

index.md

keyboard-navigation.md

search-filtering.md

selection.md

tagging.md

tile.json