# 选项单选组件

一个单选下拉组件，允许用户从预定义列表中选择一个选项，支持自定义样式和行为。

## 功能

- **单选**：从下拉列表中选择一个选项
- **自定义字段映射**：配置用于键和显示文本的对象属性
- **选项控制**：隐藏或禁用特定选项
- **自定义渲染**：使用自定义组件渲染下拉项
- **必填模式**：防止清除已选值
- **快速过滤**：内置搜索/过滤功能，适用于大量选项
- **焦点管理**：适当的焦点处理和键盘导航
- **显示模式**：支持编辑和查看模式
- **空状态处理**：可自定义的占位符和空文本
- **事件回调**：支持选择、聚焦和失焦等丰富的事件处理

## 安装

```bash
npm install @ticatec/uniface-element
```

## 基本用法

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let selectedValue = null;
  let options = [
    { code: 'JS', text: 'JavaScript' },
    { code: 'TS', text: 'TypeScript' },
    { code: 'PY', text: 'Python' }
  ];
</script>

<OptionsSelect 
  {options} 
  bind:value={selectedValue}
/>
```

## API 参考

### 属性

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `value` | `any` | `null` | 选中的选项键/值 |
| `options` | `Array<any>` | `[]` | 选项对象数组 |
| `keyField` | `string` | `'code'` | 选项键的属性名 |
| `textField` | `string` | `'text'` | 选项显示文本的属性名 |
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 视觉样式变体 |
| `compact` | `boolean` | `false` | 启用紧凑显示模式 |
| `disabled` | `boolean` | `false` | 禁用组件 |
| `readonly` | `boolean` | `false` | 使组件只读 |
| `mandatory` | `boolean` | `false` | 禁止清除已选值 |
| `style` | `string` | `''` | 自定义 CSS 样式 |
| `placeholder` | `string` | `''` | 未选择选项时的占位符文本 |
| `emptyText` | `string` | `null` | 未选择选项时显示的文本（覆盖占位符） |
| `disableOptions` | `Array<string>` | `[]` | 要禁用的选项键 |
| `hideOptions` | `Array<string>` | `[]` | 要隐藏的选项键 |
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 编辑或查看模式 |
| `menu$height` | `number` | `0` | 下拉菜单的最大高度 |
| `itemRender` | `any` | `null` | 用于渲染选项的自定义组件 |
| `quickFilter` | `boolean` | `false` | 启用快速过滤/搜索 |
| `quickFilterPlaceholder` | `string` | `'Search...'` | 搜索输入框的占位符文本 |
| `filterFunction` | `(option: any, keyword: string) => boolean` | `null` | 自定义过滤函数 |
| `noResultsText` | `string` | `'No results found'` | 无匹配结果时显示的文本 |
| `onchange` | `OnChangeHandler<any>` | `null` | 更改事件处理程序 |
| `onfocus` | `(() => void) \| null` | `null` | 聚焦事件处理程序 |
| `onblur` | `(() => void) \| null` | `null` | 失焦事件处理程序 |
| `onSelected` | `OnSelectedHandler` | `null` | 选择事件处理程序，包含完整选项数据 |

### 方法

| 方法 | 描述 |
|--------|-------------|
| `setFocus()` | 以编程方式聚焦组件 |

## 示例

### 货币选择器

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  import FormField from '@ticatec/uniface-element/FormField';
  
  let selectedCurrency = 'USD';
  let currencies = [
    { code: 'USD', text: '美元' },
    { code: 'EUR', text: '欧元' },
    { code: 'GBP', text: '英镑' },
    { code: 'JPY', text: '日元' },
    { code: 'CNY', text: '人民币' }
  ];
  
  function handleCurrencyChange(currency) {
    console.log('货币更改为：', currency);
  }
</script>

<FormField label="货币">
  <OptionsSelect 
    options={currencies}
    bind:value={selectedCurrency}
    variant="outlined"
    placeholder="选择货币"
    onchange={handleCurrencyChange}
  />
</FormField>
```

### 国家选择器（自定义字段）

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let selectedCountry = null;
  let countries = [
    { id: 'US', name: '美国' },
    { id: 'CA', name: '加拿大' },
    { id: 'UK', name: '英国' },
    { id: 'DE', name: '德国' },
    { id: 'JP', name: '日本' }
  ];
  
  function handleCountrySelection(countryItem) {
    console.log('选择的国家对象：', countryItem);
  }
</script>

<OptionsSelect 
  options={countries}
  keyField="id"
  textField="name"
  bind:value={selectedCountry}
  variant="filled"
  placeholder="选择你的国家"
  onSelected={handleCountrySelection}
/>
```

### 状态选择器（带禁用选项）

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let taskStatus = 'PENDING';
  let statusOptions = [
    { code: 'PENDING', text: '待处理' },
    { code: 'IN_PROGRESS', text: '进行中' },
    { code: 'REVIEW', text: '审核中' },
    { code: 'COMPLETED', text: '已完成' },
    { code: 'CANCELLED', text: '已取消' },
    { code: 'ARCHIVED', text: '已归档' }
  ];
  
  let disabledStatuses = ['ARCHIVED']; // 归档状态不可用
  let hiddenStatuses = ['CANCELLED']; // 隐藏取消选项
</script>

<div class="status-selector">
  <label>任务状态</label>
  <OptionsSelect 
    options={statusOptions}
    bind:value={taskStatus}
    variant="outlined"
    disableOptions={disabledStatuses}
    hideOptions={hiddenStatuses}
    mandatory
    emptyText="未设置状态"
  />
</div>
```

### 语言偏好

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let preferredLanguage = null;
  let languages = [
    { locale: 'en', display: '英语' },
    { locale: 'es', display: '西班牙语' },
    { locale: 'fr', display: '法语' },
    { locale: 'de', display: '德语' },
    { locale: 'zh', display: '中文' },
    { locale: 'ja', display: '日语' }
  ];
  
  function handleLanguageChange(locale) {
    console.log('语言更改为：', locale);
    // 更新应用语言
    updateAppLanguage(locale);
  }
  
  function handleFocus() {
    console.log('语言选择器聚焦');
  }
  
  function handleBlur() {
    console.log('语言选择器失焦');
  }
</script>

<div class="language-settings">
  <OptionsSelect 
    options={languages}
    keyField="locale"
    textField="display"
    bind:value={preferredLanguage}
    variant="filled"
    placeholder="选择你的语言"
    onchange={handleLanguageChange}
    onfocus={handleFocus}
    onblur={handleBlur}
  />
</div>
```

### 优先级选择器（自定义样式）

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  import PriorityItem from './PriorityItem.svelte'; // 自定义组件
  
  let priority = 'MEDIUM';
  let priorities = [
    { level: 'LOW', label: '低优先级', color: '#4caf50' },
    { level: 'MEDIUM', label: '中优先级', color: '#ff9800' },
    { level: 'HIGH', label: '高优先级', color: '#f44336' },
    { level: 'URGENT', label: '紧急', color: '#9c27b0' }
  ];
  
  const priorityRenderer = {
    component: PriorityItem,
    props: {
      showIcon: true
    }
  };
</script>

<div class="priority-selector">
  <label>任务优先级</label>
  <OptionsSelect 
    options={priorities}
    keyField="level"
    textField="label"
    bind:value={priority}
    variant="outlined"
    itemRender={priorityRenderer}
    mandatory
  />
</div>
```

### 主题选择器

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let currentTheme = 'light';
  let themes = [
    { code: 'light', text: '浅色主题' },
    { code: 'dark', text: '深色主题' },
    { code: 'auto', text: '系统默认' },
    { code: 'high-contrast', text: '高对比度' }
  ];
  
  function applyTheme(themeCode) {
    console.log('应用主题：', themeCode);
    document.documentElement.setAttribute('data-theme', themeCode);
  }
</script>

<div class="theme-settings">
  <h3>外观</h3>
  <OptionsSelect 
    options={themes}
    bind:value={currentTheme}
    variant="outlined"
    mandatory
    onchange={applyTheme}
    style="width: 200px;"
  />
</div>
```

### 带多个选择器的表单

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  import FormField from '@ticatec/uniface-element/FormField';
  
  let userProfile = {
    gender: null,
    country: null,
    timezone: null,
    currency: 'USD'
  };
  
  let genders = [
    { code: 'M', text: '男性' },
    { code: 'F', text: '女性' },
    { code: 'O', text: '其他' },
    { code: 'N', text: '不愿透露' }
  ];
  
  let countries = [
    { id: 'US', name: '美国' },
    { id: 'CA', name: '加拿大' },
    { id: 'UK', name: '英国' },
    { id: 'AU', name: '澳大利亚' }
  ];
  
  let timezones = [
    { code: 'UTC', text: '协调世界时' },
    { code: 'EST', text: '东部时间' },
    { code: 'PST', text: '太平洋时间' },
    { code: 'GMT', text: '格林尼治标准时间' }
  ];
  
  let currencies = [
    { code: 'USD', text: '美元' },
    { code: 'EUR', text: '欧元' },
    { code: 'GBP', text: '英镑' },
    { code: 'CAD', text: '加拿大元' }
  ];
  
  function saveProfile() {
    console.log('保存用户信息：', userProfile);
  }
</script>

<div class="profile-form">
  <h3>用户信息</h3>
  
  <FormField label="性别">
    <OptionsSelect 
      options={genders}
      bind:value={userProfile.gender}
      variant="outlined"
      placeholder="选择性别"
    />
  </FormField>
  
  <FormField label="国家">
    <OptionsSelect 
      options={countries}
      keyField="id"
      textField="name"
      bind:value={userProfile.country}
      variant="outlined"
      placeholder="选择国家"
      mandatory
    />
  </FormField>
  
  <FormField label="时区">
    <OptionsSelect 
      options={timezones}
      bind:value={userProfile.timezone}
      variant="outlined"
      placeholder="选择时区"
    />
  </FormField>
  
  <FormField label="货币">
    <OptionsSelect 
      options={currencies}
      bind:value={userProfile.currency}
      variant="outlined"
      mandatory
    />
  </FormField>
  
  <button on:click={saveProfile}>保存用户信息</button>
</div>
```

### 紧凑表格用法

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let statusOptions = [
    { code: 'ACTIVE', text: '活跃' },
    { code: 'INACTIVE', text: '非活跃' },
    { code: 'PENDING', text: '待处理' }
  ];
  
  let users = [
    { id: 1, name: 'John Doe', status: 'ACTIVE' },
    { id: 2, name: 'Jane Smith', status: 'INACTIVE' },
    { id: 3, name: 'Bob Wilson', status: 'PENDING' }
  ];
  
  function updateUserStatus(userId, newStatus) {
    const user = users.find(u => u.id === userId);
    if (user) {
      user.status = newStatus;
      users = [...users];
      console.log(`用户 ${user.name} 的状态更新为 ${newStatus}`);
    }
  }
</script>

<table class="user-table">
  <thead>
    <tr>
      <th>姓名</th>
      <th>状态</th>
    </tr>
  </thead>
  <tbody>
    {#each users as user}
      <tr>
        <td>{user.name}</td>
        <td>
          <OptionsSelect 
            options={statusOptions}
            value={user.status}
            compact
            variant="outlined"
            onchange={(status) => updateUserStatus(user.id, status)}
          />
        </td>
      </tr>
    {/each}
  </tbody>
</table>
```

### 快速过滤（大量选项）

当选项很多时，可以启用内置的快速过滤功能：

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';

  let selectedCountry = null;
  let countries = [
    { code: 'US', name: '美国' },
    { code: 'CA', name: '加拿大' },
    { code: 'UK', name: '英国' },
    { code: 'DE', name: '德国' },
    { code: 'FR', name: '法国' },
    { code: 'JP', name: '日本' },
    { code: 'CN', name: '中国' },
    { code: 'AU', name: '澳大利亚' },
    // ... 更多国家
  ];
</script>

<OptionsSelect
  options={countries}
  keyField="code"
  textField="name"
  bind:value={selectedCountry}
  variant="outlined"
  placeholder="选择国家"
  quickFilter={true}
  quickFilterPlaceholder="输入搜索国家..."
/>
```

### 自定义过滤函数

提供自定义过滤函数以实现高级过滤：

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';

  let selectedProduct = null;
  let products = [
    { sku: 'LAPTOP-001', name: '笔记本电脑', category: '电子产品' },
    { sku: 'PHONE-002', name: '智能手机', category: '电子产品' },
    { sku: 'SHIRT-003', name: 'T恤', category: '服装' },
    { sku: 'PANTS-004', name: '牛仔裤', category: '服装' }
  ];

  const productFilter = (option, keyword) => {
    if (!keyword) return true;
    const search = keyword.toLowerCase();
    return option.name.toLowerCase().includes(search) ||
           option.sku.toLowerCase().includes(search) ||
           option.category.toLowerCase().includes(search);
  };
</script>

<OptionsSelect
  options={products}
  keyField="sku"
  textField="name"
  bind:value={selectedProduct}
  quickFilter={true}
  filterFunction={productFilter}
  quickFilterPlaceholder="搜索产品..."
  noResultsText="未找到匹配的产品"
/>
```

### 动态选项加载

```svelte
<script>
  import OptionsSelect from '@ticatec/uniface-element/OptionsSelect';
  
  let selectedCategory = null;
  let selectedProduct = null;
  let categories = [
    { id: 'electronics', name: '电子产品' },
    { id: 'clothing', name: '服装' },
    { id: 'books', name: '书籍' }
  ];
  let products = [];
  
  async function loadProducts(categoryId) {
    if (!categoryId) {
      products = [];
      selectedProduct = null;
      return;
    }
    
    // 模拟 API 调用
    const productData = {
      'electronics': [
        { sku: 'E001', name: '笔记本电脑' },
        { sku: 'E002', name: '智能手机' }
      ],
      'clothing': [
        { sku: 'C001', name: 'T恤' },
        { sku: 'C002', name: '牛仔裤' }
      ],
      'books': [
        { sku: 'B001', name: '编程指南' },
        { sku: 'B002', name: '设计模式' }
      ]
    };
    
    products = productData[categoryId] || [];
    selectedProduct = null;
  }
  
  $: loadProducts(selectedCategory);
</script>

<div class="product-selector">
  <div class="selector-row">
    <label>类别</label>
    <OptionsSelect 
      options={categories}
      keyField="id"
      textField="name"
      bind:value={selectedCategory}
      variant="outlined"
      placeholder="选择类别"
    />
  </div>
  
  <div class="selector-row">
    <label>产品</label>
    <OptionsSelect 
      options={products}
      keyField="sku"
      textField="name"
      bind:value={selectedProduct}
      variant="outlined"
      placeholder={selectedCategory ? '选择产品' : '请先选择类别'}
      disabled={!selectedCategory}
    />
  </div>
</div>
```

## 自定义选项渲染

你可以提供一个自定义组件来渲染下拉选项：

```svelte
<!-- CustomItemRenderer.svelte -->
<script>
  export let item;
  export let disabled = false;
  
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
  
  function handleClick() {
    if (!disabled) {
      dispatch('click');
    }
  }
</script>

<div class="custom-item" class:disabled on:click={handleClick}>
  <div class="item-icon">📌</div>
  <div class="item-content">
    <div class="item-title">{item.title}</div>
    <div class="item-description">{item.description}</div>
  </div>
</div>

<style>
  .custom-item {
    display: flex;
    align-items: center;
    padding: 12px;
    cursor: pointer;
    border-bottom: 1px solid #eee;
  }
  
  .custom-item:hover:not(.disabled) {
    background-color: #f5f5f5;
  }
  
  .custom-item.disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  
  .item-icon {
    margin-right: 12px;
    font-size: 18px;
  }
  
  .item-title {
    font-weight: 600;
    margin-bottom: 4px;
  }
  
  .item-description {
    font-size: 12px;
    color: #666;
  }
</style>
```

## 最佳实践

1. **选项管理**：保持选项列表专注且相关
2. **性能**：对于超大选项列表，考虑虚拟化
3. **可访问性**：提供清晰的标签和有意义的选项文本
4. **用户体验**：选择适合你设计的变体和尺寸
5. **数据验证**：处理选项缺失等边缘情况
6. **加载状态**：为动态选项显示加载指示器
7. **错误处理**：优雅处理动态选项的 API 失败

## 样式

```css
.options-popover {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.option-item {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
}

.option-item:hover:not(.disabled) {
  background-color: #f5f5f5;
}

.option-item.disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background-color: #fafafa;
}

.option-item:last-child {
  border-bottom: none;
}
```

## 可访问性

- 全面支持键盘导航（箭头键、回车、Esc键）
- 为屏幕阅读器提供 ARIA 标签和角色
- 焦点管理和视觉指示器
- 高对比度模式兼容性
- 适当的表单关联标签

## 相关组件

- [选项多选](../options-multi-select/README.md) - 用于多选选项
- [表单字段](../form-field/README.md) - 用于表单布局和标签
- [通用选择器](../common/README.md) - 基础选择器功能
- [查找编辑器](../lookup-editor/README.md) - 用于动态数据查找