# useTableFiltering

## 使用方法

给表格的列加过滤器，控件可以是行内的，也可以在弹出层里。列的 `filter` 指向 `fields` 里的某个字段时，这一列才有过滤器。`inline` 变体把控件放在表头单元格里、列名下面；`inline-compact` 是同一个位置，但控件更紧凑，表格窄的时候用；`popover` 变体把它藏在一个漏斗按钮后面，确认后才生效。状态交给 `useTableFilterState` 管。

```ts
import { useTableFiltering, useTableFilterState } from '@astryx-vue/core/Table'
```

支持五种字段类型，每种都有对应的控件：

| `type` | 控件 | 命中条件 |
| --- | --- | --- |
| `string` | 文本框 | 单元格文字包含输入的子串 |
| `enum` | 下拉 | 正好是某个选项 |
| `multi-select` | 多选列表 | 是勾选中的任意一个选项 |
| `number` | 数字框 | 数值相等 |
| `date` | 日期选择 | 单元格日期比选的更晚 |

```ts
const fields: TableFilterField[] = [
  { key: 'name', label: 'Name', type: 'string' },
  { key: 'role', label: 'Role', type: 'enum', options: roleOptions },
  { key: 'teams', label: 'Teams', type: 'multi-select', options: teamOptions },
  { key: 'age', label: 'Age', type: 'number', min: 18, max: 70, isIntegerOnly: true },
  { key: 'hired', label: 'Hired', type: 'date' },
]
```

`applyTableFilters()` 返回同时满足所有生效过滤的行，插件自己不碰 `data`。

```ts
const { filters, onFilterChange } = useTableFilterState()
const filterPlugin = useTableFiltering(() => ({
  filters: filters.value,
  onFilterChange,
  fields,
  variant: 'inline',
}))
const rows = computed(() => applyTableFilters(employees, filters.value, fields))
```

每次读配置都会重新取值，所以过滤值存在 `ref` 里时要传 getter。直接传对象也能跑，但表格会用第一次读到的过滤值。

行内变体里，带过滤但没有写 `width` 的列会被补成 `proportional(1)`，控件在列名下面放得开。

这套过滤和 Power Search 是分开的。没有 `filter` 的列永远不会被过滤，插件对它什么也不画。

## 示例

### Table — 行内过滤

在每个表头下面直接放一行过滤控件的表格。

```vue
<script setup lang="ts">
import type { TableColumn, TableFilterField } from '@astryx-vue/core/Table'
import { applyTableFilters, proportional, Table, useTableFiltering, useTableFilterState } from '@astryx-vue/core/Table'
import { computed } from 'vue'

interface Employee extends Record<string, unknown> {
  id: string
  name: string
  email: string
  role: string
  department: string
}

const employees: Employee[] = [
  { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer', department: 'Platform' },
  { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'Designer', department: 'Product' },
  { id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Manager', department: 'Platform' },
  { id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Engineer', department: 'Infra' },
  { id: '5', name: 'Eve Davis', email: 'eve@example.com', role: 'Admin', department: 'Operations' },
]

const fields: TableFilterField[] = [
  { key: 'name', type: 'string', label: 'Name' },
  { key: 'email', type: 'string', label: 'Email' },
  {
    key: 'role',
    type: 'enum',
    label: 'Role',
    options: [
      { value: 'Engineer', label: 'Engineer' },
      { value: 'Designer', label: 'Designer' },
      { value: 'Manager', label: 'Manager' },
      { value: 'Admin', label: 'Admin' },
    ],
  },
]

const columns: TableColumn<Employee>[] = [
  { key: 'name', header: 'Name', width: proportional(1), filter: 'name' },
  { key: 'email', header: 'Email', width: proportional(2), filter: 'email' },
  { key: 'role', header: 'Role', width: proportional(1), filter: 'role' },
  { key: 'department', header: 'Department', width: proportional(1) },
]

const { filters, onFilterChange } = useTableFilterState()
const filterPlugin = useTableFiltering<Employee>(() => ({
  filters: filters.value,
  onFilterChange,
  fields,
  variant: 'inline',
}))
const rows = computed(() => applyTableFilters(employees, filters.value, fields))
</script>

<template>
  <Table
    :data="rows"
    :columns="columns"
    id-key="id"
    :plugins="{ filter: filterPlugin }"
  />
</template>
```

### Table — 弹出过滤

点表头上的图标，在弹出层里过滤的表格。

```vue
<script setup lang="ts">
import type { TableColumn, TableFilterField } from '@astryx-vue/core/Table'
import { applyTableFilters, proportional, Table, useTableFiltering, useTableFilterState } from '@astryx-vue/core/Table'
import { computed } from 'vue'

interface Employee extends Record<string, unknown> {
  id: string
  name: string
  email: string
  role: string
  department: string
}

const employees: Employee[] = [
  { id: '1', name: 'Alice', email: 'alice@example.com', role: 'Engineer', department: 'Platform' },
  { id: '2', name: 'Bob', email: 'bob@example.com', role: 'Designer', department: 'Product' },
  { id: '3', name: 'Charlie', email: 'charlie@example.com', role: 'Manager', department: 'Platform' },
  { id: '4', name: 'Diana', email: 'diana@example.com', role: 'Engineer', department: 'Infrastructure' },
  { id: '5', name: 'Eve', email: 'eve@example.com', role: 'Admin', department: 'Operations' },
]

const fields: TableFilterField[] = [
  { key: 'name', type: 'string', label: 'Name' },
  { key: 'email', type: 'string', label: 'Email' },
  {
    key: 'role',
    type: 'enum',
    label: 'Role',
    options: [
      { value: 'Engineer', label: 'Engineer' },
      { value: 'Designer', label: 'Designer' },
      { value: 'Manager', label: 'Manager' },
      { value: 'Admin', label: 'Admin' },
    ],
  },
]

const columns: TableColumn<Employee>[] = [
  { key: 'name', header: 'Name', width: proportional(1), filter: 'name' },
  { key: 'email', header: 'Email', width: proportional(2), filter: 'email' },
  { key: 'role', header: 'Role', width: proportional(1), filter: 'role' },
  { key: 'department', header: 'Department', width: proportional(1) },
]

const { filters, onFilterChange } = useTableFilterState()
const filterPlugin = useTableFiltering<Employee>(() => ({
  filters: filters.value,
  onFilterChange,
  fields,
}))
const rows = computed(() => applyTableFilters(employees, filters.value, fields))
</script>

<template>
  <Table
    :data="rows"
    :columns="columns"
    id-key="id"
    :plugins="{ filter: filterPlugin }"
  />
</template>
```

## 参数

`useTableFiltering()` 只接一个配置对象。过滤值存在 `ref` 里时用 getter 包一层，表格每次读都能拿到最新的值：`useTableFiltering(() => ({ ... }))`。

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `filters` | `TableFilterState` | — | 必填。当前的过滤状态：字段 key 到过滤值的映射。 |
| `onFilterChange` | `(key: string, value: TableFilterValue \| null) => void` | — | 必填。用户改过滤值时调用。`null` 表示清掉这个过滤。 |
| `fields` | `readonly TableFilterField[]` | — | 必填。列可以过滤的字段。每一项有 `key`、`label`、`type`，以及这种类型需要的选项或范围。 |
| `variant` | `'inline' \| 'inline-compact' \| 'popover'` | `'popover'` | 控件放在哪。`inline` 和 `inline-compact` 放在表头单元格里、列名下面，后者更紧凑；`popover` 藏在漏斗按钮后面，确认后才生效。 |

## 返回值

`useTableFiltering()` 返回一个 `TablePlugin&lt;T&gt;`，放进 `Table` 的 `plugins` 里。它填的是插件约定里的两个字段：

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `transformColumns` | `(columns: TableColumn[]) => TableColumn[]` | 只有行内变体才给：带过滤而且没写 `width` 的列补成 `proportional(1)`，控件在列名下面放得开。 |
| `filterFor` | `(column: TableColumn) => TableFilterContribution \| null` | 生成这一列的控件。列没有 `filter`，或者它指的字段不在 `fields` 里时返回 `null`。 |

### TableFilterContribution

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `variant` | `'inline' \| 'inline-compact' \| 'popover'` | 补完默认值之后的摆放方式。 |
| `field` | `TableFilterField` | 列指到的字段，来自 `fields`。 |
| `value` | `TableFilterValue \| null` | 当前值，来自 `filters`。这个字段还没有过滤时是 `null`。 |
| `onChange` | `(value: TableFilterValue \| null) => void` | 把新值交给你的 `onFilterChange`，字段 key 已经带上了。 |

### TableFilterField

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `key` | `string` | — | 字段 key。列的 `filter` 里写这个字符串，字段读的是 `row[key]`。 |
| `label` | `string` | — | 控件的标签和占位文字，也是读屏读到的名字。 |
| `type` | `'string' \| 'enum' \| 'multi-select' \| 'number' \| 'date'` | — | 命中方式。`string` 匹配子串，`enum` 匹配一个选项，`multi-select` 匹配勾选中的任意一项，`number` 比数值，`date` 要求单元格日期比选的更晚。 |
| `options` | `readonly { value: string, label: string }[]` | — | `enum` 和 `multi-select` 的选项。没有选项时列表是空的。 |
| `min` | `number` | — | `number` 字段能接受的最小值。 |
| `max` | `number` | — | `number` 字段能接受的最大值。 |
| `step` | `number` | `isIntegerOnly` 时是 `1`，否则不设 | `number` 字段的步进。 |
| `isIntegerOnly` | `boolean` | `false` | `number` 字段只接受整数。 |

### TableFilterValue

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `value` | `string \| number \| string[]` | `number` 字段存数字，`multi-select` 存选项值数组，其余存字符串。`date` 字段存 ISO 日期，例如 `2026-03-21`。 |
