Skip to content

Conversation

J-Sek
Copy link
Contributor

@J-Sek J-Sek commented Aug 4, 2025

Description

  • intersection (default) causes all rows to be filtered out when all headers have filter function
    • the test case seems to be wrong

Markup:

Minimal example
<template>
  <v-app theme="dark">
    <v-container>
      <v-data-table
        :headers="headers"
        :items="plants"
        item-key="name"
      />
    </v-container>
  </v-app>
</template>

<script setup lang="ts">
  const plants = [
    { name: 'Fern', light: 'Low', height: '20cm', petFriendly: 'Yes', price: 20 },
    { name: 'Snake Plant', light: 'Low', height: '50cm', petFriendly: 'No', price: 35 },
    { name: 'Monstera', light: 'Medium', height: '60cm', petFriendly: 'No', price: 50 },
    { name: 'Pothos', light: 'Low to medium', height: '40cm', petFriendly: 'Yes', price: 25 },
    { name: 'ZZ Plant', light: 'Low to medium', height: '90cm', petFriendly: 'Yes', price: 30 },
    { name: 'Spider Plant', light: 'Bright, indirect', height: '30cm', petFriendly: 'Yes', price: 15 },
    { name: 'Air Plant', light: 'Bright, indirect', height: '15cm', petFriendly: 'Yes', price: 10 },
    { name: 'Peperomia', light: 'Bright, indirect', height: '25cm', petFriendly: 'Yes', price: 20 },
    { name: 'Aloe Vera', light: 'Bright, direct', height: '30cm', petFriendly: 'Yes', price: 15 },
    { name: 'Jade Plant', light: 'Bright, direct', height: '40cm', petFriendly: 'Yes', price: 25 },
  ]

  const headers = [
    { title: 'Plant', key: 'name', filter: v => true },
    { title: 'Light', key: 'light', filter: v => true },
  ]
</script>
Full example
<template>
  <v-app theme="dark">
    <v-container>
      <v-data-table
        :headers="headers"
        :items="plants"
        density="compact"
        item-key="name"
        height="300"
        fixed-header
        hide-default-footer
      >
        <template #headers="{ columns, isSorted, toggleSort, getSortIcon }">
          <tr>
            <th
              v-for="column in columns"
              :key="column.key"
              :class="[
                'v-data-table__td',
                { 'v-data-table__th--sortable': column.sortable },
                { 'v-data-table__th--sorted': isSorted(column) },
              ]"
              :width="column.width"
              @click="column.sortable && toggleSort(column)"
            >
              {{ column.title }}
              <v-icon
                v-if="column.sortable"
                key="icon"
                class="v-data-table-header__sort-icon"
                size="small"
                :icon="getSortIcon(column)"
              />
            </th>
          </tr>
          <tr>
            <th
              v-for="column in columns"
              :key="column.key"
              class="v-data-table__td pa-2"
            >
              <v-text-field
                v-if="column.filterType === 'text'"
                v-model="filters[column.key]"
                density="compact"
                variant="solo"
                clearable
                persistent-clear
                flat
                hide-details
              >
                <template #append-inner>
                  <v-icon-btn
                    size="24"
                    icon-size="16"
                    icon="mdi-filter-variant"
                    class="mr-n1"
                    variant="text"
                  />
                </template>
              </v-text-field>
              <v-select
                v-if="column.filterType === 'select'"
                v-model="filters[column.key]"
                :items="filtersConfig[column.key].options"
                :multiple="filtersConfig[column.key].multiple"
                density="compact"
                variant="solo"
                clearable
                persistent-clear
                flat
                hide-details
              />
              <template v-if="column.filterType === 'number'">
                <v-number-input
                  v-model="filters[column.key].min"
                  control-variant="hidden"
                  density="compact"
                  variant="solo"
                  clearable
                  persistent-clear
                  flat
                  hide-details
                >
                  <template #append-inner>
                    <v-icon-btn
                      size="24"
                      icon-size="16"
                      icon="mdi-filter-variant"
                      class="mr-n1"
                      variant="text"
                    />
                  </template>
                </v-number-input>
              </template>
            </th>
          </tr>
        </template>
      </v-data-table>
    </v-container>
  </v-app>
</template>

<script setup lang="ts">
  import { reactive, ref } from 'vue'

  const plants = [
    { name: 'Fern', light: 'Low', height: '20cm', petFriendly: 'Yes', price: 20 },
    { name: 'Snake Plant', light: 'Low', height: '50cm', petFriendly: 'No', price: 35 },
    { name: 'Monstera', light: 'Medium', height: '60cm', petFriendly: 'No', price: 50 },
    { name: 'Pothos', light: 'Low to medium', height: '40cm', petFriendly: 'Yes', price: 25 },
    { name: 'ZZ Plant', light: 'Low to medium', height: '90cm', petFriendly: 'Yes', price: 30 },
    { name: 'Spider Plant', light: 'Bright, indirect', height: '30cm', petFriendly: 'Yes', price: 15 },
    { name: 'Air Plant', light: 'Bright, indirect', height: '15cm', petFriendly: 'Yes', price: 10 },
    { name: 'Peperomia', light: 'Bright, indirect', height: '25cm', petFriendly: 'Yes', price: 20 },
    { name: 'Aloe Vera', light: 'Bright, direct', height: '30cm', petFriendly: 'Yes', price: 15 },
    { name: 'Jade Plant', light: 'Bright, direct', height: '40cm', petFriendly: 'Yes', price: 25 },
  ]

  const filters = reactive({
    name: null as string | null,
    light: null as string | null,
    height: [] as string[],
    petFriendly: null as 'Yes' | 'No' | null,
    price: {
      min: null as number | null,
      max: null as number | null,
    }
  })

  const filtersConfig = {
    petFriendly: { options: ['Yes', 'No'] },
    height: { options: [...new Set(plants.map(x => x.height).toSorted())], multiple: true },
    price: { min: 0, max: 100 },
  }

  function textMatch(value: string | null, phrase: string | null) {
    const phraseLowercase = phrase?.toLowerCase().trim()
    return !phraseLowercase || value?.toLowerCase().trim().includes(phraseLowercase)
  }

  function selectMatch(value: any, filter: any | any[]) {
    return Array.isArray(filter)
      ? filter.length === 0 || filter.includes(value)
      : !filter || value === filter
  }

  function numericMatch(value: number | null, filter: { min: number | null, max: number | null }) {
    return (!filter.min || value !== null && value >= filter.min)
      && (!filter.max || value !== null && value <= filter.max)
  }

  const headers = ref([
    { width: 200, title: 'Plant', align: 'start', sortable: false, key: 'name',
      filterType: 'text',
      filter: v => textMatch(v, filters.name)
    },
    { width: 200, title: 'Light', align: 'end', key: 'light',
      filterType: 'text',
      filter: v => textMatch(v, filters.light)
    },
    { width: 200, title: 'Height', align: 'end', key: 'height',
      filterType: 'select',
      filter: v => selectMatch(v, filters.height)
    },
    { width: 200, title: 'Pet Friendly', align: 'end', key: 'petFriendly',
      filterType: 'select',
      filter: v => selectMatch(v, filters.petFriendly)
    },
    { width: 200, title: 'Price ($)', align: 'end', key: 'price',
      filterType: 'number',
      filter: v => numericMatch(v, filters.price),
    },
  ])
</script>

<style>
  @import 'https://fonts.bunny.net/css?family=recursive:300,400,500,700';
  body * {
    font-family: 'Recursive', sans-serif !important;
  }

  .v-data-table__td:not(:last-child) {
    border-right: 1px solid rgb(var(--v-border-color), var(--v-border-opacity));
  }
  tr:has(th) {
    background-color: rgb(var(--v-theme-surface)) !important;
  }
  th.v-data-table__td {
    background-color: #8882 !important;
  }
  th.v-data-table__td .v-field {
    --v-input-control-height: 24px;
    --v-field-input-padding-inline: 4px;
    --v-field-input-padding-top: 4px;
    --v-field-input-padding-bottom: 4px;
    --v-field-padding-start: 8px;
    --v-field-padding-end: 8px;
    font-size: 0.75rem;

    &.v-field--appended {
      padding-inline-end: 8px;
    }
  }
</style>

@J-Sek J-Sek self-assigned this Aug 4, 2025
@J-Sek J-Sek added T: bug Functionality that does not work as intended/expected E: filter labels Aug 4, 2025
@J-Sek J-Sek requested a review from a team August 24, 2025 08:53
johnleider
johnleider previously approved these changes Aug 24, 2025
@J-Sek J-Sek merged commit af20234 into vuetifyjs:master Aug 30, 2025
5 checks passed
@J-Sek J-Sek deleted the fix/vdatatable-filter branch August 30, 2025 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E: filter T: bug Functionality that does not work as intended/expected
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants