# ContextMenu 右键菜单组件

一个功能强大且易于使用的右键菜单组件，支持完整的键盘导航和无障碍访问。非常适合创建丰富的交互体验和上下文操作。

## 功能特性

- **上下文操作**：右键点击显示相关菜单选项
- **键盘导航**：完整的方向键导航和键盘快捷键支持
- **无障碍访问**：ARIA 属性和屏幕阅读器支持
- **智能定位**：自动调整位置以保持在视口范围内
- **图标支持**：菜单项可选图标显示
- **禁用状态**：支持禁用菜单项
- **分隔符**：菜单组之间的视觉分隔
- **事件处理**：焦点、失焦和隐藏事件回调
- **Portal 渲染**：在组件层次结构之外渲染，确保正确的层级

## 基本用法

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

let contextMenu;

const menuItems = [
  { text: '复制', action: () => console.log('复制') },
  { text: '粘贴', action: () => console.log('粘贴') },
  { separator: true },
  { text: '删除', action: () => console.log('删除') }
];

const handleRightClick = (event) => {
  contextMenu.show(event, menuItems);
};
</script>

<div on:contextmenu={handleRightClick}>
  右键点击我显示上下文菜单
</div>

<ContextMenu bind:this={contextMenu} />
```

## 属性

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `onfocus` | `((event: FocusEvent) => void) \| null` | `null` | 焦点事件处理器 |
| `onblur` | `((event: FocusEvent) => void) \| null` | `null` | 失焦事件处理器 |
| `onhide` | `(() => void) \| null` | `null` | 隐藏事件处理器 |

## 方法

| 方法 | 签名 | 描述 |
|--------|-----------|-------------|
| `show(event, items, width?, height?)` | 在事件位置显示上下文菜单 |
| `hide()` | 隐藏上下文菜单 |

### show() 参数

- `event`: MouseEvent - 触发的鼠标事件（用于定位）
- `items`: ContextMenuItem[] - 要显示的菜单项数组
- `width`: number（可选，默认: 150）- 菜单宽度（像素）
- `height`: number（可选，默认: 150）- 菜单高度（像素）

## ContextMenuItem 接口

```typescript
interface ContextMenuItem {
  text: string;           // 菜单项显示文本
  action?: () => void;    // 点击时执行的操作
  disabled?: boolean;     // 是否禁用该项
  separator?: boolean;    // 是否为分隔符项
  icon?: string;         // 图标类名或名称
  style?: string;        // 自定义 CSS 样式
}
```

## 基础示例

### 简单右键菜单

```svelte
<script>
let contextMenu;

const items = [
  { text: '新建文件', action: () => createFile() },
  { text: '新建文件夹', action: () => createFolder() },
  { separator: true },
  { text: '刷新', action: () => refresh() }
];

const showMenu = (event) => {
  contextMenu.show(event, items);
};
</script>

<div class="file-explorer" on:contextmenu={showMenu}>
  <p>右键点击查看选项</p>
</div>

<ContextMenu bind:this={contextMenu} />
```

### 带图标和禁用项

```svelte
<script>
let contextMenu;
let canPaste = false;

const items = [
  { 
    text: '剪切', 
    icon: 'icon-cut',
    action: () => cut() 
  },
  { 
    text: '复制', 
    icon: 'icon-copy',
    action: () => copy() 
  },
  { 
    text: '粘贴', 
    icon: 'icon-paste',
    action: () => paste(),
    disabled: !canPaste 
  },
  { separator: true },
  { 
    text: '删除', 
    icon: 'icon-delete',
    action: () => deleteItem() 
  }
];
</script>

<div on:contextmenu={(e) => contextMenu.show(e, items)}>
  包含编辑操作的内容
</div>

<ContextMenu bind:this={contextMenu} />
```

### 动态菜单项

```svelte
<script>
let contextMenu;
let selectedItems = [];

const getMenuItems = (target) => {
  const baseItems = [
    { text: '属性', action: () => showProperties(target) }
  ];

  if (selectedItems.length > 0) {
    baseItems.unshift(
      { text: `删除 ${selectedItems.length} 个项目`, action: () => deleteSelected() },
      { separator: true }
    );
  }

  if (target.type === 'file') {
    baseItems.unshift(
      { text: '打开', action: () => openFile(target) },
      { text: '打开方式...', action: () => openWith(target) },
      { separator: true }
    );
  }

  return baseItems;
};

const handleContextMenu = (event, target) => {
  const items = getMenuItems(target);
  contextMenu.show(event, items);
};
</script>

<div class="item-list">
  {#each items as item}
    <div class="item" on:contextmenu={(e) => handleContextMenu(e, item)}>
      {item.name}
    </div>
  {/each}
</div>

<ContextMenu bind:this={contextMenu} />
```

## 事件处理

```svelte
<script>
let contextMenu;
let menuVisible = false;

const handleFocus = (event) => {
  console.log('右键菜单获得焦点');
  menuVisible = true;
};

const handleBlur = (event) => {
  console.log('右键菜单失去焦点');
  menuVisible = false;
};

const handleHide = () => {
  console.log('右键菜单隐藏');
  menuVisible = false;
};

const items = [
  { text: '操作 1', action: () => console.log('操作 1') },
  { text: '操作 2', action: () => console.log('操作 2') }
];
</script>

<div on:contextmenu={(e) => contextMenu.show(e, items)}>
  右键点击我
</div>

<ContextMenu 
  bind:this={contextMenu}
  onfocus={handleFocus}
  onblur={handleBlur}
  onhide={handleHide}
/>

<p>菜单可见: {menuVisible}</p>
```

## 高级示例

### 文件管理器右键菜单

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

let contextMenu;
let files = [
  { name: 'document.pdf', type: 'file', selected: false },
  { name: 'images', type: 'folder', selected: false },
  { name: 'script.js', type: 'file', selected: true }
];

let clipboard = null;

const getFileMenuItems = (file) => {
  const items = [];

  if (file.type === 'file') {
    items.push(
      { text: '打开', icon: 'icon-open', action: () => openFile(file) },
      { text: '打开方式...', action: () => openWith(file) },
      { separator: true }
    );
  }

  items.push(
    { text: '剪切', icon: 'icon-cut', action: () => cutFile(file) },
    { text: '复制', icon: 'icon-copy', action: () => copyFile(file) },
    { 
      text: '粘贴', 
      icon: 'icon-paste', 
      action: () => pasteFile(),
      disabled: !clipboard 
    },
    { separator: true },
    { text: '重命名', action: () => renameFile(file) },
    { text: '删除', icon: 'icon-delete', action: () => deleteFile(file) },
    { separator: true },
    { text: '属性', action: () => showProperties(file) }
  );

  return items;
};

const getEmptySpaceMenuItems = () => [
  { text: '新建文件夹', icon: 'icon-folder-plus', action: () => createFolder() },
  { text: '新建文件', icon: 'icon-file-plus', action: () => createFile() },
  { separator: true },
  { 
    text: '粘贴', 
    icon: 'icon-paste', 
    action: () => pasteFile(),
    disabled: !clipboard 
  },
  { separator: true },
  { text: '刷新', icon: 'icon-refresh', action: () => refresh() }
];

const handleFileContextMenu = (event, file) => {
  const items = getFileMenuItems(file);
  contextMenu.show(event, items, 200, 300);
};

const handleEmptySpaceContextMenu = (event) => {
  const items = getEmptySpaceMenuItems();
  contextMenu.show(event, items);
};

const openFile = (file) => console.log('打开', file.name);
const openWith = (file) => console.log('打开方式...', file.name);
const cutFile = (file) => { clipboard = { type: 'cut', file }; };
const copyFile = (file) => { clipboard = { type: 'copy', file }; };
const pasteFile = () => console.log('粘贴', clipboard?.file?.name);
const renameFile = (file) => console.log('重命名', file.name);
const deleteFile = (file) => console.log('删除', file.name);
const createFolder = () => console.log('创建文件夹');
const createFile = () => console.log('创建文件');
const refresh = () => console.log('刷新');
const showProperties = (file) => console.log('属性', file.name);
</script>

<div class="file-manager" on:contextmenu={handleEmptySpaceContextMenu}>
  <h3>文件管理器</h3>
  
  <div class="file-list">
    {#each files as file}
      <div 
        class="file-item" 
        class:selected={file.selected}
        on:contextmenu|stopPropagation={(e) => handleFileContextMenu(e, file)}
      >
        <span class="file-icon {file.type === 'folder' ? 'icon-folder' : 'icon-file'}"></span>
        <span class="file-name">{file.name}</span>
      </div>
    {/each}
  </div>
</div>

<ContextMenu bind:this={contextMenu} />

<style>
  .file-manager {
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
    min-height: 300px;
    background: #f9f9f9;
  }
  
  .file-list {
    margin-top: 16px;
  }
  
  .file-item {
    display: flex;
    align-items: center;
    padding: 8px;
    margin: 4px 0;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s;
  }
  
  .file-item:hover {
    background: #e9e9e9;
  }
  
  .file-item.selected {
    background: #007acc;
    color: white;
  }
  
  .file-icon {
    margin-right: 8px;
    width: 16px;
    height: 16px;
  }
</style>
```

### 表格行右键菜单

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

let contextMenu;
let users = [
  { id: 1, name: '张三', email: 'zhang@example.com', active: true },
  { id: 2, name: '李四', email: 'li@example.com', active: false },
  { id: 3, name: '王五', email: 'wang@example.com', active: true }
];

const getUserMenuItems = (user) => [
  { text: '查看资料', icon: 'icon-user', action: () => viewProfile(user) },
  { text: '编辑用户', icon: 'icon-edit', action: () => editUser(user) },
  { separator: true },
  { 
    text: user.active ? '停用' : '启用', 
    icon: user.active ? 'icon-pause' : 'icon-play',
    action: () => toggleUserStatus(user) 
  },
  { text: '重置密码', action: () => resetPassword(user) },
  { separator: true },
  { text: '删除用户', icon: 'icon-trash', action: () => deleteUser(user) }
];

const handleRowContextMenu = (event, user) => {
  const items = getUserMenuItems(user);
  contextMenu.show(event, items, 180, 200);
};

const viewProfile = (user) => console.log('查看资料', user.name);
const editUser = (user) => console.log('编辑', user.name);
const toggleUserStatus = (user) => {
  user.active = !user.active;
  users = [...users];
  console.log(`${user.active ? '启用' : '停用'} ${user.name}`);
};
const resetPassword = (user) => console.log('重置密码', user.name);
const deleteUser = (user) => {
  users = users.filter(u => u.id !== user.id);
  console.log('删除', user.name);
};
</script>

<div class="user-table">
  <h3>用户管理</h3>
  
  <table>
    <thead>
      <tr>
        <th>姓名</th>
        <th>邮箱</th>
        <th>状态</th>
      </tr>
    </thead>
    <tbody>
      {#each users as user}
        <tr on:contextmenu={(e) => handleRowContextMenu(e, user)}>
          <td>{user.name}</td>
          <td>{user.email}</td>
          <td>
            <span class="status" class:active={user.active}>
              {user.active ? '活跃' : '非活跃'}
            </span>
          </td>
        </tr>
      {/each}
    </tbody>
  </table>
</div>

<ContextMenu bind:this={contextMenu} />

<style>
  .user-table {
    padding: 20px;
  }
  
  table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 16px;
  }
  
  th, td {
    text-align: left;
    padding: 12px;
    border-bottom: 1px solid #ddd;
  }
  
  th {
    background: #f5f5f5;
    font-weight: 600;
  }
  
  tr:hover {
    background: #f9f9f9;
    cursor: context-menu;
  }
  
  .status {
    padding: 4px 8px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
    background: #dc3545;
    color: white;
  }
  
  .status.active {
    background: #28a745;
  }
</style>
```

## 键盘导航

ContextMenu 支持完整的键盘导航：

- **上/下方向键**：在菜单项之间导航
- **Enter/空格键**：执行当前焦点的菜单项
- **Escape**：关闭菜单
- **Tab**：关闭菜单（焦点移开）

## 无障碍访问

ContextMenu 组件包含全面的无障碍功能：

- 正确的 ARIA 角色（`menu`、`menuitem`、`separator`）
- ARIA 属性（`aria-label`、`aria-disabled`）
- 键盘导航支持
- 焦点管理
- 屏幕阅读器兼容性
- 语义化 HTML 结构

### 最佳实践

```svelte
<!-- 好的：清晰的上下文指示 -->
<div class="editable-content" on:contextmenu={showEditMenu}>
  <!-- 内容 -->
</div>

<!-- 好的：适合上下文的菜单项 -->
const editMenuItems = [
  { text: '剪切', action: () => cut() },
  { text: '复制', action: () => copy() },
  { text: '粘贴', action: () => paste(), disabled: !canPaste }
];

<!-- 好的：禁用项的视觉反馈 -->
.menu-item.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
```

## 浏览器支持

支持所有现代浏览器：
- ES6+ JavaScript
- 现代 DOM API
- CSS 自定义属性
- Portal/Teleport 功能

## 性能考虑

- 高效的事件处理和适当的清理
- Portal 渲染以获得最佳层级
- 响应式变化时最小的 DOM 更新
- 智能定位计算
- 组件销毁时自动清理