From f7ec2e9312e1e65e0441bc38ed520e7a74047b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E7=86=B1?= Date: Sat, 18 Jul 2026 21:56:42 +0800 Subject: [PATCH] feat(sheets-data-validation): add quick checkbox and dropdown actions (#7291) --- common/debugger/package.json | 2 +- common/shared/package.json | 4 +- examples/package.json | 2 +- packages/action-recorder/package.json | 2 +- packages/design/package.json | 2 +- packages/docs-hyper-link-ui/package.json | 2 +- packages/docs-quick-insert-ui/package.json | 2 +- packages/docs-thread-comment-ui/package.json | 2 +- packages/docs-ui/package.json | 2 +- packages/drawing-ui/package.json | 2 +- packages/find-replace/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../sheets-crosshair-highlight/package.json | 2 +- .../sheets-data-validation-ui/package.json | 2 +- .../data-validation-ui.command.spec.ts | 156 +++++++++++++++++- .../commands/data-validation-ui.command.ts | 85 +++++++++- .../src/controllers/components.controller.ts | 6 +- .../src/locale/ar-SA.ts | 16 ++ .../src/locale/ca-ES.ts | 16 ++ .../src/locale/de-DE.ts | 16 ++ .../src/locale/en-US.ts | 16 ++ .../src/locale/es-ES.ts | 16 ++ .../src/locale/fa-IR.ts | 16 ++ .../src/locale/fr-FR.ts | 16 ++ .../src/locale/id-ID.ts | 16 ++ .../src/locale/it-IT.ts | 16 ++ .../src/locale/ja-JP.ts | 16 ++ .../src/locale/ko-KR.ts | 16 ++ .../src/locale/pl-PL.ts | 16 ++ .../src/locale/pt-BR.ts | 16 ++ .../src/locale/ru-RU.ts | 16 ++ .../src/locale/sk-SK.ts | 16 ++ .../src/locale/vi-VN.ts | 16 ++ .../src/locale/zh-CN.ts | 16 ++ .../src/locale/zh-HK.ts | 16 ++ .../src/locale/zh-TW.ts | 16 ++ .../src/menu/dv.menu.ts | 110 +++++++++++- .../src/menu/schema.ts | 20 ++- .../src/mobile-plugin.ts | 8 +- .../sheets-data-validation-ui/src/plugin.ts | 8 +- .../src/views/components/DropdownPresets.tsx | 73 ++++++++ .../__tests__/DropdownPresets.spec.tsx | 77 +++++++++ .../__tests__/data-validation.command.spec.ts | 24 +++ .../commands/data-validation.command.ts | 13 +- .../src/models/rule-matrix.ts | 6 +- packages/sheets-filter-ui/package.json | 2 +- packages/sheets-formula-ui/package.json | 2 +- packages/sheets-hyper-link-ui/package.json | 2 +- packages/sheets-note-ui/package.json | 2 +- packages/sheets-numfmt-ui/package.json | 2 +- packages/sheets-sort-ui/package.json | 2 +- packages/sheets-table-ui/package.json | 2 +- .../sheets-thread-comment-ui/package.json | 2 +- packages/sheets-ui/package.json | 2 +- packages/slides-ui/package.json | 2 +- packages/thread-comment-ui/package.json | 2 +- packages/ui/package.json | 2 +- .../src/services/menu/menu-manager.service.ts | 7 +- packages/ui/src/services/menu/menu.ts | 6 +- packages/ui/src/services/menu/types.ts | 1 + .../views/components/ribbon/ToolbarItem.tsx | 7 +- .../ribbon/TooltipButtonWrapper.tsx | 2 +- .../ribbon/__tests__/ToolbarItem.spec.tsx | 122 +++++++++++--- .../__tests__/TooltipButtonWrapper.spec.tsx | 61 +++---- pnpm-lock.yaml | 124 +++++++------- 66 files changed, 1096 insertions(+), 180 deletions(-) create mode 100644 packages/sheets-data-validation-ui/src/views/components/DropdownPresets.tsx create mode 100644 packages/sheets-data-validation-ui/src/views/components/__tests__/DropdownPresets.spec.tsx diff --git a/common/debugger/package.json b/common/debugger/package.json index 60e15b2a94..136e316de4 100644 --- a/common/debugger/package.json +++ b/common/debugger/package.json @@ -35,7 +35,7 @@ "@univerjs/design": "workspace:*", "@univerjs/docs-drawing-ui": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/mockdata": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-drawing-ui": "workspace:*", diff --git a/common/shared/package.json b/common/shared/package.json index a7ac82fc5b..16881ed4e2 100644 --- a/common/shared/package.json +++ b/common/shared/package.json @@ -51,8 +51,8 @@ "vitest": "^4.1.10" }, "devDependencies": { - "@univerjs/icons": "1.24.0", - "@univerjs/icons-svg": "^1.23.0", + "@univerjs/icons": "1.25.0", + "@univerjs/icons-svg": "^1.25.0", "typescript": "^6.0.3", "vue-tsc": "^3.3.6" } diff --git a/examples/package.json b/examples/package.json index 6177025408..ff2922f285 100644 --- a/examples/package.json +++ b/examples/package.json @@ -36,7 +36,7 @@ "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", "@univerjs/find-replace": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/mockdata": "workspace:*", "@univerjs/network": "workspace:*", "@univerjs/preset-docs-core": "workspace:*", diff --git a/packages/action-recorder/package.json b/packages/action-recorder/package.json index f385e221f0..6aca0dffe2 100644 --- a/packages/action-recorder/package.json +++ b/packages/action-recorder/package.json @@ -76,7 +76,7 @@ "dependencies": { "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-filter": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/design/package.json b/packages/design/package.json index 9bbf74903e..7a2d0e77b8 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -81,7 +81,7 @@ "@radix-ui/react-popover": "^1.1.19", "@radix-ui/react-separator": "^1.1.11", "@radix-ui/react-slot": "^1.3.0", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "react-transition-group": "^4.4.5", diff --git a/packages/docs-hyper-link-ui/package.json b/packages/docs-hyper-link-ui/package.json index 0bc14b74e7..9e46b639eb 100644 --- a/packages/docs-hyper-link-ui/package.json +++ b/packages/docs-hyper-link-ui/package.json @@ -80,7 +80,7 @@ "@univerjs/docs-hyper-link": "workspace:*", "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/ui": "workspace:*" }, "devDependencies": { diff --git a/packages/docs-quick-insert-ui/package.json b/packages/docs-quick-insert-ui/package.json index 754d7d3a8b..89ccba6027 100644 --- a/packages/docs-quick-insert-ui/package.json +++ b/packages/docs-quick-insert-ui/package.json @@ -83,7 +83,7 @@ "@univerjs/drawing": "workspace:*", "@univerjs/drawing-ui": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/ui": "workspace:*" }, "devDependencies": { diff --git a/packages/docs-thread-comment-ui/package.json b/packages/docs-thread-comment-ui/package.json index a3f79c7a07..37229226e8 100644 --- a/packages/docs-thread-comment-ui/package.json +++ b/packages/docs-thread-comment-ui/package.json @@ -78,7 +78,7 @@ "@univerjs/docs": "workspace:*", "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/thread-comment": "workspace:*", "@univerjs/thread-comment-ui": "workspace:*", "@univerjs/ui": "workspace:*" diff --git a/packages/docs-ui/package.json b/packages/docs-ui/package.json index ff45ee7bba..6e66361f1c 100644 --- a/packages/docs-ui/package.json +++ b/packages/docs-ui/package.json @@ -89,7 +89,7 @@ "@univerjs/design": "workspace:*", "@univerjs/docs": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/ui": "workspace:*" }, "devDependencies": { diff --git a/packages/drawing-ui/package.json b/packages/drawing-ui/package.json index 2a5f29cd7a..876398c502 100644 --- a/packages/drawing-ui/package.json +++ b/packages/drawing-ui/package.json @@ -78,7 +78,7 @@ "@univerjs/design": "workspace:*", "@univerjs/drawing": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/ui": "workspace:*" }, "devDependencies": { diff --git a/packages/find-replace/package.json b/packages/find-replace/package.json index c1e057958b..ca05f28b2f 100644 --- a/packages/find-replace/package.json +++ b/packages/find-replace/package.json @@ -77,7 +77,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/ui": "workspace:*" }, "devDependencies": { diff --git a/packages/sheets-conditional-formatting-ui/package.json b/packages/sheets-conditional-formatting-ui/package.json index 9237925e9b..0ba26d0e47 100644 --- a/packages/sheets-conditional-formatting-ui/package.json +++ b/packages/sheets-conditional-formatting-ui/package.json @@ -78,7 +78,7 @@ "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-conditional-formatting": "workspace:*", "@univerjs/sheets-formula": "workspace:*", diff --git a/packages/sheets-conditional-formatting/package.json b/packages/sheets-conditional-formatting/package.json index 472ce86d20..ef1c9489da 100644 --- a/packages/sheets-conditional-formatting/package.json +++ b/packages/sheets-conditional-formatting/package.json @@ -92,7 +92,7 @@ }, "devDependencies": { "@univerjs-infra/shared": "workspace:*", - "@univerjs/icons-svg": "^1.23.0", + "@univerjs/icons-svg": "^1.25.0", "@univerjs/sheets-formula": "workspace:*", "rxjs": "^7.8.2", "typescript": "^6.0.3", diff --git a/packages/sheets-crosshair-highlight/package.json b/packages/sheets-crosshair-highlight/package.json index c5a35e63b0..e0f8ffeadf 100644 --- a/packages/sheets-crosshair-highlight/package.json +++ b/packages/sheets-crosshair-highlight/package.json @@ -88,7 +88,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-ui": "workspace:*", "@univerjs/ui": "workspace:*" diff --git a/packages/sheets-data-validation-ui/package.json b/packages/sheets-data-validation-ui/package.json index ab2a2b5385..8ed80caeb8 100644 --- a/packages/sheets-data-validation-ui/package.json +++ b/packages/sheets-data-validation-ui/package.json @@ -80,7 +80,7 @@ "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-data-validation": "workspace:*", "@univerjs/sheets-formula-ui": "workspace:*", diff --git a/packages/sheets-data-validation-ui/src/commands/commands/__tests__/data-validation-ui.command.spec.ts b/packages/sheets-data-validation-ui/src/commands/commands/__tests__/data-validation-ui.command.spec.ts index 82d5c59256..068bf5315f 100644 --- a/packages/sheets-data-validation-ui/src/commands/commands/__tests__/data-validation-ui.command.spec.ts +++ b/packages/sheets-data-validation-ui/src/commands/commands/__tests__/data-validation-ui.command.spec.ts @@ -27,6 +27,7 @@ import type { ISidebarMethodOptions } from '@univerjs/ui'; import { CommandType, DataValidationOperator, + DataValidationRenderMode, DataValidationType, ICommandService, Inject, @@ -39,14 +40,42 @@ import { UniverInstanceType, } from '@univerjs/core'; import { DataValidationModel } from '@univerjs/data-validation'; -import { SheetsSelectionsService } from '@univerjs/sheets'; -import { AddSheetDataValidationCommand } from '@univerjs/sheets-data-validation'; +import { serializeListOptions, SheetsSelectionsService } from '@univerjs/sheets'; +import { AddSheetDataValidationCommand, ClearRangeDataValidationCommand } from '@univerjs/sheets-data-validation'; import { ISidebarService } from '@univerjs/ui'; import { BehaviorSubject } from 'rxjs'; import { afterEach, describe, expect, it } from 'vitest'; import { DataValidationPanelService } from '../../../services/data-validation-panel.service'; import { DATA_VALIDATION_PANEL, OpenValidationPanelOperation } from '../../operations/data-validation.operation'; -import { AddSheetDataValidationAndOpenCommand } from '../data-validation-ui.command'; +import { + AddSheetDataValidationAndOpenCommand, + ClearQuickSheetDataValidationCommand, + InsertQuickSheetDataValidationCommand, +} from '../data-validation-ui.command'; + +const SELECTION_RANGE = { + startRow: 1, + endRow: 2, + startColumn: 3, + endColumn: 4, +}; + +const CHECKBOX_RULE = { + type: DataValidationType.CHECKBOX, + operator: undefined, + formula1: undefined, + formula2: undefined, +}; + +function createDropdownRule(formula1: string) { + return { + type: DataValidationType.LIST, + operator: undefined, + formula1, + formula2: '', + renderMode: DataValidationRenderMode.CUSTOM, + }; +} interface ITestBed { univer: Univer; @@ -139,6 +168,8 @@ function createTestBed(createWorkbook = true): ITestBed { const commandService = injector.get(ICommandService); commandService.registerCommand(AddSheetDataValidationAndOpenCommand); + commandService.registerCommand(InsertQuickSheetDataValidationCommand); + commandService.registerCommand(ClearQuickSheetDataValidationCommand); commandService.registerCommand(OpenValidationPanelOperation); commandService.registerCommand({ id: AddSheetDataValidationCommand.id, @@ -156,12 +187,24 @@ function createTestBed(createWorkbook = true): ITestBed { return true; }, } as ICommand); + commandService.registerCommand({ + id: ClearRangeDataValidationCommand.id, + type: CommandType.COMMAND, + handler: (_accessor, params) => { + executedCommands.push({ id: ClearRangeDataValidationCommand.id, params }); + return true; + }, + } as ICommand); const workbook = createWorkbook ? univer.createUnit(UniverInstanceType.UNIVER_SHEET, createWorkbookData()) : undefined; if (workbook) { injector.get(IUniverInstanceService).focusUnit(workbook.getUnitId()); + injector.get(SheetsSelectionsService).setSelections(workbook.getUnitId(), 'sheet-1', [{ + range: SELECTION_RANGE, + primary: null, + }] as never); } return { @@ -209,6 +252,113 @@ describe('AddSheetDataValidationAndOpenCommand', () => { }); }); + it('inserts the supplied checkbox rule into the current selection', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + + expect(commandService.syncExecuteCommand(InsertQuickSheetDataValidationCommand.id, { rule: CHECKBOX_RULE })).toBe(true); + + const addParams = testBed.executedCommands[0].params as { rule: IDataValidationRule }; + expect(addParams.rule).toMatchObject({ + type: DataValidationType.CHECKBOX, + ranges: [SELECTION_RANGE], + }); + expect(addParams.rule.operator).toBeUndefined(); + expect(addParams.rule.formula1).toBeUndefined(); + expect(addParams.rule.formula2).toBeUndefined(); + }); + + it('inserts a custom dropdown rule into the current selection', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + const formula1 = serializeListOptions(['Yes', 'No']); + + expect(commandService.syncExecuteCommand(InsertQuickSheetDataValidationCommand.id, { rule: createDropdownRule(formula1) })).toBe(true); + + const addParams = testBed.executedCommands[0].params as { rule: IDataValidationRule }; + expect(addParams.rule).toMatchObject({ + type: DataValidationType.LIST, + formula1, + formula2: '', + renderMode: DataValidationRenderMode.CUSTOM, + ranges: [SELECTION_RANGE], + }); + expect(addParams.rule.operator).toBeUndefined(); + }); + + it('adds a dropdown rule and opens the existing panel to edit it', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + const panelService = testBed.get(DataValidationPanelService); + const formula1 = serializeListOptions(['Option 1', 'Option 2']); + + expect(commandService.syncExecuteCommand(AddSheetDataValidationAndOpenCommand.id, { rule: createDropdownRule(formula1) })).toBe(true); + + const addParams = testBed.executedCommands[0].params as { rule: IDataValidationRule }; + expect(addParams.rule).toMatchObject({ + type: DataValidationType.LIST, + formula1, + formula2: '', + renderMode: DataValidationRenderMode.CUSTOM, + ranges: [SELECTION_RANGE], + }); + expect(panelService.activeRule?.rule.uid).toBe(addParams.rule.uid); + expect(panelService.isOpen).toBe(true); + }); + + it('clears only dropdown validation types from the current selection', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + + expect(commandService.syncExecuteCommand(ClearQuickSheetDataValidationCommand.id, { + types: [DataValidationType.LIST, DataValidationType.LIST_MULTIPLE], + })).toBe(true); + + expect(testBed.executedCommands).toEqual([{ + id: ClearRangeDataValidationCommand.id, + params: { + unitId: 'book-1', + subUnitId: 'sheet-1', + ranges: [SELECTION_RANGE], + types: [DataValidationType.LIST, DataValidationType.LIST_MULTIPLE], + }, + }]); + }); + + it('clears only checkbox validation from the current selection', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + + expect(commandService.syncExecuteCommand(ClearQuickSheetDataValidationCommand.id, { + types: [DataValidationType.CHECKBOX], + })).toBe(true); + + expect(testBed.executedCommands[0]).toEqual({ + id: ClearRangeDataValidationCommand.id, + params: { + unitId: 'book-1', + subUnitId: 'sheet-1', + ranges: [SELECTION_RANGE], + types: [DataValidationType.CHECKBOX], + }, + }); + }); + + it('does not insert, edit, or clear without a current selection', () => { + testBed = createTestBed(); + const commandService = testBed.get(ICommandService); + testBed.get(SheetsSelectionsService).clearCurrentSelections(); + + expect(commandService.syncExecuteCommand(InsertQuickSheetDataValidationCommand.id, { rule: CHECKBOX_RULE })).toBe(false); + expect(commandService.syncExecuteCommand(AddSheetDataValidationAndOpenCommand.id, { + rule: createDropdownRule(serializeListOptions(['Option 1', 'Option 2'])), + })).toBe(false); + expect(commandService.syncExecuteCommand(ClearQuickSheetDataValidationCommand.id, { + types: [DataValidationType.LIST, DataValidationType.LIST_MULTIPLE], + })).toBe(false); + expect(testBed.executedCommands).toEqual([]); + }); + it('does not add or open when there is no active sheet target', () => { testBed = createTestBed(false); const commandService = testBed.get(ICommandService); diff --git a/packages/sheets-data-validation-ui/src/commands/commands/data-validation-ui.command.ts b/packages/sheets-data-validation-ui/src/commands/commands/data-validation-ui.command.ts index 3d3b53bb5d..e519a61aaf 100644 --- a/packages/sheets-data-validation-ui/src/commands/commands/data-validation-ui.command.ts +++ b/packages/sheets-data-validation-ui/src/commands/commands/data-validation-ui.command.ts @@ -14,23 +14,94 @@ * limitations under the License. */ -import type { ICommand } from '@univerjs/core'; +import type { DataValidationType, IAccessor, ICommand, ISheetDataValidationRule } from '@univerjs/core'; import type { IAddSheetDataValidationCommandParams } from '@univerjs/sheets-data-validation'; -import { CommandType, ICommandService, IUniverInstanceService } from '@univerjs/core'; -import { getSheetCommandTarget } from '@univerjs/sheets'; -import { AddSheetDataValidationCommand, createDefaultNewRule } from '@univerjs/sheets-data-validation'; +import { + CommandType, + ICommandService, + IUniverInstanceService, +} from '@univerjs/core'; +import { getSheetCommandTarget, SheetsSelectionsService } from '@univerjs/sheets'; +import { + AddSheetDataValidationCommand, + ClearRangeDataValidationCommand, + createDefaultNewRule, +} from '@univerjs/sheets-data-validation'; import { OpenValidationPanelOperation } from '../operations/data-validation.operation'; -export const AddSheetDataValidationAndOpenCommand: ICommand = { +export type IQuickDataValidationRuleInput = Pick & Partial>; + +interface IInsertQuickSheetDataValidationCommandParams { + rule: IQuickDataValidationRuleInput; +} + +interface IClearQuickSheetDataValidationCommandParams { + types: DataValidationType[]; +} + +interface IAddSheetDataValidationAndOpenCommandParams { + rule?: IQuickDataValidationRuleInput; +} + +function createRule(accessor: IAccessor, rule?: IQuickDataValidationRuleInput): ISheetDataValidationRule { + const defaultRule = createDefaultNewRule(accessor); + + return { + ...defaultRule, + ...rule, + uid: defaultRule.uid, + ranges: defaultRule.ranges, + }; +} + +export const InsertQuickSheetDataValidationCommand: ICommand = { + type: CommandType.COMMAND, + id: 'data-validation.command.insert-quick-rule', + handler(accessor, params) { + const target = getSheetCommandTarget(accessor.get(IUniverInstanceService)); + const ranges = accessor.get(SheetsSelectionsService).getCurrentSelections().map((selection) => selection.range); + if (!target || !ranges.length || !params?.rule) return false; + + const rule = createRule(accessor, params.rule); + + return accessor.get(ICommandService).syncExecuteCommand(AddSheetDataValidationCommand.id, { + unitId: target.workbook.getUnitId(), + subUnitId: target.worksheet.getSheetId(), + rule, + }); + }, +}; + +export const ClearQuickSheetDataValidationCommand: ICommand = { + type: CommandType.COMMAND, + id: 'data-validation.command.clear-quick-rule', + handler(accessor, params) { + const target = getSheetCommandTarget(accessor.get(IUniverInstanceService)); + const ranges = accessor.get(SheetsSelectionsService).getCurrentSelections().map((selection) => selection.range); + if (!target || !ranges.length || !params?.types?.length) return false; + + return accessor.get(ICommandService).syncExecuteCommand(ClearRangeDataValidationCommand.id, { + unitId: target.workbook.getUnitId(), + subUnitId: target.worksheet.getSheetId(), + ranges, + types: params.types, + }); + }, +}; + +export const AddSheetDataValidationAndOpenCommand: ICommand = { type: CommandType.COMMAND, id: 'data-validation.command.addRuleAndOpen', - handler(accessor) { + handler(accessor, params) { const univerInstanceService = accessor.get(IUniverInstanceService); const target = getSheetCommandTarget(univerInstanceService); if (!target) return false; const { workbook, worksheet } = target; - const rule = createDefaultNewRule(accessor); + const ranges = accessor.get(SheetsSelectionsService).getCurrentSelections(); + if (!ranges.length) return false; + + const rule = createRule(accessor, params?.rule); const commandService = accessor.get(ICommandService); const unitId = workbook.getUnitId(); const subUnitId = worksheet.getSheetId(); diff --git a/packages/sheets-data-validation-ui/src/controllers/components.controller.ts b/packages/sheets-data-validation-ui/src/controllers/components.controller.ts index 27603a34bd..bfbf107b3e 100644 --- a/packages/sheets-data-validation-ui/src/controllers/components.controller.ts +++ b/packages/sheets-data-validation-ui/src/controllers/components.controller.ts @@ -15,11 +15,12 @@ */ import { Disposable, Inject } from '@univerjs/core'; -import { DataValidationIcon } from '@univerjs/icons'; +import { CheckboxIcon, DataValidationIcon, DropdownListIcon } from '@univerjs/icons'; import { ComponentManager, IconManager } from '@univerjs/ui'; import { DATA_VALIDATION_PANEL } from '../commands/operations/data-validation.operation'; import { DataValidationPanel } from '../views/components'; import { DateShowTimeOption } from '../views/components/DateShowTimeOption'; +import { DROPDOWN_PRESETS_COMPONENT, DropdownPresets } from '../views/components/DropdownPresets'; import { FORMULA_INPUTS } from '../views/components/formula-input'; import { ListRenderModeInput } from '../views/components/ListRenderModeInput'; @@ -36,7 +37,9 @@ export class ComponentsController extends Disposable { private _registerIcons(): void { this.disposeWithMe(this._iconManager.register({ + CheckboxIcon, DataValidationIcon, + DropdownListIcon, })); } @@ -45,6 +48,7 @@ export class ComponentsController extends Disposable { [DATA_VALIDATION_PANEL, DataValidationPanel], [ListRenderModeInput.componentKey, ListRenderModeInput], [DateShowTimeOption.componentKey, DateShowTimeOption], + [DROPDOWN_PRESETS_COMPONENT, DropdownPresets], ...FORMULA_INPUTS, ] as const).forEach(([key, component]) => { this.disposeWithMe(this._componentManager.register(key, component)); diff --git a/packages/sheets-data-validation-ui/src/locale/ar-SA.ts b/packages/sheets-data-validation-ui/src/locale/ar-SA.ts index 897f22e1ac..3dc55827da 100644 --- a/packages/sheets-data-validation-ui/src/locale/ar-SA.ts +++ b/packages/sheets-data-validation-ui/src/locale/ar-SA.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'التحقق من صحة البيانات', + ribbon: { + setCheckbox: 'تعيين خانة اختيار', + clearCheckbox: 'مسح خانة الاختيار', + dropdownPresetTitle: 'تطبيق إعداد مسبق:', + editDropdown: 'تحرير الخيارات', + clearDropdown: 'مسح القائمة المنسدلة', + presets: { + yes: 'نعم', + no: 'لا', + notStarted: 'لم يبدأ', + inProgress: 'قيد التنفيذ', + completed: 'مكتمل', + option1: 'الخيار 1', + option2: 'الخيار 2', + }, + }, operators: { legal: 'نوع قانوني', }, diff --git a/packages/sheets-data-validation-ui/src/locale/ca-ES.ts b/packages/sheets-data-validation-ui/src/locale/ca-ES.ts index a94fbb8b1f..6f5ffaed6b 100644 --- a/packages/sheets-data-validation-ui/src/locale/ca-ES.ts +++ b/packages/sheets-data-validation-ui/src/locale/ca-ES.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Validació de dades', + ribbon: { + setCheckbox: 'Defineix la casella de selecció', + clearCheckbox: 'Esborra la casella de selecció', + dropdownPresetTitle: 'Aplica una predefinició:', + editDropdown: 'Edita les opcions', + clearDropdown: 'Esborra el menú desplegable', + presets: { + yes: 'Sí', + no: 'No', + notStarted: 'No iniciat', + inProgress: 'En curs', + completed: 'Completat', + option1: 'Opció 1', + option2: 'Opció 2', + }, + }, operators: { legal: 'és tipus legal', }, diff --git a/packages/sheets-data-validation-ui/src/locale/de-DE.ts b/packages/sheets-data-validation-ui/src/locale/de-DE.ts index 37e5f6724f..54fed4d187 100644 --- a/packages/sheets-data-validation-ui/src/locale/de-DE.ts +++ b/packages/sheets-data-validation-ui/src/locale/de-DE.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Datenüberprüfung', + ribbon: { + setCheckbox: 'Kontrollkästchen festlegen', + clearCheckbox: 'Kontrollkästchen löschen', + dropdownPresetTitle: 'Vorlage anwenden:', + editDropdown: 'Optionen bearbeiten', + clearDropdown: 'Dropdown löschen', + presets: { + yes: 'Ja', + no: 'Nein', + notStarted: 'Nicht begonnen', + inProgress: 'In Bearbeitung', + completed: 'Abgeschlossen', + option1: 'Option 1', + option2: 'Option 2', + }, + }, operators: { legal: 'ist gültiger Typ', }, diff --git a/packages/sheets-data-validation-ui/src/locale/en-US.ts b/packages/sheets-data-validation-ui/src/locale/en-US.ts index 5ba09c7df6..edc8f577d9 100644 --- a/packages/sheets-data-validation-ui/src/locale/en-US.ts +++ b/packages/sheets-data-validation-ui/src/locale/en-US.ts @@ -17,6 +17,22 @@ const locale = { 'sheets-data-validation-ui': { title: 'Data validation', + ribbon: { + setCheckbox: 'Set Checkbox', + clearCheckbox: 'Clear Checkbox', + dropdownPresetTitle: 'Apply a preset:', + editDropdown: 'Edit options', + clearDropdown: 'Clear Dropdown', + presets: { + yes: 'Yes', + no: 'No', + notStarted: 'Not Started', + inProgress: 'In Progress', + completed: 'Completed', + option1: 'Option 1', + option2: 'Option 2', + }, + }, operators: { legal: 'is legal type', }, diff --git a/packages/sheets-data-validation-ui/src/locale/es-ES.ts b/packages/sheets-data-validation-ui/src/locale/es-ES.ts index ca4a003798..a9e2e2b80a 100644 --- a/packages/sheets-data-validation-ui/src/locale/es-ES.ts +++ b/packages/sheets-data-validation-ui/src/locale/es-ES.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Validación de datos', + ribbon: { + setCheckbox: 'Establecer casilla de verificación', + clearCheckbox: 'Borrar casilla de verificación', + dropdownPresetTitle: 'Aplicar un preajuste:', + editDropdown: 'Editar opciones', + clearDropdown: 'Borrar lista desplegable', + presets: { + yes: 'Sí', + no: 'No', + notStarted: 'No iniciado', + inProgress: 'En curso', + completed: 'Completado', + option1: 'Opción 1', + option2: 'Opción 2', + }, + }, operators: { legal: 'es tipo legal', }, diff --git a/packages/sheets-data-validation-ui/src/locale/fa-IR.ts b/packages/sheets-data-validation-ui/src/locale/fa-IR.ts index 7cf9915560..509e0dd7aa 100644 --- a/packages/sheets-data-validation-ui/src/locale/fa-IR.ts +++ b/packages/sheets-data-validation-ui/src/locale/fa-IR.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'اعتبارسنجی داده', + ribbon: { + setCheckbox: 'تنظیم کادر انتخاب', + clearCheckbox: 'پاک کردن کادر انتخاب', + dropdownPresetTitle: 'اعمال یک پیش‌تنظیم:', + editDropdown: 'ویرایش گزینه‌ها', + clearDropdown: 'پاک کردن فهرست کشویی', + presets: { + yes: 'بله', + no: 'خیر', + notStarted: 'شروع نشده', + inProgress: 'در حال انجام', + completed: 'تکمیل‌شده', + option1: 'گزینه ۱', + option2: 'گزینه ۲', + }, + }, operators: { legal: 'نوع قانونی است', }, diff --git a/packages/sheets-data-validation-ui/src/locale/fr-FR.ts b/packages/sheets-data-validation-ui/src/locale/fr-FR.ts index a39ee2c86c..8b496e5d7f 100644 --- a/packages/sheets-data-validation-ui/src/locale/fr-FR.ts +++ b/packages/sheets-data-validation-ui/src/locale/fr-FR.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Validation des données', + ribbon: { + setCheckbox: 'Définir la case à cocher', + clearCheckbox: 'Effacer la case à cocher', + dropdownPresetTitle: 'Appliquer un préréglage :', + editDropdown: 'Modifier les options', + clearDropdown: 'Effacer la liste déroulante', + presets: { + yes: 'Oui', + no: 'Non', + notStarted: 'Non commencé', + inProgress: 'En cours', + completed: 'Terminé', + option1: 'Option 1', + option2: 'Option 2', + }, + }, operators: { legal: 'est de type légal', }, diff --git a/packages/sheets-data-validation-ui/src/locale/id-ID.ts b/packages/sheets-data-validation-ui/src/locale/id-ID.ts index 306cedce72..c819a554eb 100644 --- a/packages/sheets-data-validation-ui/src/locale/id-ID.ts +++ b/packages/sheets-data-validation-ui/src/locale/id-ID.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Validasi data', + ribbon: { + setCheckbox: 'Atur Kotak Centang', + clearCheckbox: 'Hapus Kotak Centang', + dropdownPresetTitle: 'Terapkan preset:', + editDropdown: 'Edit opsi', + clearDropdown: 'Hapus Dropdown', + presets: { + yes: 'Ya', + no: 'Tidak', + notStarted: 'Belum Dimulai', + inProgress: 'Sedang Berlangsung', + completed: 'Selesai', + option1: 'Opsi 1', + option2: 'Opsi 2', + }, + }, operators: { legal: 'tipe yang sah', }, diff --git a/packages/sheets-data-validation-ui/src/locale/it-IT.ts b/packages/sheets-data-validation-ui/src/locale/it-IT.ts index b92d9bec96..fc5f7b04b8 100644 --- a/packages/sheets-data-validation-ui/src/locale/it-IT.ts +++ b/packages/sheets-data-validation-ui/src/locale/it-IT.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Convalida dati', + ribbon: { + setCheckbox: 'Imposta casella di controllo', + clearCheckbox: 'Cancella casella di controllo', + dropdownPresetTitle: 'Applica una preimpostazione:', + editDropdown: 'Modifica opzioni', + clearDropdown: 'Cancella elenco a discesa', + presets: { + yes: 'Sì', + no: 'No', + notStarted: 'Non iniziato', + inProgress: 'In corso', + completed: 'Completato', + option1: 'Opzione 1', + option2: 'Opzione 2', + }, + }, operators: { legal: 'è un tipo valido', }, diff --git a/packages/sheets-data-validation-ui/src/locale/ja-JP.ts b/packages/sheets-data-validation-ui/src/locale/ja-JP.ts index 6c21f5baf7..31c1a0e2c3 100644 --- a/packages/sheets-data-validation-ui/src/locale/ja-JP.ts +++ b/packages/sheets-data-validation-ui/src/locale/ja-JP.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'データの入力規則', + ribbon: { + setCheckbox: 'チェックボックスを設定', + clearCheckbox: 'チェックボックスをクリア', + dropdownPresetTitle: 'プリセットを適用:', + editDropdown: 'オプションを編集', + clearDropdown: 'プルダウンをクリア', + presets: { + yes: 'はい', + no: 'いいえ', + notStarted: '未着手', + inProgress: '進行中', + completed: '完了', + option1: 'オプション 1', + option2: 'オプション 2', + }, + }, operators: { legal: '有効な形式', }, diff --git a/packages/sheets-data-validation-ui/src/locale/ko-KR.ts b/packages/sheets-data-validation-ui/src/locale/ko-KR.ts index 304ef29ba8..b5127d9dc7 100644 --- a/packages/sheets-data-validation-ui/src/locale/ko-KR.ts +++ b/packages/sheets-data-validation-ui/src/locale/ko-KR.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: '데이터 유효성 검사', + ribbon: { + setCheckbox: '체크박스 설정', + clearCheckbox: '체크박스 지우기', + dropdownPresetTitle: '프리셋 적용:', + editDropdown: '옵션 편집', + clearDropdown: '드롭다운 지우기', + presets: { + yes: '예', + no: '아니요', + notStarted: '시작 전', + inProgress: '진행 중', + completed: '완료', + option1: '옵션 1', + option2: '옵션 2', + }, + }, operators: { legal: '유효한 형식', }, diff --git a/packages/sheets-data-validation-ui/src/locale/pl-PL.ts b/packages/sheets-data-validation-ui/src/locale/pl-PL.ts index 16e1cfa42c..aadbd24aec 100644 --- a/packages/sheets-data-validation-ui/src/locale/pl-PL.ts +++ b/packages/sheets-data-validation-ui/src/locale/pl-PL.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Sprawdzanie poprawności danych', + ribbon: { + setCheckbox: 'Ustaw pole wyboru', + clearCheckbox: 'Wyczyść pole wyboru', + dropdownPresetTitle: 'Zastosuj ustawienie wstępne:', + editDropdown: 'Edytuj opcje', + clearDropdown: 'Wyczyść listę rozwijaną', + presets: { + yes: 'Tak', + no: 'Nie', + notStarted: 'Nierozpoczęte', + inProgress: 'W toku', + completed: 'Ukończone', + option1: 'Opcja 1', + option2: 'Opcja 2', + }, + }, operators: { legal: 'jest prawidłowym typem', }, diff --git a/packages/sheets-data-validation-ui/src/locale/pt-BR.ts b/packages/sheets-data-validation-ui/src/locale/pt-BR.ts index 6c3420e7ef..b435bb8b32 100644 --- a/packages/sheets-data-validation-ui/src/locale/pt-BR.ts +++ b/packages/sheets-data-validation-ui/src/locale/pt-BR.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Validação de dados', + ribbon: { + setCheckbox: 'Definir caixa de seleção', + clearCheckbox: 'Limpar caixa de seleção', + dropdownPresetTitle: 'Aplicar predefinição:', + editDropdown: 'Editar opções', + clearDropdown: 'Limpar lista suspensa', + presets: { + yes: 'Sim', + no: 'Não', + notStarted: 'Não iniciado', + inProgress: 'Em andamento', + completed: 'Concluído', + option1: 'Opção 1', + option2: 'Opção 2', + }, + }, operators: { legal: 'é tipo válido', }, diff --git a/packages/sheets-data-validation-ui/src/locale/ru-RU.ts b/packages/sheets-data-validation-ui/src/locale/ru-RU.ts index c039bee3aa..c8facfb449 100644 --- a/packages/sheets-data-validation-ui/src/locale/ru-RU.ts +++ b/packages/sheets-data-validation-ui/src/locale/ru-RU.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Проверка данных', + ribbon: { + setCheckbox: 'Установить флажок', + clearCheckbox: 'Очистить флажок', + dropdownPresetTitle: 'Применить шаблон:', + editDropdown: 'Изменить варианты', + clearDropdown: 'Очистить раскрывающийся список', + presets: { + yes: 'Да', + no: 'Нет', + notStarted: 'Не начато', + inProgress: 'В процессе', + completed: 'Завершено', + option1: 'Вариант 1', + option2: 'Вариант 2', + }, + }, operators: { legal: 'является допустимым типом', }, diff --git a/packages/sheets-data-validation-ui/src/locale/sk-SK.ts b/packages/sheets-data-validation-ui/src/locale/sk-SK.ts index b3449bd1fe..e8b952f635 100644 --- a/packages/sheets-data-validation-ui/src/locale/sk-SK.ts +++ b/packages/sheets-data-validation-ui/src/locale/sk-SK.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Overenie údajov', + ribbon: { + setCheckbox: 'Nastaviť začiarkavacie políčko', + clearCheckbox: 'Vymazať začiarkavacie políčko', + dropdownPresetTitle: 'Použiť predvoľbu:', + editDropdown: 'Upraviť možnosti', + clearDropdown: 'Vymazať rozbaľovací zoznam', + presets: { + yes: 'Áno', + no: 'Nie', + notStarted: 'Nezačaté', + inProgress: 'Prebieha', + completed: 'Dokončené', + option1: 'Možnosť 1', + option2: 'Možnosť 2', + }, + }, operators: { legal: 'je platný typ', }, diff --git a/packages/sheets-data-validation-ui/src/locale/vi-VN.ts b/packages/sheets-data-validation-ui/src/locale/vi-VN.ts index 67f63d8762..f5102ae3af 100644 --- a/packages/sheets-data-validation-ui/src/locale/vi-VN.ts +++ b/packages/sheets-data-validation-ui/src/locale/vi-VN.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: 'Xác thực dữ liệu', + ribbon: { + setCheckbox: 'Đặt hộp kiểm', + clearCheckbox: 'Xóa hộp kiểm', + dropdownPresetTitle: 'Áp dụng mẫu:', + editDropdown: 'Chỉnh sửa tùy chọn', + clearDropdown: 'Xóa danh sách thả xuống', + presets: { + yes: 'Có', + no: 'Không', + notStarted: 'Chưa bắt đầu', + inProgress: 'Đang thực hiện', + completed: 'Hoàn thành', + option1: 'Tùy chọn 1', + option2: 'Tùy chọn 2', + }, + }, operators: { legal: 'là kiểu hợp lệ', }, diff --git a/packages/sheets-data-validation-ui/src/locale/zh-CN.ts b/packages/sheets-data-validation-ui/src/locale/zh-CN.ts index 0e94def32e..a9a3b03c5a 100644 --- a/packages/sheets-data-validation-ui/src/locale/zh-CN.ts +++ b/packages/sheets-data-validation-ui/src/locale/zh-CN.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: '数据验证', + ribbon: { + setCheckbox: '设置复选框', + clearCheckbox: '清除复选框', + dropdownPresetTitle: '应用预设:', + editDropdown: '编辑选项', + clearDropdown: '清除下拉菜单', + presets: { + yes: '是', + no: '否', + notStarted: '未开始', + inProgress: '进行中', + completed: '已完成', + option1: '选项 1', + option2: '选项 2', + }, + }, operators: { legal: '是合法类型', }, diff --git a/packages/sheets-data-validation-ui/src/locale/zh-HK.ts b/packages/sheets-data-validation-ui/src/locale/zh-HK.ts index 8746b25881..2c464a3a0a 100644 --- a/packages/sheets-data-validation-ui/src/locale/zh-HK.ts +++ b/packages/sheets-data-validation-ui/src/locale/zh-HK.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: '資料驗證', + ribbon: { + setCheckbox: '設定核取方塊', + clearCheckbox: '清除核取方塊', + dropdownPresetTitle: '套用預設:', + editDropdown: '編輯選項', + clearDropdown: '清除下拉式選單', + presets: { + yes: '是', + no: '否', + notStarted: '未開始', + inProgress: '進行中', + completed: '已完成', + option1: '選項 1', + option2: '選項 2', + }, + }, operators: { legal: '是合法類型', }, diff --git a/packages/sheets-data-validation-ui/src/locale/zh-TW.ts b/packages/sheets-data-validation-ui/src/locale/zh-TW.ts index 8746b25881..3880891d0f 100644 --- a/packages/sheets-data-validation-ui/src/locale/zh-TW.ts +++ b/packages/sheets-data-validation-ui/src/locale/zh-TW.ts @@ -19,6 +19,22 @@ import type enUS from './en-US'; const locale: typeof enUS = { 'sheets-data-validation-ui': { title: '資料驗證', + ribbon: { + setCheckbox: '設定核取方塊', + clearCheckbox: '清除核取方塊', + dropdownPresetTitle: '套用預設:', + editDropdown: '編輯選項', + clearDropdown: '清除下拉式選單', + presets: { + yes: '是', + no: '否', + notStarted: '尚未開始', + inProgress: '進行中', + completed: '已完成', + option1: '選項 1', + option2: '選項 2', + }, + }, operators: { legal: '是合法類型', }, diff --git a/packages/sheets-data-validation-ui/src/menu/dv.menu.ts b/packages/sheets-data-validation-ui/src/menu/dv.menu.ts index 14b863f647..99f1720a0b 100644 --- a/packages/sheets-data-validation-ui/src/menu/dv.menu.ts +++ b/packages/sheets-data-validation-ui/src/menu/dv.menu.ts @@ -15,21 +15,61 @@ */ import type { IAccessor } from '@univerjs/core'; -import type { IMenuItem } from '@univerjs/ui'; +import type { IMenuItem, IMenuSelectorItem } from '@univerjs/ui'; +import type { IQuickDataValidationRuleInput } from '../commands/commands/data-validation-ui.command'; import type { LocaleKey } from '../locale/types'; -import { UniverInstanceType } from '@univerjs/core'; +import { DataValidationRenderMode, DataValidationType, LocaleService, UniverInstanceType } from '@univerjs/core'; import { RangeProtectionPermissionEditPoint, + serializeListOptions, WorkbookEditablePermission, WorksheetEditPermission, WorksheetSetCellStylePermission, } from '@univerjs/sheets'; import { getCurrentRangeDisable$ } from '@univerjs/sheets-ui'; import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui'; -import { AddSheetDataValidationAndOpenCommand } from '../commands/commands/data-validation-ui.command'; +import { + AddSheetDataValidationAndOpenCommand, + ClearQuickSheetDataValidationCommand, + InsertQuickSheetDataValidationCommand, +} from '../commands/commands/data-validation-ui.command'; import { OpenValidationPanelOperation } from '../commands/operations/data-validation.operation'; +import { DROPDOWN_PRESETS_COMPONENT } from '../views/components/DropdownPresets'; export const DATA_VALIDATION_MENU_ID = 'sheet.menu.data-validation'; +export const QUICK_DROPDOWN_MENU_ID = 'sheet.menu.quick-dropdown'; + +const CHECKBOX_RULE_INPUT: IQuickDataValidationRuleInput = { + type: DataValidationType.CHECKBOX, + operator: undefined, + formula1: undefined, + formula2: undefined, +}; + +function createDropdownRuleInput(formula1: string): IQuickDataValidationRuleInput { + return { + type: DataValidationType.LIST, + operator: undefined, + formula1, + formula2: '', + renderMode: DataValidationRenderMode.CUSTOM, + }; +} + +function createDropdownCommandParams(value?: string | number) { + return typeof value === 'undefined' ? {} : { rule: createDropdownRuleInput(String(value)) }; +} + +function getQuickDataValidationMenuState(accessor: IAccessor) { + return { + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + disabled$: getCurrentRangeDisable$(accessor, { + workbookTypes: [WorkbookEditablePermission], + worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission], + rangeTypes: [RangeProtectionPermissionEditPoint], + }), + }; +} export function dataValidationMenuFactory(accessor: IAccessor): IMenuItem { return { @@ -46,6 +86,70 @@ export function dataValidationMenuFactory(accessor: IAccessor): IMenuItem { + return { + id: InsertQuickSheetDataValidationCommand.id, + type: MenuItemType.BUTTON_SELECTOR, + icon: 'CheckboxIcon', + tooltip: 'sheets-data-validation-ui.checkbox.title', + params: { rule: CHECKBOX_RULE_INPUT }, + selectionsCommandId: InsertQuickSheetDataValidationCommand.id, + selections: [ + { + label: 'sheets-data-validation-ui.ribbon.setCheckbox', + value: DataValidationType.CHECKBOX, + params: { rule: CHECKBOX_RULE_INPUT }, + }, + { + label: 'sheets-data-validation-ui.ribbon.clearCheckbox', + id: ClearQuickSheetDataValidationCommand.id, + value: DataValidationType.CHECKBOX, + params: { types: [DataValidationType.CHECKBOX] }, + }, + ], + ...getQuickDataValidationMenuState(accessor), + }; +} + +export function quickDropdownMenuFactory(accessor: IAccessor): IMenuSelectorItem { + const localeService = accessor.get(LocaleService); + const editDropdownValue = serializeListOptions([ + localeService.t('sheets-data-validation-ui.ribbon.presets.option1'), + localeService.t('sheets-data-validation-ui.ribbon.presets.option2'), + ]); + + return { + id: QUICK_DROPDOWN_MENU_ID, + type: MenuItemType.SUBITEMS, + icon: 'DropdownListIcon', + tooltip: 'sheets-data-validation-ui.list.title', + selectionsCommandId: InsertQuickSheetDataValidationCommand.id, + selections: [ + { + label: { + name: DROPDOWN_PRESETS_COMPONENT, + hoverable: false, + selectable: false, + }, + params: createDropdownCommandParams, + }, + { + id: AddSheetDataValidationAndOpenCommand.id, + label: 'sheets-data-validation-ui.ribbon.editDropdown', + value: editDropdownValue, + params: { rule: createDropdownRuleInput(editDropdownValue) }, + }, + { + id: ClearQuickSheetDataValidationCommand.id, + label: 'sheets-data-validation-ui.ribbon.clearDropdown', + value: DataValidationType.LIST, + params: { types: [DataValidationType.LIST, DataValidationType.LIST_MULTIPLE] }, + }, + ], + ...getQuickDataValidationMenuState(accessor), + }; +} + export function openDataValidationMenuFactory(): IMenuItem { return { id: OpenValidationPanelOperation.id, diff --git a/packages/sheets-data-validation-ui/src/menu/schema.ts b/packages/sheets-data-validation-ui/src/menu/schema.ts index f6bc0033aa..f6130b5d63 100644 --- a/packages/sheets-data-validation-ui/src/menu/schema.ts +++ b/packages/sheets-data-validation-ui/src/menu/schema.ts @@ -15,17 +15,33 @@ */ import type { MenuSchemaType } from '@univerjs/ui'; -import { RibbonDataGroup } from '@univerjs/ui'; -import { AddSheetDataValidationAndOpenCommand } from '../commands/commands/data-validation-ui.command'; +import { RibbonDataGroup, RibbonInsertGroup } from '@univerjs/ui'; +import { + AddSheetDataValidationAndOpenCommand, + InsertQuickSheetDataValidationCommand, +} from '../commands/commands/data-validation-ui.command'; import { OpenValidationPanelOperation } from '../commands/operations/data-validation.operation'; import { addDataValidationMenuFactory, DATA_VALIDATION_MENU_ID, dataValidationMenuFactory, openDataValidationMenuFactory, + QUICK_DROPDOWN_MENU_ID, + quickCheckboxMenuFactory, + quickDropdownMenuFactory, } from './dv.menu'; export const menuSchema: MenuSchemaType = { + [RibbonInsertGroup.CELL]: { + [InsertQuickSheetDataValidationCommand.id]: { + order: 0, + menuItemFactory: quickCheckboxMenuFactory, + }, + [QUICK_DROPDOWN_MENU_ID]: { + order: 1, + menuItemFactory: quickDropdownMenuFactory, + }, + }, [RibbonDataGroup.RULES]: { [DATA_VALIDATION_MENU_ID]: { order: 0, diff --git a/packages/sheets-data-validation-ui/src/mobile-plugin.ts b/packages/sheets-data-validation-ui/src/mobile-plugin.ts index 28952ca734..690daca25e 100644 --- a/packages/sheets-data-validation-ui/src/mobile-plugin.ts +++ b/packages/sheets-data-validation-ui/src/mobile-plugin.ts @@ -19,7 +19,11 @@ import type { IUniverSheetsDataValidationUIConfig } from './config/config'; import { ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; import { IRenderManagerService } from '@univerjs/engine-render'; import pkg from '../package.json'; -import { AddSheetDataValidationAndOpenCommand } from './commands/commands/data-validation-ui.command'; +import { + AddSheetDataValidationAndOpenCommand, + ClearQuickSheetDataValidationCommand, + InsertQuickSheetDataValidationCommand, +} from './commands/commands/data-validation-ui.command'; import { CloseValidationPanelOperation, HideDataValidationDropdown, @@ -83,6 +87,8 @@ export class UniverSheetsDataValidationMobileUIPlugin extends Plugin { [ AddSheetDataValidationAndOpenCommand, + InsertQuickSheetDataValidationCommand, + ClearQuickSheetDataValidationCommand, ShowDataValidationDropdown, HideDataValidationDropdown, CloseValidationPanelOperation, diff --git a/packages/sheets-data-validation-ui/src/plugin.ts b/packages/sheets-data-validation-ui/src/plugin.ts index 09fe5cf6d3..426f10e9db 100644 --- a/packages/sheets-data-validation-ui/src/plugin.ts +++ b/packages/sheets-data-validation-ui/src/plugin.ts @@ -20,7 +20,11 @@ import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, import { IRenderManagerService } from '@univerjs/engine-render'; import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; import pkg from '../package.json'; -import { AddSheetDataValidationAndOpenCommand } from './commands/commands/data-validation-ui.command'; +import { + AddSheetDataValidationAndOpenCommand, + ClearQuickSheetDataValidationCommand, + InsertQuickSheetDataValidationCommand, +} from './commands/commands/data-validation-ui.command'; import { CloseValidationPanelOperation, HideDataValidationDropdown, @@ -87,6 +91,8 @@ export class UniverSheetsDataValidationUIPlugin extends Plugin { [ AddSheetDataValidationAndOpenCommand, + InsertQuickSheetDataValidationCommand, + ClearQuickSheetDataValidationCommand, ShowDataValidationDropdown, HideDataValidationDropdown, CloseValidationPanelOperation, diff --git a/packages/sheets-data-validation-ui/src/views/components/DropdownPresets.tsx b/packages/sheets-data-validation-ui/src/views/components/DropdownPresets.tsx new file mode 100644 index 0000000000..7442a91509 --- /dev/null +++ b/packages/sheets-data-validation-ui/src/views/components/DropdownPresets.tsx @@ -0,0 +1,73 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICustomComponentProps } from '@univerjs/ui'; +import type { LocaleKey } from '../../locale/types'; +import { LocaleService } from '@univerjs/core'; +import { Separator } from '@univerjs/design'; +import { serializeListOptions } from '@univerjs/sheets'; +import { useDependency } from '@univerjs/ui'; + +export const DROPDOWN_PRESETS_COMPONENT = 'SHEETS_DATA_VALIDATION_DROPDOWN_PRESETS'; + +export function DropdownPresets({ onChange }: ICustomComponentProps) { + const localeService = useDependency(LocaleService); + const presets = [ + [ + localeService.t('sheets-data-validation-ui.ribbon.presets.yes'), + localeService.t('sheets-data-validation-ui.ribbon.presets.no'), + ], + [ + localeService.t('sheets-data-validation-ui.ribbon.presets.notStarted'), + localeService.t('sheets-data-validation-ui.ribbon.presets.inProgress'), + localeService.t('sheets-data-validation-ui.ribbon.presets.completed'), + ], + ['A', 'B', 'C'], + ]; + + return ( +
+
+ {localeService.t('sheets-data-validation-ui.ribbon.dropdownPresetTitle')} +
+ +
+ {presets.map((values) => ( + + ))} +
+ + +
+ ); +} diff --git a/packages/sheets-data-validation-ui/src/views/components/__tests__/DropdownPresets.spec.tsx b/packages/sheets-data-validation-ui/src/views/components/__tests__/DropdownPresets.spec.tsx new file mode 100644 index 0000000000..24f1c55bd5 --- /dev/null +++ b/packages/sheets-data-validation-ui/src/views/components/__tests__/DropdownPresets.spec.tsx @@ -0,0 +1,77 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Root } from 'react-dom/client'; +import { LocaleService, LocaleType, Univer } from '@univerjs/core'; +import { serializeListOptions } from '@univerjs/sheets'; +import { RediContext } from '@univerjs/ui'; +import { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import enUS from '../../../locale/en-US'; +import { DropdownPresets } from '../DropdownPresets'; + +(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + +describe('DropdownPresets', () => { + let univer: Univer | undefined; + let root: Root | undefined; + let container: HTMLDivElement | undefined; + + afterEach(() => { + act(() => root?.unmount()); + container?.remove(); + univer?.dispose(); + root = undefined; + container = undefined; + univer = undefined; + }); + + it('emits each localized preset as a serialized dropdown value', async () => { + univer = new Univer({ locale: LocaleType.EN_US }); + const injector = univer.__getInjector(); + injector.get(LocaleService).load({ [LocaleType.EN_US]: enUS }); + const onChange = vi.fn(); + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + + await act(async () => { + root!.render( + + + + ); + await Promise.resolve(); + }); + + const buttons = [...container.querySelectorAll('button')]; + expect(buttons).toHaveLength(3); + + for (const button of buttons) { + await act(async () => { + button.click(); + await Promise.resolve(); + }); + } + + expect(onChange.mock.calls.map(([value]) => value)).toEqual([ + serializeListOptions(['Yes', 'No']), + serializeListOptions(['Not Started', 'In Progress', 'Completed']), + serializeListOptions(['A', 'B', 'C']), + ]); + }); +}); diff --git a/packages/sheets-data-validation/src/commands/commands/__tests__/data-validation.command.spec.ts b/packages/sheets-data-validation/src/commands/commands/__tests__/data-validation.command.spec.ts index 2171da094b..e2e0aeff84 100644 --- a/packages/sheets-data-validation/src/commands/commands/__tests__/data-validation.command.spec.ts +++ b/packages/sheets-data-validation/src/commands/commands/__tests__/data-validation.command.spec.ts @@ -213,6 +213,30 @@ describe('sheet data validation commands', () => { expect(rules[0].ranges).toEqual([{ unitId, sheetId: subUnitId, startRow: 4, endRow: 4, startColumn: 4, endColumn: 4 }]); }); + it('clears only requested validation types from a range', async () => { + const activeSheet = univerAPI.getActiveWorkbook()!.getActiveSheet(); + const checkbox = univerAPI.newDataValidation().requireCheckbox().build(); + const dropdown = univerAPI.newDataValidation().requireValueInList(['A', 'B']).build(); + + activeSheet.getRange(0, 0, 1, 1).setDataValidation(checkbox); + activeSheet.getRange(0, 1, 1, 1).setDataValidation(dropdown); + + const result = await commandService.executeCommand(ClearRangeDataValidationCommand.id, { + unitId, + subUnitId, + ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 1 }], + types: [DataValidationType.CHECKBOX], + }); + const rules = model.getRules(unitId, subUnitId); + + expect(result).toBe(true); + expect(rules).toHaveLength(1); + expect(rules[0].type).toBe(DataValidationType.LIST); + expect(rules[0].ranges).toEqual([ + { unitId, sheetId: subUnitId, startRow: 0, endRow: 0, startColumn: 1, endColumn: 1 }, + ]); + }); + it('removes one rule or all rules from the sheet', async () => { const activeSheet = univerAPI.getActiveWorkbook()!.getActiveSheet(); const firstRule = univerAPI.newDataValidation().requireCheckbox().build(); diff --git a/packages/sheets-data-validation/src/commands/commands/data-validation.command.ts b/packages/sheets-data-validation/src/commands/commands/data-validation.command.ts index e24c204cba..612833e7c0 100644 --- a/packages/sheets-data-validation/src/commands/commands/data-validation.command.ts +++ b/packages/sheets-data-validation/src/commands/commands/data-validation.command.ts @@ -605,6 +605,7 @@ export interface IClearRangeDataValidationCommandParams { unitId: string; subUnitId: string; ranges: IRange[]; + types?: DataValidationType[]; } export const ClearRangeDataValidationCommand: ICommand = { @@ -614,7 +615,7 @@ export const ClearRangeDataValidationCommand: ICommand types.includes(rule.type as DataValidationType)) + .map((rule) => rule.uid) + ) + : undefined; + matrix.removeRange(ranges, ruleIds); const diffs = matrix.diff(sheetDataValidationModel.getRules(unitId, subUnitId)); const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, accessor); diff --git a/packages/sheets-data-validation/src/models/rule-matrix.ts b/packages/sheets-data-validation/src/models/rule-matrix.ts index 41362f7ce7..6976537343 100644 --- a/packages/sheets-data-validation/src/models/rule-matrix.ts +++ b/packages/sheets-data-validation/src/models/rule-matrix.ts @@ -105,12 +105,16 @@ export class RuleMatrix { this._addRule(rule.uid, rule.ranges); } - removeRange(_ranges: IRange[]) { + removeRange(_ranges: IRange[], ruleIds?: ReadonlySet) { if (!this._worksheet) { return; } const ranges = _ranges.map((range) => Range.transformRange(range, this._worksheet!)); this._map.forEach((value, key) => { + if (ruleIds && !ruleIds.has(key)) { + return; + } + const newRanges = Rectangle.subtractMulti(value, ranges); if (newRanges.length === 0) { this._map.delete(key); diff --git a/packages/sheets-filter-ui/package.json b/packages/sheets-filter-ui/package.json index ad82e7d2fb..4620f28215 100644 --- a/packages/sheets-filter-ui/package.json +++ b/packages/sheets-filter-ui/package.json @@ -77,7 +77,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/rpc": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-filter": "workspace:*", diff --git a/packages/sheets-formula-ui/package.json b/packages/sheets-formula-ui/package.json index 1963325051..34721d96ef 100644 --- a/packages/sheets-formula-ui/package.json +++ b/packages/sheets-formula-ui/package.json @@ -91,7 +91,7 @@ "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-formula": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/sheets-hyper-link-ui/package.json b/packages/sheets-hyper-link-ui/package.json index 146747e8ff..7344ca865f 100644 --- a/packages/sheets-hyper-link-ui/package.json +++ b/packages/sheets-hyper-link-ui/package.json @@ -91,7 +91,7 @@ "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-data-validation": "workspace:*", "@univerjs/sheets-formula-ui": "workspace:*", diff --git a/packages/sheets-note-ui/package.json b/packages/sheets-note-ui/package.json index 7b92b72380..700b719121 100644 --- a/packages/sheets-note-ui/package.json +++ b/packages/sheets-note-ui/package.json @@ -74,7 +74,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-note": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/sheets-numfmt-ui/package.json b/packages/sheets-numfmt-ui/package.json index f100ac6f43..89918e76e7 100644 --- a/packages/sheets-numfmt-ui/package.json +++ b/packages/sheets-numfmt-ui/package.json @@ -78,7 +78,7 @@ "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-numfmt": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/sheets-sort-ui/package.json b/packages/sheets-sort-ui/package.json index f0f4ed2053..0d0fb6f954 100644 --- a/packages/sheets-sort-ui/package.json +++ b/packages/sheets-sort-ui/package.json @@ -77,7 +77,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-sort": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/sheets-table-ui/package.json b/packages/sheets-table-ui/package.json index 73874ef507..66831c6402 100644 --- a/packages/sheets-table-ui/package.json +++ b/packages/sheets-table-ui/package.json @@ -79,7 +79,7 @@ "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-formula-ui": "workspace:*", "@univerjs/sheets-sort": "workspace:*", diff --git a/packages/sheets-thread-comment-ui/package.json b/packages/sheets-thread-comment-ui/package.json index 49e3057ae0..3c75a055ba 100644 --- a/packages/sheets-thread-comment-ui/package.json +++ b/packages/sheets-thread-comment-ui/package.json @@ -77,7 +77,7 @@ "@univerjs/core": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-thread-comment": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/packages/sheets-ui/package.json b/packages/sheets-ui/package.json index ed4f492fd5..605465d318 100644 --- a/packages/sheets-ui/package.json +++ b/packages/sheets-ui/package.json @@ -91,7 +91,7 @@ "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/protocol": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/telemetry": "workspace:*", diff --git a/packages/slides-ui/package.json b/packages/slides-ui/package.json index 880ceba7b8..fe2efb2199 100644 --- a/packages/slides-ui/package.json +++ b/packages/slides-ui/package.json @@ -80,7 +80,7 @@ "@univerjs/docs-ui": "workspace:*", "@univerjs/drawing": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/slides": "workspace:*", "@univerjs/ui": "workspace:*" }, diff --git a/packages/thread-comment-ui/package.json b/packages/thread-comment-ui/package.json index b9c472382a..b4c2592d02 100644 --- a/packages/thread-comment-ui/package.json +++ b/packages/thread-comment-ui/package.json @@ -77,7 +77,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/docs-ui": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@univerjs/thread-comment": "workspace:*", "@univerjs/ui": "workspace:*" }, diff --git a/packages/ui/package.json b/packages/ui/package.json index b74a006fde..5b2ef4f482 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -90,7 +90,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons": "1.24.0", + "@univerjs/icons": "1.25.0", "@wendellhu/redi": "1.1.2" }, "devDependencies": { diff --git a/packages/ui/src/services/menu/menu-manager.service.ts b/packages/ui/src/services/menu/menu-manager.service.ts index 37d20048d0..d1c73351f2 100644 --- a/packages/ui/src/services/menu/menu-manager.service.ts +++ b/packages/ui/src/services/menu/menu-manager.service.ts @@ -95,12 +95,15 @@ export class MenuManagerService extends Disposable implements IMenuManagerServic [RibbonInsertGroup.EDIT]: { order: 0, }, - [RibbonInsertGroup.MEDIA]: { + [RibbonInsertGroup.CELL]: { order: 1, }, - [RibbonInsertGroup.OTHERS]: { + [RibbonInsertGroup.MEDIA]: { order: 2, }, + [RibbonInsertGroup.OTHERS]: { + order: 3, + }, }, [RibbonPosition.FORMULAS]: { order: 2, diff --git a/packages/ui/src/services/menu/menu.ts b/packages/ui/src/services/menu/menu.ts index 8939b92e8f..d9d064b87b 100644 --- a/packages/ui/src/services/menu/menu.ts +++ b/packages/ui/src/services/menu/menu.ts @@ -37,6 +37,8 @@ type MenuLabel = string | { props?: Record; }; +export type IMenuCommandParams = Record; + interface IMenuItemBase { /** ID of the menu item. Normally it should be the same as the ID of the command that it would invoke. */ id: string; @@ -61,7 +63,7 @@ interface IMenuItemBase { hidden$?: Observable; disabled$?: Observable; - params?: any | Function; + params?: IMenuCommandParams; /** On observable value that should emit the value of the corresponding selection component. */ value$?: Observable; } @@ -76,7 +78,7 @@ export interface IValueOption; - params?: any; + params?: IMenuCommandParams | ((value?: string | number) => IMenuCommandParams); slot?: boolean; label?: MenuLabel; // custom component, send to CustomLabel label property icon?: string; diff --git a/packages/ui/src/services/menu/types.ts b/packages/ui/src/services/menu/types.ts index 1e30aed349..4294e3020a 100644 --- a/packages/ui/src/services/menu/types.ts +++ b/packages/ui/src/services/menu/types.ts @@ -37,6 +37,7 @@ export enum RibbonStartGroup { export enum RibbonInsertGroup { EDIT = 'ribbon.insert.edit', + CELL = 'ribbon.insert.cell', MEDIA = 'ribbon.insert.media', OTHERS = 'ribbon.insert.others', } diff --git a/packages/ui/src/views/components/ribbon/ToolbarItem.tsx b/packages/ui/src/views/components/ribbon/ToolbarItem.tsx index 773ae0cc1f..6d733be48e 100644 --- a/packages/ui/src/views/components/ribbon/ToolbarItem.tsx +++ b/packages/ui/src/views/components/ribbon/ToolbarItem.tsx @@ -239,7 +239,8 @@ export const ToolbarItem = forwardRef void; }) { const onChange = (v: string | number) => { - onOptionSelect?.({ value: v, label: option?.label, commandId: option?.commandId }); + onOptionSelect?.({ ...option, value: v }); }; const hasCheckMark = typeof option.label === 'string' || (typeof option.label === 'object' && option.label?.selectable !== false); diff --git a/packages/ui/src/views/components/ribbon/__tests__/ToolbarItem.spec.tsx b/packages/ui/src/views/components/ribbon/__tests__/ToolbarItem.spec.tsx index d078757dab..921a58df26 100644 --- a/packages/ui/src/views/components/ribbon/__tests__/ToolbarItem.spec.tsx +++ b/packages/ui/src/views/components/ribbon/__tests__/ToolbarItem.spec.tsx @@ -15,7 +15,7 @@ */ import type { ComponentType, ReactElement } from 'react'; -import { cleanup, render } from '@testing-library/react'; +import { cleanup, fireEvent, render } from '@testing-library/react'; import { ICommandService, ILogService, Injector, LocaleService } from '@univerjs/core'; import { Subject } from 'rxjs'; import { afterEach, describe, expect, it } from 'vitest'; @@ -35,7 +35,11 @@ class TestLocaleService { } class TestCommandService { - executeCommand(): void {} + calls: Array<{ commandId: string; params?: Record }> = []; + + executeCommand(commandId: string, params?: Record): void { + this.calls.push({ commandId, params }); + } } class TestLayoutService { @@ -72,42 +76,110 @@ function renderWithDependencies(element: ReactElement) { injector.add([ILogService, { useClass: TestLogService as never }]); injector.add([ComponentManager]); injector.add([IconManager]); + injector.get(ComponentManager).register('TestDynamicOption', ({ onChange }: { onChange: (value: string) => void }) => ( + + )); injector.get(IconManager).register({ TestIcon: ({ className }: { className?: string }) => , }); const ConnectedTestRoot = connectInjector(() => element, injector) as ComponentType; - return render(); + return { + ...render(), + commandService: injector.get(ICommandService) as unknown as TestCommandService, + }; } afterEach(cleanup); describe('ToolbarItem', () => { - it('mirrors button selector dropdown trigger placement in rtl', () => { - const { container } = renderWithDependencies( -
- -
+ it('forwards menu params when clicking a button selector main action', () => { + const rule = { type: 'checkbox' }; + const { container, commandService } = renderWithDependencies( + ); - const root = container.querySelector('.univer-toolbar-button-selector-root') as HTMLElement; - const main = container.querySelector('.univer-toolbar-button-selector-main') as HTMLElement; - const trigger = container.querySelector('.univer-toolbar-button-selector-trigger') as HTMLElement; + fireEvent.click(container.querySelector('.univer-toolbar-button-selector-main') as HTMLElement); - expect(root.className).toContain('rtl:univer-pl-5'); - expect(root.className).toContain('rtl:univer-pr-0'); - expect(main.className).toContain('rtl:univer-rounded-l-none'); - expect(main.className).toContain('rtl:univer-rounded-r'); - expect(trigger.className).toContain('rtl:univer-left-0'); - expect(trigger.className).toContain('rtl:univer-right-auto'); - expect(trigger.className).toContain('rtl:univer-rounded-l'); - expect(trigger.className).toContain('rtl:univer-rounded-r-none'); + expect(commandService.calls).toEqual([ + { + commandId: 'test-button-selector', + params: { rule }, + }, + ]); + }); + + it('resolves option params with a value emitted by a custom option', async () => { + const { container, findByRole, commandService } = renderWithDependencies( + ({ rule: { formula1: value } }), + }, + ]} + /> + ); + + fireEvent.click(container.querySelector('.univer-toolbar-selector-root') as HTMLElement); + fireEvent.click(await findByRole('button', { name: 'Choose dynamic value' })); + + expect(commandService.calls).toEqual([ + { + commandId: 'insert-dynamic-rule', + params: { rule: { formula1: 'dynamic-value' } }, + }, + ]); + }); + + it('forwards static option params without wrapping them as a value', async () => { + const { container, findByRole, commandService } = renderWithDependencies( + + ); + + fireEvent.click(container.querySelector('.univer-toolbar-selector-root') as HTMLElement); + fireEvent.click(await findByRole('button', { name: 'Choose dynamic value' })); + + expect(commandService.calls).toEqual([ + { + commandId: 'clear-rule', + params: { types: ['list'] }, + }, + ]); }); }); diff --git a/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx b/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx index 4ceaeca7a4..a6611b5119 100644 --- a/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx +++ b/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx @@ -19,9 +19,9 @@ */ import type { ComponentType, ReactElement } from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { ILogService, Injector, LocaleService } from '@univerjs/core'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { ComponentManager } from '../../../../common/component-manager'; import { IconManager } from '../../../../common/icon-manager'; import { connectInjector } from '../../../../utils/di'; @@ -43,50 +43,39 @@ function renderWithDependencies(element: ReactElement) { injector.add([ILogService, { useClass: TestLogService as never }]); injector.add([ComponentManager]); injector.add([IconManager]); + injector.get(ComponentManager).register('TestDynamicOption', ({ onChange }: { onChange: (value: string) => void }) => ( + + )); const ConnectedTestRoot = connectInjector(() => element, injector) as ComponentType; return render(); } describe('DropdownMenuLabel', () => { - it('keeps menu content left-aligned and renders the selected checkmark on the right', () => { - const { container, getByText } = renderWithDependencies( + it('preserves option metadata when a custom label emits a dynamic value', () => { + const onOptionSelect = vi.fn(); + const params = (value?: string | number) => ({ value }); + const option = { + id: 'dynamic-option', + commandId: 'dynamic-command', + label: { + name: 'TestDynamicOption', + selectable: false, + }, + params, + }; + const { getByRole } = renderWithDependencies( ); - const root = container.firstElementChild as HTMLElement; - const children = Array.from(root.children) as HTMLElement[]; + fireEvent.click(getByRole('button', { name: 'Choose dynamic value' })); - expect(root.className).toContain('univer-justify-between'); - expect(children[0].className).toContain('univer-min-w-0'); - expect(children[0].className).toContain('univer-gap-2'); - expect(children[1].className).toContain('univer-ml-auto'); - expect(children[1].querySelector('svg')).toBeTruthy(); - expect(getByText('Normal')).toBeTruthy(); - }); - - it('reserves the right-side checkmark slot for selectable items even when not selected', () => { - const { container } = renderWithDependencies( - - ); - - const root = container.firstElementChild as HTMLElement; - const children = Array.from(root.children) as HTMLElement[]; - - expect(root.className).toContain('univer-justify-between'); - expect(children[1].className).toContain('univer-w-4'); - expect(children[1].querySelector('svg')).toBeNull(); + expect(onOptionSelect).toHaveBeenCalledWith({ + ...option, + value: 'dynamic-value', + }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca0feb6892..79a69b2872 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,8 +112,8 @@ importers: specifier: workspace:* version: link:../../packages/engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/mockdata': specifier: workspace:* version: link:../mockdata @@ -355,11 +355,11 @@ importers: version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-istanbul@4.1.10)(happy-dom@20.10.6)(jsdom@29.1.1)(vite@8.0.11(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.87.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) devDependencies: '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/icons-svg': - specifier: ^1.23.0 - version: 1.23.0 + specifier: ^1.25.0 + version: 1.25.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -500,8 +500,8 @@ importers: specifier: workspace:* version: link:../packages/find-replace '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/mockdata': specifier: workspace:* version: link:../common/mockdata @@ -738,8 +738,8 @@ importers: specifier: workspace:* version: link:../design '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -873,8 +873,8 @@ importers: specifier: ^1.3.0 version: 1.3.0(@types/react@19.2.17)(react@19.2.7) '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1064,8 +1064,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/ui': specifier: workspace:* version: link:../ui @@ -1165,8 +1165,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/ui': specifier: workspace:* version: link:../ui @@ -1208,8 +1208,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/thread-comment': specifier: workspace:* version: link:../thread-comment @@ -1257,8 +1257,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/ui': specifier: workspace:* version: link:../ui @@ -1331,8 +1331,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/ui': specifier: workspace:* version: link:../ui @@ -1442,8 +1442,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/ui': specifier: workspace:* version: link:../ui @@ -1602,8 +1602,8 @@ importers: specifier: workspace:* version: link:../../common/shared '@univerjs/icons-svg': - specifier: ^1.23.0 - version: 1.23.0 + specifier: ^1.25.0 + version: 1.25.0 '@univerjs/sheets-formula': specifier: workspace:* version: link:../sheets-formula @@ -1632,8 +1632,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -1687,8 +1687,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -1773,8 +1773,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -1945,8 +1945,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/rpc': specifier: workspace:* version: link:../rpc @@ -2074,8 +2074,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2160,8 +2160,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2246,8 +2246,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2323,8 +2323,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2394,8 +2394,8 @@ importers: specifier: workspace:* version: link:../engine-formula '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2480,8 +2480,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2563,8 +2563,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/sheets': specifier: workspace:* version: link:../sheets @@ -2627,8 +2627,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/protocol': specifier: workspace:* version: link:../protocol @@ -2710,8 +2710,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/slides': specifier: workspace:* version: link:../slides @@ -2794,8 +2794,8 @@ importers: specifier: workspace:* version: link:../docs-ui '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@univerjs/thread-comment': specifier: workspace:* version: link:../thread-comment @@ -2840,8 +2840,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons': - specifier: 1.24.0 - version: 1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 1.25.0 + version: 1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@wendellhu/redi': specifier: 1.1.2 version: 1.1.2(react@19.2.7) @@ -6823,11 +6823,11 @@ packages: resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@univerjs/icons-svg@1.23.0': - resolution: {integrity: sha512-U0b/jjyu+z+J65l8g3I4KS7Ownun3KaBuoBglOg+SkIn58jEi/LyjzHyUzsUp3qXFmx1C74T0I7zYCUpPhTULA==} + '@univerjs/icons-svg@1.25.0': + resolution: {integrity: sha512-s/uZnO9tzbPNmP3KSj5kLtCc8yrdjYJAkHapLCdUUzwjxV8ywNqMYv0SYvGMjJTkYyZ1Fj98Oo/A6qToKY6Nww==} - '@univerjs/icons@1.24.0': - resolution: {integrity: sha512-fvPzG6EbJxg82czgwvvyqBMFuVHY+WQQgOCNA+/CBR362ikwGhliyZ8j1QpBjv7ebwCIxMVYQlGy3Zcon0mTtw==} + '@univerjs/icons@1.25.0': + resolution: {integrity: sha512-sd1tkGuHIarNPJ/zFtS4B1gPfFj119PN61pgZvPIpdnadT07RGMVT01GfOIkoDdVhhVGrR3/KszT+XsBs6CnHQ==} peerDependencies: react: 19.2.7 react-dom: 19.2.7 @@ -14515,9 +14515,9 @@ snapshots: '@typescript-eslint/types': 8.63.0 eslint-visitor-keys: 5.0.1 - '@univerjs/icons-svg@1.23.0': {} + '@univerjs/icons-svg@1.25.0': {} - '@univerjs/icons@1.24.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@univerjs/icons@1.25.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7)