---
title: "SigmaSearchControl"
description: "带防抖、键盘导航与命中高亮的检索框，四个插槽可逐层接管外观，作用域连行为一起给。"
canonical_url: "https://sigma.mhaibaraai.cn/docs/components/search-control"
---
# SigmaSearchControl

> 带防抖、键盘导航与命中高亮的检索框，四个插槽可逐层接管外观，作用域连行为一起给。

## 用法

输入即时回显，检索按防抖触发，命中片段自带高亮，选中后相机聚焦过去。默认外观已经接好键盘：上下键移动高亮项（越界回绕）、回车选中（未高亮时取第一条）、Esc 清空。

> [!WARNING]
> 
> 默认外观没有把高亮项滚动进可视区，也没有接 
> 
> aria-activedescendant
> 
> 。结果条数多且对无障碍有要求时，用 
> 
> #input
> 
>  与 
> 
> #results
> 
>  自行补齐。

### `fields`

参与匹配的属性名，按顺序取第一个命中的作展示。下面这份数据集的节点带 `label` 与 `category` 两个可检索字段：

```vue [SearchControlFieldsExample.vue]
<script setup lang="ts">
defineProps<{ fields: string[] }>()

const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl
        :key="`fields-${fields.join()}`"
        :fields="fields"
        placeholder="试试 Javert"
      />
    </SigmaControls>
  </SigmaGraph>
</template>
```

### `edges`

是否同时检索边。边命中后经 `graph.source(edge)` 定位，相机聚焦到边的**源节点**。匹配只认字符串属性，所以边上得有可匹配的字段——这份小数据集的边标签是「源-目标」，输入 `B` 关掉时只出节点 B，勾上之后 `A-B`、`B-C`、`B-D`、`C-B` 四条边一起出。

```vue [SearchControlEdgesExample.vue]
<script setup lang="ts">
import type { Settings } from 'sigma/settings'
import type { StylesDeclaration } from 'sigma/types'

defineProps<{ edges: boolean }>()

const { data } = await useFetch('/api/small.json')

const styles: StylesDeclaration = {
  edges: { labelVisibility: 'visible' }
}

const settings: Partial<Settings> = {
  renderEdgeLabels: true,
  itemSizesReference: 'positions',
  autoRescale: true
}
</script>

<template>
  <SigmaGraph :data="data" :styles="styles" :settings="settings">
    <SigmaControls position="top-right">
      <SigmaSearchControl
        :key="`edges-${edges}`"
        :edges="edges"
        placeholder="试试 B"
      />
    </SigmaControls>
  </SigmaGraph>
</template>
```

### `limit`

结果条数上限，超出的直接截断：

```vue [SearchControlLimitExample.vue]
<script setup lang="ts">
defineProps<{ limit: number }>()

const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl
        :key="limit"
        :limit="Number(limit)"
        placeholder="试试单个字母 a"
      />
    </SigmaControls>
  </SigmaGraph>
</template>
```

### `debounce`

输入到发起检索的间隔，单位毫秒。输入框的值即时更新用于显示，真正参与匹配的词是防抖之后的——万级节点上每次按键都全量扫一遍会把输入卡住。

结果列表的展开与否看的是**即时值**（非空即展开），不是防抖后的词，所以输入的一瞬间列表就出现，内容稍后填上。

```vue [SearchControlDebounceExample.vue]
<script setup lang="ts">
defineProps<{ debounce: number }>()

const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl
        :key="debounce"
        :debounce="Number(debounce)"
        placeholder="试试单个字母 a"
      />
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!WARNING]
> 
> fields
> 
>  / 
> 
> limit
> 
>  / 
> 
> edges
> 
>  在 setup 时读一次就交给了 
> 
> useSigmaSearch()
> 
> ，挂载后改这三个 prop 不会重新配置检索。它们通常是静态的，需要动态切换请自己用 composable 接（上面两个示例是靠换 
> 
> key
> 
>  强制重建才做到即时生效的）。

### `placeholder`

输入框占位文本：

```vue [SearchControlTextExample.vue]
<script setup lang="ts">
defineProps<{ placeholder: string }>()

const { data } = await useFetch('/api/small.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl :placeholder="placeholder" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

### `emptyText`

有检索词但没有结果时的提示文本，输入一个匹配不到的词即可看到。

```vue [SearchControlEmptyTextExample.vue]
<script setup lang="ts">
defineProps<{ emptyText: string }>()

const { data } = await useFetch('/api/small.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl :empty-text="emptyText" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

## 示例

### `select` 事件

在相机动画**结束后**才抛出，携带完整的结果对象：

```vue [SearchControlSelectExample.vue]
<script setup lang="ts">
import type { SigmaSearchResult } from '@movk/sigma'

const picked = shallowRef<SigmaSearchResult | null>(null)

const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl :fields="['label', 'category']" @select="picked = $event" />
    </SigmaControls>

    <SigmaControls position="bottom-left" direction="horizontal">
      <UBadge color="neutral" variant="subtle" :label="`命中类型 ${picked?.type ?? '—'}`" />
      <UBadge color="neutral" variant="subtle" :label="`id ${picked?.id ?? '—'}`" />
      <UBadge color="neutral" variant="subtle" :label="`字段 ${picked?.field ?? '—'}`" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

### 接管输入框与结果项

作用域连行为一起给——只暴露数据的话，接管外观就等于丢掉键盘导航与聚焦。`#input` 的 `onKeydown` 一次绑完全部按键，也可以只用 `move` / `confirm` / `clear` 自定义按键映射；`#option` 的 `segments` 是已经按当前检索词切好的高亮片段；`#empty` 没有作用域：

```vue [SearchControlSlotExample.vue]
<script setup lang="ts">
const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl :fields="['label', 'category']">
        <template #input="{ modelValue, placeholder, open, onUpdate, onKeydown }">
          <UInput
            :model-value="modelValue"
            :placeholder="placeholder"
            :aria-expanded="open"
            role="combobox"
            icon="i-lucide-search"
            size="sm"
            @update:model-value="onUpdate($event as string)"
            @keydown="onKeydown"
          />
        </template>

        <template #option="{ result, segments }">
          <span class="flex w-full items-center gap-2">
            <span class="truncate">
              <span
                v-for="(segment, index) in segments"
                :key="index"
                :class="segment.match && 'text-primary font-medium'"
              >{{ segment.text }}</span>
            </span>
            <UBadge color="neutral" variant="subtle" size="sm" :label="result.field" class="ml-auto" />
          </span>
        </template>

        <template #empty>
          <span class="text-muted">换个词试试</span>
        </template>
      </SigmaSearchControl>
    </SigmaControls>
  </SigmaGraph>
</template>
```

#### `#input`

**modelValue** (`string`): 输入框即时值，不是防抖后参与匹配的词。

**placeholder** (`string`): 透传自 placeholder prop。

**open** (`boolean`): 结果列表是否展开，绑给 aria-expanded。

**activeIndex** (`number`): 当前键盘高亮项下标，未选中为 -1。

**onUpdate()** (`(value: string) => void`): 写回输入值。

**onKeydown()** (`(event: KeyboardEvent) => void`): 上下键、回车、Esc 的完整处理。

**move()** (`(delta: number) => void`): 按增量移动高亮项，越界回绕。

**confirm()** (`() => void`): 选中当前高亮项，未高亮时取第一条。

**clear()** (`() => void`): 清空输入与高亮。

#### `#option`

**result** (`SigmaSearchResult`): 该条结果。

**segments** (`HighlightSegment[]`): 已切好的命中片段。HighlightSegment 是 { text: string, match: boolean }，来自 @movk/core。

### 接管整个下拉

`#results` 接管的是**整个下拉容器**：

```vue [SearchControlResultsExample.vue]
<script setup lang="ts">
const { data } = await useFetch('/api/data.json')
</script>

<template>
  <SigmaGraph :data="data">
    <SigmaControls position="top-right">
      <SigmaSearchControl :fields="['label', 'category']" :limit="5">
        <template #results="{ results, query, highlight, choose }">
          <div class="absolute z-10 mt-1 w-full overflow-hidden rounded-md border border-accented bg-default shadow-lg">
            <p v-if="results.length === 0" class="p-2 text-xs text-muted">
              「{{ query }}」无匹配
            </p>
            <UButton
              v-for="result in results"
              :key="result.id"
              variant="ghost"
              color="neutral"
              size="xs"
              class="w-full rounded-none"
              @click="choose(result)"
            >
              <span
                v-for="(segment, index) in highlight(result)"
                :key="index"
                :class="segment.match && 'text-primary font-medium'"
              >{{ segment.text }}</span>
            </UButton>
          </div>
        </template>
      </SigmaSearchControl>
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!WARNING]
> 
> 一旦接管，
> 
> #option
> 
>  与 
> 
> #empty
> 
>  不再渲染，
> 
> .sigma-search-results
> 
>  的绝对定位、滚动上限与「停靠在底部时向上展开」的规则也一并失效，都得自己处理。开发环境下同时提供 
> 
> #results
> 
>  与 
> 
> #option
> 
>  / 
> 
> #empty
> 
>  会有一条控制台告警。

**results** (`SigmaSearchResult[]`): 当前检索结果。

**activeIndex** (`number`): 当前键盘高亮项下标，未选中为 -1。

**query** (`string`): 防抖后真正参与匹配的词。

**highlight()** (`(result: SigmaSearchResult) => HighlightSegment[]`): 按当前 query 切出命中片段。

**choose()** (`(result: SigmaSearchResult) => Promise<void>`): 聚焦到该结果、抛出 select 事件并清空输入。

类型从根出口取：

```ts
import type { SigmaSearchResult } from '@movk/sigma'
```

## API

### Props

```ts
/**
 * Props for the SigmaSearchControl component
 */
interface SigmaSearchControlProps {
  /**
   * 参与匹配的属性名
   * @default ["label"]
   */
  fields?: string[] | undefined;
  /**
   * 结果条数上限
   * @default 10
   */
  limit?: number | undefined;
  /**
   * 是否同时检索边
   * @default false
   */
  edges?: boolean | undefined;
  /**
   * 输入到发起检索的防抖间隔，单位毫秒
   * @default 200
   */
  debounce?: number | undefined;
  /**
   * 输入框占位文本
   * @default '检索节点'
   */
  placeholder?: string | undefined;
  /**
   * 无结果时的提示文本
   * @default '无匹配'
   */
  emptyText?: string | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the SigmaSearchControl component
 */
interface SigmaSearchControlEmits {
  select: (payload: [result: SigmaSearchResult]) => void;
}
```

### Slots

```ts
/**
 * Slots for the SigmaSearchControl component
 */
interface SigmaSearchControlSlots {
  /**
   * 接管输入框。作用域连行为一起给：`onKeydown` 一次绑完上下键、回车与 Esc，
   * `move` / `confirm` / `clear` 供自定义按键映射时使用。
   */
  input(): any;
  /**
   * 接管整个结果下拉容器。接管后 `#option` 与 `#empty` 不再渲染，
   * `.sigma-search-results` 的绝对定位、滚动与向上展开规则一并失效，需自行处理。
   */
  results(): any;
  /**
   * 接管单条结果的内容，`segments` 是已切好的命中片段
   */
  option(): any;
  /**
   * 接管无结果时的提示内容
   */
  empty(): any;
}
```


## Sitemap

See the full [sitemap](https://sigma.mhaibaraai.cn/sitemap.md) for all pages.
