# 範圍 & 選區 & 單元格

- Human documentation: [https://docs.univer.ai/zh-TW/guides/sheets/features/core/range-selection](https://docs.univer.ai/zh-TW/guides/sheets/features/core/range-selection)

- Agent Markdown: [https://docs.univer.ai/zh-TW/guides/sheets/features/core/range-selection.md](https://docs.univer.ai/zh-TW/guides/sheets/features/core/range-selection.md)

- Requested language: `zh-TW`

- Content language: `zh-TW`

- Documentation version: `1.2.0`

- Source: [sheets/features/core/range-selection.zh-TW.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/sheets/features/core/range-selection.zh-TW.mdx)

---

依任務跳轉到下方章節。本頁集中介紹基礎操作、進階範例、設定與疑難排解；完整 API 定義統一在 Reference 中維護。

* [範圍 Range](#range)
* [選區 Selection](#selection)
* [範圍進階操作](#range-operations)
* [工作表邊界的鍵盤導覽](#keyboard-navigation)
* [儲存格事件與編輯](#events)
* [儲存格編輯疑難排解](#troubleshooting)

## 範圍 Range

範圍指工作表中的一塊矩形區域，有起始行號、起始列號、長寬或者結束行號、結束列號來確定。

表格中的大部分操作都可以透過範圍 API 來操作，如設定值、獲取值、設定樣式、獲取樣式等。

範圍完整 Facade API 型別定義，請檢視 [FRange Facade API](https://docs.univer.ai/zh-TW/reference/facade/range.md)

### 建立範圍

獲得一個範圍需要知道起始行號、起始列號、長寬。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 獲取 A1 單元格的範圍：
const fRange = fWorksheet.getRange(0, 0)
console.log(fRange, fRange.getA1Notation())

// 獲取 A1:B2 的範圍：
const fRange2 = fWorksheet.getRange(0, 0, 2, 2)
console.log(fRange2, fRange2.getA1Notation())
```

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 獲取名稱是 Sheet1 的工作表的 A1:B2 範圍
const fRange1 = fWorksheet.getRange('Sheet1!A1:B2')
console.log(fRange1, fRange1.getA1Notation())

// 獲取單個單元格 A1
const fRange2 = fWorksheet.getRange('A1')
console.log(fRange2, fRange2.getA1Notation())

// 獲取 A1:B2 範圍
const fRange3 = fWorksheet.getRange('A1:B2')
console.log(fRange3, fRange3.getA1Notation())

// 獲取 A 列的範圍
const fRange4 = fWorksheet.getRange('A:A')
console.log(fRange4, fRange4.getA1Notation())

// 獲取第 1 行的範圍
const fRange5 = fWorksheet.getRange('1:1')
console.log(fRange5, fRange5.getA1Notation())
```

### 獲取範圍資料

獲取範圍第一個單元格的值

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
if (!fWorkbook) throw new Error('No active workbook')
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')

console.log(fRange.getValue()) // 範圍左上角單元格值
console.log(fRange.getRawValue()) // 範圍左上角單元格原始值
console.log(fRange.getDisplayValue()) // 範圍左上角單元格顯示值
console.log(fRange.getCellData()) // 範圍左上角單元格 ICellData 物件
const value = fRange.getValue(true)
console.log(value)
if (value !== null && typeof value === 'object') {
  console.log(value.toPlainText())
}
```

使用 `getValue(true)` 保留豐富文字物件。空儲存格傳回 `null` / `undefined`，一般儲存格傳回字串、數字或布林值；僅對豐富文字物件呼叫 `toPlainText()`。

獲取範圍的所有值

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
if (!fWorkbook) throw new Error('No active workbook')
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')

console.log(fRange.getValues()) // 範圍所有單元格值
console.log(fRange.getRawValues()) // 範圍所有單元格原始值
console.log(fRange.getDisplayValues()) // 範圍所有單元格顯示值
console.log(fRange.getCellDataGrid()) // 範圍所有單元格 ICellData 物件
console.log(fRange.getValues(true))
```

獲取範圍的所有公式

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')

console.log(fRange.getFormula()) // 範圍左上角單元格公式
console.log(fRange.getFormulas()) // 範圍所有單元格公式
```

### 設定範圍資料

#### 設定單一值

傳入一個值或者單元格物件，將會覆蓋範圍內所有單元格，如果以 `=` 開頭，將被解釋為公式。

比如，設定 A1:B2 的值為 `Hello, Univer`：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange.setValue('Hello, Univer')
```

將 C1 的值設為 `=A1+B1`，讓結果與輸入儲存格分開：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
if (!fWorkbook) throw new Error('No active workbook')
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('C1')
fRange.setValue('=A1+B1')
```

設定 A1:B2 的值為單元格物件：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange.setValue({
  v: 'Hello, Univer',
  custom: {
    key: 'value',
  },
})
```

如果只想設定範圍左上角的單元格值，可以使用 [`FRange.setValueForCell`](https://docs.univer.ai/zh-TW/reference/facade/range.md#setvalueforcell) 方法：

```typescript
fRange.setValueForCell('Hello, Univer')
```

#### 透過陣列設定多個值

陣列的長度和寬度必須和範圍的長寬一致。

可以傳入單元格值也可以傳入單元格物件。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange.setValues([
  ['A1', 'B1'],
  ['A2', 'B2'],
])

fRange.setValues([
  [{ v: 'A1' }, { v: 'B1' }],
  [{ v: 'A2' }, { v: 'B2' }],
])
```

#### 透過物件設定多個值

則物件的一級索引代表行號，二級索引代表列號，與範圍的長寬無需一致。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange.setValues({
  0: {
    0: 'A1',
    1: 'B1',
  },
  1: {
    0: 'A2',
    1: 'B2',
  },
})
```

### 清理範圍資料

* [`FRange.clear`](https://docs.univer.ai/zh-TW/reference/facade/range.md#clear) 方法可以清理範圍的內容和格式資訊。
* [`FRange.clearContent`](https://docs.univer.ai/zh-TW/reference/facade/range.md#clearcontent) 方法可以清理範圍的內容資訊。
* [`FRange.clearFormat`](https://docs.univer.ai/zh-TW/reference/facade/range.md#clearformat) 方法可以清理範圍的格式資訊。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorkSheet = fWorkbook.getActiveSheet()
const fRange = fWorkSheet.getRange('A1:D10')

// 清除範圍的內容和格式資訊。
fRange.clear()
```

### 獲取範圍樣式

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
console.log(fRange.getCellStyleData()) // 範圍左上角單元格樣式資料
console.log(fRange.getCellStyle()) // 範圍左上角單元格樣式
console.log(fRange.getCellStyles()) // 範圍所有單元格樣式
```

### 設定範圍樣式

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange
  .setValues([
    [1, 2],
    [3, 4],
  ])
  .setFontWeight('bold')
  .setFontLine('underline')
  .setFontFamily('Arial')
  .setFontSize(24)
  .setFontColor('red')
```

### 清理範圍樣式

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
fRange.setFontWeight(null).setFontLine(null).setFontFamily(null).setFontSize(null).setFontColor(null)
```

## 選區 Selection

Univer 表格支援多選區，所以選區是一個範圍陣列，可以透過範圍 API 來操作選區資料。

我們還提供 API 來獲取當前選區、設定選區和監聽選區變化。

選區完整 Facade API 型別定義，請檢視 [FSelection Facade API](https://docs.univer.ai/zh-TW/reference/facade/selection.md)

### 獲取啟用選區的範圍

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 啟用新的選區
const fRange = fWorksheet.getRange('A1:B2')
fRange.activate()

// 獲取啟用選區的範圍
const fSelection = fWorksheet.getSelection()
console.log(fSelection)
const activeRange = fSelection.getActiveRange()
console.log(activeRange.getA1Notation()) // A1:B2
```

### 設定選區

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 透過 FRange 設定選區 A1:B2
const fRange = fWorksheet.getRange('A1:B2')
fRange.activate()

// 透過 FWorksheet 設定選區 C1:D2
fWorksheet.setActiveSelection(fWorksheet.getRange('C1:D2'))
```

### 獲取當前啟用的單元格

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 啟用選區 A10:B11
const fRange = fWorksheet.getRange('A10:B11')
fRange.activate()

// 獲取當前啟用的單元格
let fSelection = fWorksheet.getSelection()
const { actualRow, actualColumn } = fSelection.getCurrentCell()
console.log(fWorksheet.getRange(actualRow, actualColumn).getA1Notation()) // A10

// 更新新的啟用單元格 B11
fSelection = fSelection.updatePrimaryCell(fWorksheet.getRange('B11'))
const { actualRow: newRow, actualColumn: newColumn } = fSelection.getCurrentCell()
console.log(fWorksheet.getRange(newRow, newColumn).getA1Notation()) // B11
```

### 禁用/啟用/隱藏/顯示選區

禁用後，選區將不會響應。設定選區不可見，與禁用選區不同，選區仍然有效，只是看不到它們。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()

// 禁用選區
fWorkbook.disableSelection()

// 啟用選區
fWorkbook.enableSelection()

// 隱藏選區
fWorkbook.transparentSelection()

// 顯示選區
fWorkbook.showSelection()
```

### 監聽選區變化

```typescript
univerAPI.addEvent(univerAPI.Event.SelectionChanged, (params) => {
  const { worksheet, workbook, selections } = params
  console.log(params)
})
```

## 單元格 Cell

單元格資料以二維 Map 的形式儲存在工作表中，一二級索引分別代表行號和列號。

以下是一個典型的單元格物件：

```typescript
const data = {
  v: 'Hello, Univer',
  s: 'styleId',
  t: CellValueType.STRING,
}
```

詳細的欄位說明請參考 [單元格資訊](https://docs.univer.ai/zh-TW/guides/sheets/model/cell-data.md)。

### 注意事項

1. 對單元格的操作可以看作對行高 1 、列寬 1 的範圍進行操作，操作範圍請閱讀 [範圍-range](https://docs.univer.ai/zh-TW/guides/sheets/features/core/range-selection.md#range)。
2. 外掛還會將拓展的單元格屬性儲存在 `Workbook` 的 `resources` 屬性中，詳細請閱讀 [外掛自訂模型](/blog/custom-model/)。

## 範圍進階操作

### 插入單元格

[`FRange.insertCells`](https://docs.univer.ai/zh-TW/reference/facade/range.md#insertcells) 方法可以在此範圍內插入空單元格。工作表中沿指定維度的現有資料將移離插入的範圍。

```typescript
// 假設當前工作表是空工作表
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const values = [
  [1, 2, 3, 4],
  [2, 3, 4, 5],
  [3, 4, 5, 6],
  [4, 5, 6, 7],
  [5, 6, 7, 8],
]

// A1:D5 範圍設定值如下：
// 1 | 2 | 3 | 4
// 2 | 3 | 4 | 5
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
const fRange = fWorksheet.getRange('A1:D5')
fRange.setValues(values)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]

// 沿著列維度在範圍 A1:B2 中插入空單元格，範圍 A1:D5 將變為：
//   |   | 1 | 2
//   |   | 2 | 3
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
const fRange2 = fWorksheet.getRange('A1:B2')
fRange2.insertCells(univerAPI.Enum.Dimension.COLUMNS)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[null, null, 1, 2], [null, null, 2, 3], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]

// 再次設定範圍 A1:D5 的值，範圍 A1:D5 將變為：
// 1 | 2 | 3 | 4
// 2 | 3 | 4 | 5
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
fRange.setValues(values)

// 沿著行維度在範圍 A1:B2 中插入空單元格，範圍 A1:D5 將變為：
//   |   | 3 | 4
//   |   | 4 | 5
// 1 | 2 | 5 | 6
// 2 | 3 | 6 | 7
// 3 | 4 | 7 | 8
const fRange3 = fWorksheet.getRange('A1:B2')
fRange3.insertCells(univerAPI.Enum.Dimension.ROWS)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[null, null, 3, 4], [null, null, 4, 5], [1, 2, 5, 6], [2, 3, 6, 7], [3, 4, 7, 8]]
```

### 刪除單元格

[`FRange.deleteCells`](https://docs.univer.ai/zh-TW/reference/facade/range.md#deletecells) 方法可以在範圍內刪除單元格。工作表中沿指定維度的現有資料將向已刪除的範圍移動。

```typescript
// 假設當前工作表是空工作表
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const values = [
  [1, 2, 3, 4],
  [2, 3, 4, 5],
  [3, 4, 5, 6],
  [4, 5, 6, 7],
  [5, 6, 7, 8],
]

// A1:D5 範圍設定值如下：
// 1 | 2 | 3 | 4
// 2 | 3 | 4 | 5
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
const fRange = fWorksheet.getRange('A1:D5')
fRange.setValues(values)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]

// 沿著列維度刪除範圍 A1:B2，範圍 A1:D5 將變為：
// 3 | 4 |   |
// 4 | 5 |   |
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
const fRange2 = fWorksheet.getRange('A1:B2')
fRange2.deleteCells(univerAPI.Enum.Dimension.COLUMNS)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[3, 4, null, null], [4, 5, null, null], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]

// 再次設定範圍 A1:D5 的值，範圍 A1:D5 將變為：
// 1 | 2 | 3 | 4
// 2 | 3 | 4 | 5
// 3 | 4 | 5 | 6
// 4 | 5 | 6 | 7
// 5 | 6 | 7 | 8
fRange.setValues(values)

// 沿著行維度刪除範圍 A1:B2，範圍 A1:D5 將變為：
// 3 | 4 | 3 | 4
// 4 | 5 | 4 | 5
// 5 | 6 | 5 | 6
//   |   | 6 | 7
//   |   | 7 | 8
const fRange3 = fWorksheet.getRange('A1:B2')
fRange3.deleteCells(univerAPI.Enum.Dimension.ROWS)
console.log(fWorksheet.getRange('A1:D5').getValues()) // [[3, 4, 3, 4], [4, 5, 4, 5], [5, 6, 5, 6], [null, null, 6, 7], [null, null, 7, 8]]
```

### 合併單元格

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// 合併 B1:B2
const fRange = fWorksheet.getRange('B1:B2')
fRange.merge()
console.log(fRange.isMerged()) // true

// A1:B2 是否合併
const fRange2 = fWorksheet.getRange('A1:B2')
console.log(fRange2.isMerged()) // false
console.log(fRange2.isPartOfMerge()) // true

// 取消合併
fRange2.breakApart()

// 水平合併：fRange2.mergeAcross();
// 垂直合併：fRange2.mergeVertically();
// 獲取表格中所有合併單元格範圍：fWorksheet.getMergedRanges();
```

### 高亮範圍

[`FRange.highlight`](https://docs.univer.ai/zh-TW/reference/facade/range.md#highlight) 方法可以使用指定樣式和主單元格高亮範圍。

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// Highlight the range C3:E5 with default style
const fRange = fWorksheet.getRange('C3:E5')
fRange.highlight()

// Highlight the range C7:E9 with custom style and primary cell D8
const fRange2 = fWorksheet.getRange('C7:E9')
const primaryCell = fWorksheet.getRange('D8').getRange()
const disposable = fRange2.highlight(
  {
    stroke: 'red',
    fill: 'yellow',
  },
  {
    ...primaryCell,
    actualRow: primaryCell.startRow,
    actualColumn: primaryCell.startColumn,
  },
)

// Remove the range C7:E9 highlight after 5 seconds
setTimeout(() => {
  disposable.dispose()
}, 5000)
```

### 資料分列

[`FRange.splitTextToColumns`](https://docs.univer.ai/zh-TW/reference/facade/range.md#splittexttocolumns) 方法可以將單元格中的文字按指定分隔符分列。

預設分隔符示例：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// A1:A3 包含以下值：
//  A      |
//  1,2,3  |
//  4,,5,6 |
const fRange = fWorksheet.getRange('A1:A3')
fRange.setValues([['A'], ['1,2,3'], ['4,,5,6']])

// 呼叫 splitTextToColumns(true) 後，範圍值將變為：
//  A |   |
//  1 | 2 | 3
//  4 | 5 | 6
fRange.splitTextToColumns(true)

// 呼叫 splitTextToColumns(false) 後，範圍值將變為：
//  A |   |   |
//  1 | 2 | 3 |
//  4 |   | 5 | 6
fRange.splitTextToColumns(false)
```

指定分隔符示例：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// A1:A3 包含以下值：
//     A   |
//  1;;2;3 |
//  1;,2;3 |
const fRange = fWorksheet.getRange('A1:A3')
fRange.setValues([['A'], ['1;;2;3'], ['1;,2;3']])

// 呼叫 splitTextToColumns(false, univerAPI.Enum.SplitDelimiterType.Semicolon|univerAPI.Enum.SplitDelimiterType.Comma) 後，範圍值將變為：
//  A |   |   |
//  1 |   | 2 | 3
//  1 |   | 2 | 3
fRange.splitTextToColumns(false, univerAPI.Enum.SplitDelimiterType.Semicolon | univerAPI.Enum.SplitDelimiterType.Comma)

// 呼叫 splitTextToColumns(true, univerAPI.Enum.SplitDelimiterType.Semicolon|univerAPI.Enum.SplitDelimiterType.Comma) 後，範圍值將變為：
//  A |   |
//  1 | 2 | 3
//  1 | 2 | 3
fRange.splitTextToColumns(true, univerAPI.Enum.SplitDelimiterType.Semicolon | univerAPI.Enum.SplitDelimiterType.Comma)
```

自訂分隔符示例：

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()

// A1:A3 包含以下值：
//     A   |
//  1#2#3  |
//  4##5#6 |
const fRange = fWorksheet.getRange('A1:A3')
fRange.setValues([['A'], ['1#2#3'], ['4##5#6']])

// 呼叫 splitTextToColumns(false, univerAPI.Enum.SplitDelimiterType.Custom, '#') 後，範圍值將變為：
//  A |   |   |
//  1 | 2 | 3 |
//  4 |   | 5 | 6
fRange.splitTextToColumns(false, univerAPI.Enum.SplitDelimiterType.Custom, '#')

// 呼叫 splitTextToColumns(true, univerAPI.Enum.SplitDelimiterType.Custom, '#') 後，範圍值將變為：
//  A |   |
//  1 | 2 | 3
//  4 | 5 | 6
fRange.splitTextToColumns(true, univerAPI.Enum.SplitDelimiterType.Custom, '#')
```

### 獲取座標

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
console.log(fRange.getCellRect()) // width、heigh、left、right、top、bottom、x、y
```

### 同時獲取範圍的合併資訊和座標

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
console.log(fRange.getCell())
```

## 工作表邊界的鍵盤導覽

`allowArrowKeyWrapOnSheetBoundary` 用來控制方向鍵到達工作表邊界後是否可換到另一列或欄。預設值為 `true`，設為 `false` 時停留在邊界；不影響選取範圍內 Enter/Tab 的循環移動。預設模式下放在 `sheets` 中，外掛模式下直接傳給 `UniverSheetsUIPlugin`。

#### 外掛模式

```typescript
univer.registerPlugin(UniverSheetsUIPlugin, {
  allowArrowKeyWrapOnSheetBoundary: false,
})
```

#### 預設模式

```typescript
createUniver({
  presets: [
    UniverSheetsCorePreset({
      sheets: {
        allowArrowKeyWrapOnSheetBoundary: false,
      },
    }),
  ],
})
```

## 儲存格事件與編輯

以下範例依指標、繪製、編輯與剪貼簿互動組織。完整事件與參數型別見[事件 Reference](https://docs.univer.ai/zh-TW/reference/facade/events.md#univerjssheets-ui)。外掛模式下訂閱前需匯入 `@univerjs/sheets-ui/facade`，Sheets Core 預設已包含此擴充。所屬元件或頁面卸載時應清理訂閱。

### 使用示例

所有事件都可以透過 `addEvent` 方法進行監聽。基本格式如下：

```typescript
import '@univerjs/sheets-ui/facade'

const subscription = univerAPI.addEvent(univerAPI.Event.CellClicked, ({ row, column }) => {
  console.log(row, column)
})

// Call subscription.dispose() when the owning component or page is removed.
```

### 單元格 PointerMove 事件

`CellPointerMove` 事件在滑鼠移動到單元格上時觸發。透過此事件可以獲取當前滑鼠指向的單元格資訊。

```typescript
univerAPI.addEvent(univerAPI.Event.CellPointerMove, (params) => {
  // 獲取事件參數
  const { worksheet, workbook, row, column } = params
  console.log('當前單元格位置:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 PointerDown 事件

`CellPointerDown` 事件在滑鼠按下時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.CellPointerDown, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('滑鼠按下的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 PointerUp 事件

`CellPointerUp` 事件在滑鼠釋放時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.CellPointerUp, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('滑鼠釋放的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 Hover 事件

`CellHover` 事件在滑鼠懸停在單元格上時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.CellHover, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('懸停的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 DragOver 事件

`DragOver` 事件在拖動元素經過單元格時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.DragOver, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('拖動經過的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 Drop 事件

`Drop` 事件在拖動元素放置到單元格時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.Drop, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('放置的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格 Click 事件

`CellClicked` 事件在點選單元格時觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.CellClicked, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log('點選的單元格:', worksheet.getRange(row, column).getA1Notation())
})
```

### 單元格渲染事件

`registerCellCustomRender` 用於註冊單元格自訂渲染器。

案例 1：固定位置渲染，新增行不會影響渲染位置

```typescript
// 固定位置渲染
univerAPI.registerCellCustomRender([
  {
    drawWith: (ctx, info, skeleton, spreadsheets) => {
      const { row, col } = info
      // 在指定位置(1,1)渲染勾選標記
      if (row === 1 && col === 1) {
        const { primaryWithCoord } = info
        const { startX, startY } = primaryWithCoord
        ctx.fillText('✅', startX, startY + 10)
      }
    },
  },
])

// 重新整理畫布以應用渲染
univerAPI.getActiveWorkbook().getActiveSheet().refreshCanvas()
```

案例 2：按標記渲染，標記位置會隨著行列變化而變化

```typescript
// 設定標記
univerAPI
  .getActiveWorkbook()
  .getActiveSheet()
  .getRange('B2')
  .setValue({ custom: { key: 'needCheck' } })

// 按標記渲染
univerAPI.registerCellCustomRender([
  {
    drawWith: (ctx, info, skeleton, spreadsheets) => {
      const { row, col, data } = info
      // 在標記的位置渲染勾選標記
      if (data?.custom?.key === 'needCheck') {
        const { primaryWithCoord } = info
        const { startX, startY } = primaryWithCoord
        ctx.fillText('✅', startX, startY + 10)
      }
    },
  },
])

// 重新整理畫布以應用渲染
univerAPI.getActiveWorkbook().getActiveSheet().refreshCanvas()
```

### 單元格編輯事件

#### 開始編輯

```typescript
const fWorkbook = univerAPI.getActiveWorkbook()
fWorkbook.startEditing()
```

#### 結束編輯

```typescript
// 傳入 true 表示提交編輯，false 表示取消編輯
// 這是一個非同步函式，需要使用 await
const fWorkbook = univerAPI.getActiveWorkbook()
await fWorkbook.endEditingAsync(true)
```

#### 開始編輯事件

`BeforeSheetEditStart` 事件在單元格開始編輯前觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.BeforeSheetEditStart, (params) => {
  const { worksheet, workbook, row, column, eventType, keycode } = params
  console.log('單元格編輯前:', params)

  // 如果想阻止進入編輯狀態
  params.cancel = true
})
```

`SheetEditStarted` 事件在單元格開始編輯時觸發。

```typescript
const disposable = univerAPI.addEvent(univerAPI.Event.SheetEditStarted, (params) => {
  const { worksheet, workbook, row, column, eventType, keycode } = params
  console.log(params)
})

// 移除事件監聽器，使用 `disposable.dispose()`
```

#### 結束編輯事件

`BeforeSheetEditEnd` 事件在單元格結束編輯前觸發。

```typescript
const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetEditEnd, (params) => {
  const { worksheet, workbook, row, column, value, eventType, keycode, isConfirm } = params
  console.log(params)

  // 取消單元格編輯結束操作
  params.cancel = true
})

// 移除事件監聽器，使用 `disposable.dispose()`
```

`SheetEditEnded` 事件在單元格結束編輯後觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.SheetEditEnded, (params) => {
  const { worksheet, workbook, row, column, eventType, keycode, isConfirm } = params
  console.log('單元格編輯後:', params)
})
```

### 剪貼簿事件

#### BeforeClipboardChange

`BeforeClipboardChange` 事件在剪貼簿內容改變之前觸發。你可以使用此事件在內容改變之前監控或修改剪貼簿內容。

```typescript
univerAPI.addEvent(univerAPI.Event.BeforeClipboardChange, (params) => {
  const { text, html } = params
  console.log('剪貼簿內容:', text, html)
  // 如果想取消剪貼簿內容的改變
  // params.cancel = true;
})
```

#### BeforeClipboardPaste

`BeforeClipboardPaste` 事件在內容貼上之前觸發。你可以使用此事件在貼上之前監控或修改內容。

```typescript
univerAPI.addEvent(univerAPI.Event.BeforeClipboardPaste, (params) => {
  const { text, html } = params
  console.log('待貼上內容:', text, html)
  // 如果想取消貼上操作
  // params.cancel = true;
})
```

#### ClipboardChanged

`ClipboardChanged` 事件在剪貼簿內容改變之後觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.ClipboardChanged, (params) => {
  const { text, html } = params
  console.log('新的剪貼簿內容:', text, html)
})
```

#### ClipboardPasted

`ClipboardPasted` 事件在內容貼上完成後觸發。

```typescript
univerAPI.addEvent(univerAPI.Event.ClipboardPasted, (params) => {
  const { text, html } = params
  console.log('已貼上的內容:', text, html)
})
```

## 儲存格編輯疑難排解

### 如何實時獲取單元格編輯器內的資料？

訂閱 `SheetEditChanging`，可在儲存格編輯過程中讀取目前的富文字值。此時內容仍在編輯器內，編輯結束後才會提交到工作表。

```typescript
import '@univerjs/sheets-ui/facade'

const subscription = univerAPI.addEvent(univerAPI.Event.SheetEditChanging, ({ value }) => {
  console.log(value.toPlainText())
})

// Call subscription.dispose() when the owning component or page is removed.
```

### 單元格編輯器聚焦時，點選外部按鈕觸發事件，獲取的 snapshot 未包含當前單元格資料

單元格資料會在編輯器失焦（即編輯狀態結束）時同步到 snapshot。因此，在嘗試獲取 snapshot 資料前，應確保單元格已經失去焦點。可以透過如下方式主動結束編輯：

```typescript
import '@univerjs/sheets-ui/facade'

async function saveWorkbook() {
  const fWorkbook = univerAPI.getActiveWorkbook()
  if (!fWorkbook) return

  await fWorkbook.endEditingAsync(true)
  const snapshot = fWorkbook.save()
  console.log(snapshot)
}

$btn.addEventListener('click', () => {
  saveWorkbook().catch(console.error)
})
```

參考：[https://github.com/dream-num/univer/issues/1314](https://github.com/dream-num/univer/issues/1314)

### 使用 Facade API 建立 Workbook 後，立即呼叫其它 API 不生效的情況

部分 API 依賴於介面渲染完成後才能正常工作。如果在 Workbook 建立後立即呼叫相關 API，可能因尚未完成渲染而失效。建議在生命週期的 `Rendered` 或 `Steady` 階段再進行相關操作：

```typescript
const disposable = univerAPI.addEvent(univerAPI.Event.LifeCycleChanged, ({ stage }) => {
  if (stage === univerAPI.Enum.LifecycleStages.Steady) {
    // 程式碼...
  }
})

// 需要移除事件監聽時，可呼叫 disposable.dispose()
```

參考：[生命週期](https://docs.univer.ai/zh-TW/guides/sheets/getting-started/lifecycle.md)
