Skip to content

Commit 6b7269a

Browse files
authored
Merge pull request #4978 from fangsmile/feat/sheet-editable
feat(sheet): editable
2 parents 8a760ea + f27e0ff commit 6b7269a

10 files changed

Lines changed: 518 additions & 50 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Edit Capability Control
2+
3+
In `VTableSheet`, the editing capability is enabled by default. However, in some scenarios (such as preview mode or permission control), you may need to disable editing globally or for specific sheets.
4+
5+
VTableSheet provides flexible configuration options to control the editing capability and keyboard shortcuts at both the global and sheet levels.
6+
7+
## Configuration Options
8+
9+
### editable
10+
11+
- **Type**: `boolean`
12+
- **Default**: `true` (if not configured globally or on the sheet)
13+
- **Description**: Controls whether the table is editable.
14+
- When set to `false`, the table enters **Read-Only Mode**:
15+
- Double-click editing is disabled.
16+
- Editors are not registered.
17+
- Modification shortcuts (Cut, Paste, etc.) are disabled.
18+
- Context menu items related to modification (Insert/Delete rows, Merge cells, etc.) are hidden or disabled.
19+
- The Delete/Backspace key will not clear cell content (unless configured otherwise).
20+
- **Priority**: The configuration on a specific sheet (`ISheetDefine.editable`) takes precedence over the global configuration (`IVTableSheetOptions.editable`).
21+
22+
### keyboardShortcutPolicy
23+
24+
- **Type**: `SheetKeyboardShortcutPolicy`
25+
- **Description**: Defines the policy for keyboard shortcuts, allowing fine-grained control over specific actions.
26+
- **Properties**:
27+
- `copySelected` (boolean): Enable copy shortcut (Ctrl+C). Default `true`.
28+
- `cutSelected` (boolean): Enable cut shortcut (Ctrl+X). Default `true` (disabled in Read-Only mode).
29+
- `pasteValueToCell` (boolean): Enable paste shortcut (Ctrl+V). Default `true` (disabled in Read-Only mode).
30+
- `selectAllOnCtrlA` (boolean): Enable select all shortcut (Ctrl+A). Default `true`.
31+
- `deleteRange` (boolean): Enable clearing cell content with Delete/Backspace. Default `true` (disabled in Read-Only mode).
32+
- ... (other navigation shortcuts like `moveFocusCellOnTab`, `editCellOnEnter`, etc.)
33+
34+
## Usage Examples
35+
36+
### 1. Global Read-Only Mode
37+
38+
You can set the entire workbook to read-only by configuring `editable: false` in the initialization options.
39+
40+
```typescript
41+
import { VTableSheet } from '@visactor/vtable-sheet';
42+
43+
const sheet = new VTableSheet(container, {
44+
editable: false, // Global read-only
45+
sheets: [
46+
{
47+
sheetKey: 'sheet1',
48+
data: data1
49+
},
50+
{
51+
sheetKey: 'sheet2',
52+
data: data2
53+
}
54+
]
55+
});
56+
```
57+
58+
### 2. Mixed Mode (Global Read-Only with Specific Editable Sheets)
59+
60+
You can set the global default to read-only, and enable editing for specific sheets.
61+
62+
```typescript
63+
const sheet = new VTableSheet(container, {
64+
editable: false, // Default read-only
65+
sheets: [
66+
{
67+
sheetKey: 'read_only_sheet',
68+
sheetTitle: 'Read Only',
69+
data: data1
70+
// Inherits global editable: false
71+
},
72+
{
73+
sheetKey: 'editable_sheet',
74+
sheetTitle: 'Editable',
75+
data: data2,
76+
editable: true // Override global setting
77+
}
78+
]
79+
});
80+
```
81+
82+
### 3. Custom Keyboard Shortcut Policy
83+
84+
You can customize the keyboard behavior. For example, disable Cut and Paste but allow Copy, or disable clearing content with the Delete key.
85+
86+
```typescript
87+
const sheet = new VTableSheet(container, {
88+
// Global policy: Allow copy, disable cut/paste
89+
keyboardShortcutPolicy: {
90+
copySelected: true,
91+
cutSelected: false,
92+
pasteValueToCell: false
93+
},
94+
sheets: [
95+
{
96+
sheetKey: 'sheet1',
97+
data: data1
98+
},
99+
{
100+
sheetKey: 'sheet2',
101+
data: data2,
102+
// Sheet-level policy: Allow delete range
103+
keyboardShortcutPolicy: {
104+
deleteRange: true
105+
}
106+
}
107+
]
108+
});
109+
```
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Edit Capability Control
2+
3+
In `VTableSheet`, the editing capability is enabled by default. However, in some scenarios (such as preview mode or permission control), you may need to disable editing globally or for specific sheets.
4+
5+
VTableSheet provides flexible configuration options to control the editing capability and keyboard shortcuts at both the global and sheet levels.
6+
7+
## Configuration Options
8+
9+
### editable
10+
11+
- **Type**: `boolean`
12+
- **Default**: `true` (if not configured globally or on the sheet)
13+
- **Description**: Controls whether the table is editable.
14+
- When set to `false`, the table enters **Read-Only Mode**:
15+
- Double-click editing is disabled.
16+
- Editors are not registered.
17+
- Modification shortcuts (Cut, Paste, etc.) are disabled.
18+
- Context menu items related to modification (Insert/Delete rows, Merge cells, etc.) are hidden or disabled.
19+
- The Delete/Backspace key will not clear cell content (unless configured otherwise).
20+
- **Priority**: The configuration on a specific sheet (`ISheetDefine.editable`) takes precedence over the global configuration (`IVTableSheetOptions.editable`).
21+
22+
### keyboardShortcutPolicy
23+
24+
- **Type**: `SheetKeyboardShortcutPolicy`
25+
- **Description**: Defines the policy for keyboard shortcuts, allowing fine-grained control over specific actions.
26+
- **Properties**:
27+
- `copySelected` (boolean): Enable copy shortcut (Ctrl+C). Default `true`.
28+
- `cutSelected` (boolean): Enable cut shortcut (Ctrl+X). Default `true` (disabled in Read-Only mode).
29+
- `pasteValueToCell` (boolean): Enable paste shortcut (Ctrl+V). Default `true` (disabled in Read-Only mode).
30+
- `selectAllOnCtrlA` (boolean): Enable select all shortcut (Ctrl+A). Default `true`.
31+
- `deleteRange` (boolean): Enable clearing cell content with Delete/Backspace. Default `true` (disabled in Read-Only mode).
32+
- ... (other navigation shortcuts like `moveFocusCellOnTab`, `editCellOnEnter`, etc.)
33+
34+
## Usage Examples
35+
36+
### 1. Global Read-Only Mode
37+
38+
You can set the entire workbook to read-only by configuring `editable: false` in the initialization options.
39+
40+
```typescript
41+
import { VTableSheet } from '@visactor/vtable-sheet';
42+
43+
const sheet = new VTableSheet(container, {
44+
editable: false, // Global read-only
45+
sheets: [
46+
{
47+
sheetKey: 'sheet1',
48+
data: data1
49+
},
50+
{
51+
sheetKey: 'sheet2',
52+
data: data2
53+
}
54+
]
55+
});
56+
```
57+
58+
### 2. Mixed Mode (Global Read-Only with Specific Editable Sheets)
59+
60+
You can set the global default to read-only, and enable editing for specific sheets.
61+
62+
```typescript
63+
const sheet = new VTableSheet(container, {
64+
editable: false, // Default read-only
65+
sheets: [
66+
{
67+
sheetKey: 'read_only_sheet',
68+
sheetTitle: 'Read Only',
69+
data: data1
70+
// Inherits global editable: false
71+
},
72+
{
73+
sheetKey: 'editable_sheet',
74+
sheetTitle: 'Editable',
75+
data: data2,
76+
editable: true // Override global setting
77+
}
78+
]
79+
});
80+
```
81+
82+
### 3. Custom Keyboard Shortcut Policy
83+
84+
You can customize the keyboard behavior. For example, disable Cut and Paste but allow Copy, or disable clearing content with the Delete key.
85+
86+
```typescript
87+
const sheet = new VTableSheet(container, {
88+
// Global policy: Allow copy, disable cut/paste
89+
keyboardShortcutPolicy: {
90+
copySelected: true,
91+
cutSelected: false,
92+
pasteValueToCell: false
93+
},
94+
sheets: [
95+
{
96+
sheetKey: 'sheet1',
97+
data: data1
98+
},
99+
{
100+
sheetKey: 'sheet2',
101+
data: data2,
102+
// Sheet-level policy: Allow delete range
103+
keyboardShortcutPolicy: {
104+
deleteRange: true
105+
}
106+
}
107+
]
108+
});
109+
```

docs/assets/option/zh/table/sheet.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@
4444

4545
是否是当前活动工作表。
4646

47+
### editable(boolean)
48+
49+
是否允许编辑该工作表。
50+
- `true`: 允许编辑(默认)。
51+
- `false`: 只读模式。
52+
**优先级**:此配置优先级高于全局 `IVTableSheetOptions.editable`
53+
54+
### keyboardShortcutPolicy(SheetKeyboardShortcutPolicy)
55+
56+
该工作表的快捷键策略配置。可用于覆盖全局的快捷键策略。
57+
具体配置项参考全局配置中的 `keyboardShortcutPolicy`
58+
4759
### cellMerge(CustomMergeCellArray)
4860

4961
单元格合并配置,格式为:
@@ -184,6 +196,27 @@ rowHeightConfig: {
184196
## defaultColWidth(number) = 100
185197
默认列宽。
186198

199+
## editable(boolean) = true
200+
201+
全局编辑能力开关。
202+
- `true`: 默认所有工作表可编辑(除非工作表单独配置为只读)。
203+
- `false`: 默认所有工作表只读(除非工作表单独配置为可编辑)。
204+
只读模式下:
205+
- 双击和按键无法进入编辑状态。
206+
- 剪切、粘贴等修改性快捷键被禁用。
207+
- Delete/Backspace 无法清空单元格。
208+
- 右键菜单中的修改项被隐藏。
209+
210+
## keyboardShortcutPolicy(SheetKeyboardShortcutPolicy)
211+
212+
全局快捷键策略配置。定义了允许或禁用的快捷键行为。
213+
常用属性:
214+
- `copySelected` (boolean): 是否允许复制 (Ctrl+C)。默认 true。
215+
- `cutSelected` (boolean): 是否允许剪切 (Ctrl+X)。只读模式下强制 false。
216+
- `pasteValueToCell` (boolean): 是否允许粘贴 (Ctrl+V)。只读模式下强制 false。
217+
- `selectAllOnCtrlA` (boolean): 是否允许全选 (Ctrl+A)。默认 true。
218+
- `deleteRange` (boolean): 是否允许 Delete/Backspace 清空选区。只读模式下强制 false。
219+
187220
## dragOrder(Object)
188221

189222
拖拽列顺序和行顺序配置。

packages/vtable-plugins/src/context-menu.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,10 @@ export class ContextMenuPlugin implements pluginsDefinition.IVTablePlugin {
143143
if (this.pluginOptions.beforeShowAdjustMenuItems) {
144144
menuItems = this.pluginOptions.beforeShowAdjustMenuItems(menuItems, table as ListTable, col, row);
145145
}
146-
147-
// 显示右键菜单
148-
this.showContextMenu(menuItems, mouseX, mouseY, col, row);
146+
if (menuItems.length > 0) {
147+
// 显示右键菜单
148+
this.showContextMenu(menuItems, mouseX, mouseY, col, row);
149+
}
149150
}
150151
};
151152

@@ -175,9 +176,10 @@ export class ContextMenuPlugin implements pluginsDefinition.IVTablePlugin {
175176
if (this.pluginOptions.beforeShowAdjustMenuItems) {
176177
menuItems = this.pluginOptions.beforeShowAdjustMenuItems(menuItems, table as ListTable, colIndex, rowIndex);
177178
}
178-
179-
// 显示右键菜单
180-
this.showContextMenu(menuItems, mouseX, mouseY, colIndex, rowIndex);
179+
if (menuItems.length > 0) {
180+
// 显示右键菜单
181+
this.showContextMenu(menuItems, mouseX, mouseY, colIndex, rowIndex);
182+
}
181183
}
182184
};
183185

packages/vtable-plugins/src/excel-edit-cell-keyboard.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,30 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu
7373
// this.pluginOptions?.keyDown_before?.(event);
7474
if (this.table?.editorManager && this.isExcelShortcutKey(event)) {
7575
const eventKey = event.key.toLowerCase() as ExcelEditCellKeyboardResponse;
76-
//判断是键盘触发编辑单元格的情况下,那么在编辑状态中切换方向需要选中下一个继续编辑
76+
77+
let editable = true;
78+
let deleteRangeEnabled = true;
79+
80+
const vtableSheet = (this.table as any).__vtableSheet;
81+
const activeSheet = vtableSheet?.getActiveSheet?.();
82+
const sheetOptions = activeSheet?.options as
83+
| { editable?: boolean; keyboardShortcutPolicy?: { deleteRange?: boolean } }
84+
| undefined;
85+
86+
if (sheetOptions) {
87+
editable = sheetOptions.editable !== false;
88+
deleteRangeEnabled = sheetOptions.keyboardShortcutPolicy?.deleteRange ?? true;
89+
}
90+
91+
const isDeleteKey =
92+
eventKey === ExcelEditCellKeyboardResponse.DELETE || eventKey === ExcelEditCellKeyboardResponse.BACKSPACE;
93+
94+
if (isDeleteKey && (!editable || !deleteRangeEnabled)) {
95+
event.stopPropagation();
96+
event.preventDefault();
97+
return;
98+
}
99+
77100
if (this.table.editorManager.editingEditor && this.table.editorManager.beginTriggerEditCellMode === 'keydown') {
78101
const { col, row } = this.table.editorManager.editCell;
79102
if (eventKey !== ExcelEditCellKeyboardResponse.BACKSPACE && eventKey !== ExcelEditCellKeyboardResponse.DELETE) {
@@ -122,6 +145,9 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu
122145
!this.table.editorManager.editingEditor &&
123146
(eventKey === ExcelEditCellKeyboardResponse.DELETE || eventKey === ExcelEditCellKeyboardResponse.BACKSPACE)
124147
) {
148+
if (!deleteRangeEnabled) {
149+
return;
150+
}
125151
//响应删除键,删除
126152
const selectCells = this.table.getSelectedCellInfos();
127153
if (

packages/vtable-sheet/src/components/vtable-sheet.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as VTable from '@visactor/vtable';
55
import { getTablePlugins } from '../core/table-plugins';
66
import { DomEventManager } from '../event/dom-event-manager';
77
import { showSnackbar } from '../tools/ui/snackbar';
8-
import type { IVTableSheetOptions, ISheetDefine } from '../ts-types';
8+
import type { IVTableSheetOptions, ISheetDefine, SheetKeyboardShortcutPolicy } from '../ts-types';
99
import type { MultiSheetImportResult } from '@visactor/vtable-plugins/src/excel-import/types';
1010
import type { TableEventHandlersEventArgumentMap } from '@visactor/vtable/es/ts-types/events';
1111
import SheetTabDragManager from '../managers/tab-drag-manager';
@@ -478,6 +478,18 @@ export default class VTableSheet {
478478
const contentWidth = this.contentElement.clientWidth;
479479
const contentHeight = this.contentElement.clientHeight;
480480
sheetDefine.dragOrder = sheetDefine.dragOrder ?? this.options.dragOrder;
481+
482+
const globalEditable = this.options.editable;
483+
const sheetEditable = sheetDefine.editable;
484+
const effectiveEditable = sheetEditable ?? (globalEditable ?? true);
485+
486+
const effectiveKeyboardPolicy =
487+
(this.options.keyboardShortcutPolicy || sheetDefine.keyboardShortcutPolicy) &&
488+
{
489+
...(this.options.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy),
490+
...(sheetDefine.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy)
491+
};
492+
481493
// 创建sheet实例
482494
const sheet = new WorkSheet(this, {
483495
...sheetDefine,
@@ -488,8 +500,12 @@ export default class VTableSheet {
488500
defaultColWidth: this.options.defaultColWidth,
489501
dragOrder: sheetDefine.dragOrder,
490502
plugins: getTablePlugins(sheetDefine, this.options, this),
491-
headerEditor: 'formula',
492-
editor: 'formula',
503+
...(effectiveEditable
504+
? {
505+
headerEditor: 'formula',
506+
editor: 'formula'
507+
}
508+
: {}),
493509
select: {
494510
makeSelectCellVisible: false
495511
},
@@ -499,9 +515,11 @@ export default class VTableSheet {
499515
borderLineDash: [null, null, null, null],
500516
padding: [8, 8, 8, 8]
501517
},
502-
editCellTrigger: ['api', 'keydown', 'doubleclick'],
518+
editCellTrigger: effectiveEditable ? ['api', 'keydown', 'doubleclick'] : ['api'],
503519
customMergeCell: sheetDefine.cellMerge,
504-
theme: sheetDefine.theme?.tableTheme || this.options.theme?.tableTheme
520+
theme: sheetDefine.theme?.tableTheme || this.options.theme?.tableTheme,
521+
editable: effectiveEditable,
522+
keyboardShortcutPolicy: effectiveKeyboardPolicy as SheetKeyboardShortcutPolicy | undefined
505523
} as any);
506524

507525
// 事件系统现在通过 TableEventRelay 自动处理,不再需要手动绑定

0 commit comments

Comments
 (0)