# MemoEditor 备忘录编辑器

一个多行文本区域组件，具有字符计数、调整大小选项和多种显示模式等高级功能。

## 特性

- **多行文本输入**: 具有可配置行数和文本换行的富文本区域
- **字符计数器**: 可选的字符计数指示器，支持最大长度验证
- **调整大小选项**: 可配置的调整大小行为（无、水平、垂直、双向）
- **多种样式变体**: 支持plain、outlined和filled样式
- **显示模式**: 编辑和查看模式，支持正确的HTML格式化
- **自动调整大小处理**: 自动宽度计算和调整大小观察
- **清除按钮**: 可选的清除功能，便于删除文本
- **表单集成**: 与表单和验证无缝协作
- **事件转发**: 全面的事件处理和转发

## 安装

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

## 使用方法

### 基本用法

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let description = '';
</script>

<MemoEditor 
  bind:value={description}
  placeholder="请在此输入描述..."
  variant="outlined"
  input$rows={4}
/>
```

### 带字符计数器

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let notes = '';
</script>

<MemoEditor 
  bind:value={notes}
  variant="filled"
  input$rows={5}
  maxLength={500}
  showIndicator
  placeholder="输入备注（最多500字符）"
/>

<p>当前长度: {notes.length}</p>
```

### 可调整大小的编辑器

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let content = '';
</script>

<MemoEditor 
  bind:value={content}
  variant="outlined"
  input$rows={6}
  resize="both"
  placeholder="此编辑器可以在两个方向上调整大小"
/>
```

### 只读和查看模式

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  import { DisplayMode } from '@ticatec/uniface-element';
  
  let text = '这是一些示例文本\n包含多行\n用于演示。';
</script>

<!-- 只读模式 -->
<MemoEditor 
  value={text}
  readonly
  variant="outlined"
  input$rows={4}
/>

<!-- 查看模式（显示为格式化的HTML） -->
<MemoEditor 
  value={text}
  displayMode={DisplayMode.View}
/>
```

## API 参考

### 属性

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `value` | `string` | `''` | 编辑器的文本内容 |
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 视觉样式变体 |
| `disabled` | `boolean` | `false` | 编辑器是否禁用 |
| `readonly` | `boolean` | `false` | 编辑器是否只读 |
| `style` | `string` | `''` | 自定义CSS样式 |
| `class` | `string` | `''` | 额外的CSS类 |
| `wrap` | `'hard' \| 'soft'` | `'hard'` | 文本换行行为 |
| `resize` | `'none' \| 'horizontal' \| 'vertical' \| 'both'` | `'none'` | 调整大小手柄行为 |
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 显示模式（编辑或查看） |
| `input$rows` | `number` | `3` | 可见文本行数 |
| `showIndicator` | `boolean` | `false` | 是否显示字符计数指示器 |
| `maxLength` | `number \| null` | `null` | 允许的最大字符数 |

### 输入属性

所有以 `input$` 为前缀的属性都会转发到底层的textarea元素：

| 属性 | 类型 | 描述 |
|------|------|-------------|
| `input$placeholder` | `string` | 占位符文本 |
| `input$maxLength` | `number` | 最大字符长度 |
| `input$minLength` | `number` | 最小字符长度 |
| `input$required` | `boolean` | 字段是否必填 |
| `input$autofocus` | `boolean` | 是否在挂载时自动聚焦 |
| `input$spellcheck` | `boolean` | 启用/禁用拼写检查 |
| `input$autocomplete` | `string` | 自动完成行为 |

### 事件

组件转发所有标准textarea事件：

- `on:input` - 文本输入事件
- `on:change` - 值改变事件
- `on:focus` - 焦点事件
- `on:blur` - 失焦事件
- `on:keydown` - 键盘事件
- `on:keyup` - 键盘事件
- `on:keypress` - 键盘事件
- `on:compositionstart` - IME组合事件
- `on:compositionend` - IME组合事件
- `on:compositionupdate` - IME组合事件

## 示例

### 评论编辑器

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  import FormField from '@ticatec/uniface-element/form-field';
  
  let comment = '';
  let errors = {};
  
  function validateComment() {
    if (!comment.trim()) {
      errors.comment = '评论为必填项';
      return false;
    }
    
    if (comment.length < 10) {
      errors.comment = '评论至少需要10个字符';
      return false;
    }
    
    errors.comment = null;
    return true;
  }
  
  function handleSubmit() {
    if (validateComment()) {
      console.log('评论已提交:', comment);
    }
  }
</script>

<FormField label="评论" required error={errors.comment}>
  <MemoEditor 
    bind:value={comment}
    variant="outlined"
    input$rows={4}
    maxLength={1000}
    showIndicator
    input$placeholder="请在此输入您的评论..."
    on:blur={validateComment}
  />
</FormField>

<button on:click={handleSubmit} disabled={!comment.trim()}>
  提交评论
</button>
```

### 文章编辑器

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let article = {
    title: '',
    content: '',
    summary: ''
  };
  
  let wordCount = 0;
  
  $: wordCount = article.content.split(/\s+/).filter(word => word.length > 0).length;
</script>

<div class="article-editor">
  <h2>文章编辑器</h2>
  
  <div class="field-group">
    <label>标题</label>
    <input 
      bind:value={article.title} 
      placeholder="输入文章标题"
      maxlength="100"
    />
  </div>
  
  <div class="field-group">
    <label>摘要</label>
    <MemoEditor 
      bind:value={article.summary}
      variant="filled"
      input$rows={3}
      maxLength={300}
      showIndicator
      input$placeholder="文章的简要摘要..."
    />
  </div>
  
  <div class="field-group">
    <label>内容</label>
    <MemoEditor 
      bind:value={article.content}
      variant="outlined"
      input$rows={15}
      resize="vertical"
      input$placeholder="在此编写您的文章内容..."
    />
    <div class="word-count">
      字数: {wordCount}
    </div>
  </div>
</div>

<style>
  .article-editor {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }
  
  .field-group {
    margin-bottom: 1.5rem;
  }
  
  .field-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 600;
  }
  
  .field-group input {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    font-size: 1rem;
  }
  
  .word-count {
    text-align: right;
    font-size: 0.875rem;
    color: #6b7280;
    margin-top: 0.25rem;
  }
</style>
```

### 反馈表单

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  import FormField from '@ticatec/uniface-element/form-field';
  
  let feedback = {
    category: '',
    description: '',
    suggestions: ''
  };
  
  let isSubmitting = false;
  
  async function submitFeedback() {
    isSubmitting = true;
    
    try {
      // 模拟API调用
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      console.log('反馈已提交:', feedback);
      
      // 重置表单
      feedback = { category: '', description: '', suggestions: '' };
      alert('感谢您的反馈！');
    } catch (error) {
      console.error('提交反馈失败:', error);
      alert('提交反馈失败，请重试。');
    } finally {
      isSubmitting = false;
    }
  }
</script>

<form on:submit|preventDefault={submitFeedback}>
  <h2>反馈表单</h2>
  
  <FormField label="类别" required>
    <select bind:value={feedback.category} required>
      <option value="">选择类别</option>
      <option value="bug">错误报告</option>
      <option value="feature">功能请求</option>
      <option value="general">一般反馈</option>
    </select>
  </FormField>
  
  <FormField label="描述" required>
    <MemoEditor 
      bind:value={feedback.description}
      variant="outlined"
      input$rows={5}
      maxLength={1000}
      showIndicator
      input$placeholder="请详细描述您的反馈..."
      input$required
    />
  </FormField>
  
  <FormField label="建议">
    <MemoEditor 
      bind:value={feedback.suggestions}
      variant="filled"
      input$rows={3}
      maxLength={500}
      showIndicator
      input$placeholder="有什么改进建议吗？（可选）"
    />
  </FormField>
  
  <div class="form-actions">
    <button 
      type="submit" 
      disabled={isSubmitting || !feedback.category || !feedback.description}
    >
      {isSubmitting ? '提交中...' : '提交反馈'}
    </button>
  </div>
</form>

<style>
  form {
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem;
  }
  
  h2 {
    margin-bottom: 2rem;
    color: #1f2937;
  }
  
  select {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    font-size: 1rem;
    background: white;
  }
  
  .form-actions {
    margin-top: 2rem;
    text-align: right;
  }
  
  button {
    padding: 0.75rem 2rem;
    background: #3b82f6;
    color: white;
    border: none;
    border-radius: 0.375rem;
    font-size: 1rem;
    cursor: pointer;
    transition: background-color 0.2s;
  }
  
  button:hover:not(:disabled) {
    background: #2563eb;
  }
  
  button:disabled {
    background: #9ca3af;
    cursor: not-allowed;
  }
</style>
```

### 聊天消息编辑器

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let message = '';
  let messages = [];
  
  function sendMessage() {
    if (message.trim()) {
      messages = [...messages, {
        id: Date.now(),
        text: message,
        timestamp: new Date().toLocaleTimeString()
      }];
      message = '';
    }
  }
  
  function handleKeydown(event) {
    if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
      event.preventDefault();
      sendMessage();
    }
  }
</script>

<div class="chat-container">
  <div class="messages">
    {#each messages as msg}
      <div class="message">
        <div class="message-text">{msg.text}</div>
        <div class="message-time">{msg.timestamp}</div>
      </div>
    {/each}
  </div>
  
  <div class="message-input">
    <MemoEditor 
      bind:value={message}
      variant="outlined"
      input$rows={3}
      maxLength={500}
      showIndicator
      input$placeholder="输入您的消息... (Ctrl+Enter 发送)"
      on:keydown={handleKeydown}
    />
    
    <div class="input-actions">
      <button 
        on:click={sendMessage}
        disabled={!message.trim()}
      >
        发送
      </button>
    </div>
  </div>
</div>

<style>
  .chat-container {
    max-width: 500px;
    margin: 0 auto;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    overflow: hidden;
  }
  
  .messages {
    height: 300px;
    overflow-y: auto;
    padding: 1rem;
    background: #f9fafb;
  }
  
  .message {
    margin-bottom: 1rem;
    padding: 0.75rem;
    background: white;
    border-radius: 0.375rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  }
  
  .message-text {
    white-space: pre-wrap;
    margin-bottom: 0.25rem;
  }
  
  .message-time {
    font-size: 0.75rem;
    color: #6b7280;
    text-align: right;
  }
  
  .message-input {
    padding: 1rem;
    background: white;
    border-top: 1px solid #e5e7eb;
  }
  
  .input-actions {
    margin-top: 0.5rem;
    text-align: right;
  }
  
  button {
    padding: 0.5rem 1rem;
    background: #10b981;
    color: white;
    border: none;
    border-radius: 0.25rem;
    cursor: pointer;
    font-size: 0.875rem;
  }
  
  button:hover:not(:disabled) {
    background: #059669;
  }
  
  button:disabled {
    background: #d1d5db;
    cursor: not-allowed;
  }
</style>
```

## 样式

### CSS 变量

MemoEditor组件使用CSS变量进行一致的主题化：

```css
:root {
  --uniface-editor-border-color: #d1d5db;
  --uniface-editor-focus-border-color: #3b82f6;
  --uniface-editor-background: white;
  --uniface-editor-text-color: #374151;
  --uniface-editor-placeholder-color: #9ca3af;
  --uniface-editor-indicator-color: #6b7280;
  --uniface-editor-indicator-background: rgba(255, 255, 255, 0.9);
}
```

### 自定义样式

```css
.custom-memo-editor {
  --uniface-editor-border-color: #e2e8f0;
  --uniface-editor-focus-border-color: #2563eb;
  --uniface-editor-background: #f8fafc;
  border-radius: 8px;
  font-family: 'Monaco', 'Menlo', monospace;
}

.custom-memo-editor textarea {
  line-height: 1.6;
  font-size: 14px;
}

.custom-memo-editor .uniface-memo-indicator {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: 4px;
  padding: 2px 8px;
}
```

### 样式变体

```svelte
<!-- Outlined 样式 -->
<MemoEditor variant="outlined" />

<!-- Filled 样式 -->
<MemoEditor variant="filled" />

<!-- Plain 样式 -->
<MemoEditor variant="plain" />

<!-- 默认样式 -->
<MemoEditor />
```

## 高级功能

### 自动保存功能

```svelte
<script>
  import MemoEditor from '@ticatec/uniface-element/memo-editor';
  
  let content = '';
  let lastSaved = null;
  let saveStatus = 'saved'; // 'saving', 'saved', 'error'
  
  let saveTimeout;
  
  async function saveContent() {
    saveStatus = 'saving';
    
    try {
      // 模拟API保存
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      localStorage.setItem('draft-content', content);
      lastSaved = new Date();
      saveStatus = 'saved';
    } catch (error) {
      console.error('保存失败:', error);
      saveStatus = 'error';
    }
  }
  
  function handleInput() {
    saveStatus = 'saving';
    
    // 防抖自动保存
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(saveContent, 2000);
  }
  
  // 在挂载时加载已保存的内容
  import { onMount } from 'svelte';
  
  onMount(() => {
    const saved = localStorage.getItem('draft-content');
    if (saved) {
      content = saved;
    }
  });
</script>

<div class="auto-save-editor">
  <div class="editor-header">
    <h3>文档编辑器</h3>
    <div class="save-status">
      {#if saveStatus === 'saving'}
        <span class="status saving">保存中...</span>
      {:else if saveStatus === 'saved'}
        <span class="status saved">
          已保存 {lastSaved ? lastSaved.toLocaleTimeString() : ''}
        </span>
      {:else if saveStatus === 'error'}
        <span class="status error">保存失败</span>
      {/if}
    </div>
  </div>
  
  <MemoEditor 
    bind:value={content}
    variant="outlined"
    input$rows={15}
    resize="vertical"
    input$placeholder="开始输入您的文档..."
    on:input={handleInput}
  />
</div>

<style>
  .auto-save-editor {
    max-width: 800px;
    margin: 0 auto;
  }
  
  .editor-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
    padding: 0 0.5rem;
  }
  
  .editor-header h3 {
    margin: 0;
    color: #1f2937;
  }
  
  .save-status .status {
    font-size: 0.875rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
  }
  
  .status.saving {
    background: #fef3c7;
    color: #92400e;
  }
  
  .status.saved {
    background: #d1fae5;
    color: #065f46;
  }
  
  .status.error {
    background: #fee2e2;
    color: #991b1b;
  }
</style>
```

## 最佳实践

### 1. 使用适当的行数

根据预期内容长度设置 `input$rows`：

```svelte
<!-- 短描述 -->
<MemoEditor input$rows={3} maxLength={200} />

<!-- 中等内容 -->
<MemoEditor input$rows={6} maxLength={1000} />

<!-- 长文章 -->
<MemoEditor input$rows={15} resize="vertical" />
```

### 2. 实现字符限制

始终设置合理的字符限制并显示指示器：

```svelte
<MemoEditor 
  maxLength={500}
  showIndicator
  variant="outlined"
/>
```

### 3. 处理验证

提供清晰的验证反馈：

```svelte
<script>
  let content = '';
  let error = '';
  
  function validate() {
    if (content.length < 10) {
      error = '内容至少需要10个字符';
    } else {
      error = '';
    }
  }
</script>

<MemoEditor 
  bind:value={content}
  on:blur={validate}
  style={error ? 'border-color: #ef4444;' : ''}
/>

{#if error}
  <div class="error">{error}</div>
{/if}
```

### 4. 为长内容启用调整大小

允许用户调整编辑器大小以获得更好的可用性：

```svelte
<MemoEditor 
  resize="vertical"
  input$rows={8}
  placeholder="此编辑器可以垂直调整大小"
/>
```

## 无障碍功能

- **键盘导航**: 完整的文本编辑键盘支持
- **屏幕阅读器支持**: 适当的标签和通知
- **焦点管理**: 清晰的焦点指示器和tab导航
- **ARIA属性**: 适当的角色和属性
- **字符限制通知**: 字符限制的屏幕阅读器反馈

## 浏览器支持

- **现代浏览器**: Chrome、Firefox、Safari、Edge（最新版本）
- **移动浏览器**: iOS Safari、Chrome Mobile、Firefox Mobile
- **ResizeObserver**: 自动宽度计算需要（提供polyfill）
- **Textarea功能**: 支持所有标准textarea功能

## 相关组件

- **TextEditor**: 单行文本输入组件
- **FormField**: 用于标签和验证的表单字段包装器
- **ListBox**: 用于文本模板或片段的列表选择

## 许可证

MIT许可证 - 详情请参阅LICENSE文件。