mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-05 00:46:50 +08:00
feat(editor): 新增 alwaysMultiSelect 配置开启常驻多选模式
新增编辑器配置项 alwaysMultiSelect(默认 false),开启后无需按住 Ctrl/Meta 键,组件树与画布点击即多选;当 disabledMultiSelect=true 时本配置失效。同步 在 stage 层 ActionManager 暴露 setAlwaysMultiSelect 方法用于运行时切换,并 补充组件树/服务/画布的状态联动、文档与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2475a4f901
commit
aab73249d1
@ -3,7 +3,7 @@
|
|||||||
## get
|
## get
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect'} name`
|
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{any} value`
|
- `{any} value`
|
||||||
@ -36,6 +36,8 @@
|
|||||||
|
|
||||||
'disabledMultiSelect': 是否禁用多选
|
'disabledMultiSelect': 是否禁用多选
|
||||||
|
|
||||||
|
'alwaysMultiSelect': 是否始终启用多选模式(无需按住 Ctrl/Meta)
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -46,7 +48,7 @@ const node = editorService.get("node");
|
|||||||
|
|
||||||
## set
|
## set
|
||||||
|
|
||||||
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect'} name`
|
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
||||||
- `{any} value`
|
- `{any} value`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|||||||
@ -1043,7 +1043,26 @@ const updateDragEl = (el, target) => {
|
|||||||
<m-editor :disabled-multi-select="true"></m-editor>
|
<m-editor :disabled-multi-select="true"></m-editor>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## alwaysMultiSelect
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
始终启用多选模式:开启后无需按住 `Ctrl/Meta` 键,组件树和画布上点击即多选。
|
||||||
|
当 [`disabledMultiSelect`](#disabledmultiselect) 为 `true` 时本配置失效。
|
||||||
|
|
||||||
|
- **类型:** `boolean`
|
||||||
|
|
||||||
|
- **默认值:** `false`
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :always-multi-select="true"></m-editor>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
## guidesOptions
|
## guidesOptions
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|||||||
@ -113,6 +113,13 @@
|
|||||||
- **类型**:`() => void`
|
- **类型**:`() => void`
|
||||||
- **详情**:启用多选能力
|
- **详情**:启用多选能力
|
||||||
|
|
||||||
|
## setAlwaysMultiSelect
|
||||||
|
|
||||||
|
- **类型**:`(value: boolean) => void`
|
||||||
|
- **参数**:
|
||||||
|
- `value`:是否始终启用多选模式(无需按住 `Ctrl/Meta` 键)
|
||||||
|
- **详情**:设置是否始终启用多选模式。当多选被 `disableMultiSelect` 禁用时,本方法不会启用多选
|
||||||
|
|
||||||
## reloadIframe
|
## reloadIframe
|
||||||
|
|
||||||
- **类型**:`(url: string) => void`
|
- **类型**:`(url: string) => void`
|
||||||
|
|||||||
@ -219,6 +219,7 @@ const stageOptions: StageOptions = {
|
|||||||
renderType: props.renderType,
|
renderType: props.renderType,
|
||||||
guidesOptions: props.guidesOptions,
|
guidesOptions: props.guidesOptions,
|
||||||
disabledMultiSelect: props.disabledMultiSelect,
|
disabledMultiSelect: props.disabledMultiSelect,
|
||||||
|
alwaysMultiSelect: props.alwaysMultiSelect,
|
||||||
beforeDblclick: props.beforeDblclick,
|
beforeDblclick: props.beforeDblclick,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,11 @@ export interface EditorProps {
|
|||||||
guidesOptions?: Partial<GuidesOptions>;
|
guidesOptions?: Partial<GuidesOptions>;
|
||||||
/** 禁止多选 */
|
/** 禁止多选 */
|
||||||
disabledMultiSelect?: boolean;
|
disabledMultiSelect?: boolean;
|
||||||
|
/**
|
||||||
|
* 始终启用多选模式:开启后无需按住 Ctrl/Meta,点击即多选;
|
||||||
|
* 默认 false。当 `disabledMultiSelect` 为 true 时本配置失效
|
||||||
|
*/
|
||||||
|
alwaysMultiSelect?: boolean;
|
||||||
/** 禁用页面片 */
|
/** 禁用页面片 */
|
||||||
disabledPageFragment?: boolean;
|
disabledPageFragment?: boolean;
|
||||||
/** 禁用双击在浮层中单独编辑选中组件 */
|
/** 禁用双击在浮层中单独编辑选中组件 */
|
||||||
@ -127,6 +132,7 @@ export interface EditorProps {
|
|||||||
export const defaultEditorProps = {
|
export const defaultEditorProps = {
|
||||||
renderType: RenderType.IFRAME,
|
renderType: RenderType.IFRAME,
|
||||||
disabledMultiSelect: false,
|
disabledMultiSelect: false,
|
||||||
|
alwaysMultiSelect: false,
|
||||||
disabledPageFragment: false,
|
disabledPageFragment: false,
|
||||||
disabledStageOverlay: false,
|
disabledStageOverlay: false,
|
||||||
containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
|
containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
|
||||||
|
|||||||
@ -46,6 +46,7 @@ export const useStage = (stageOptions: StageOptions) => {
|
|||||||
updateDragEl: stageOptions.updateDragEl,
|
updateDragEl: stageOptions.updateDragEl,
|
||||||
guidesOptions: stageOptions.guidesOptions,
|
guidesOptions: stageOptions.guidesOptions,
|
||||||
disabledMultiSelect: stageOptions.disabledMultiSelect,
|
disabledMultiSelect: stageOptions.disabledMultiSelect,
|
||||||
|
alwaysMultiSelect: stageOptions.alwaysMultiSelect,
|
||||||
disabledRule: stageOptions.disabledRule,
|
disabledRule: stageOptions.disabledRule,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,6 +61,13 @@ export const useStage = (stageOptions: StageOptions) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => editorService.get('alwaysMultiSelect'),
|
||||||
|
(alwaysMultiSelect) => {
|
||||||
|
stage.setAlwaysMultiSelect(Boolean(alwaysMultiSelect));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const hGuidesCache = getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY));
|
const hGuidesCache = getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY));
|
||||||
const vGuidesCache = getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY));
|
const vGuidesCache = getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY));
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,16 @@ export const initServiceState = (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.alwaysMultiSelect,
|
||||||
|
(alwaysMultiSelect) => {
|
||||||
|
editorService.set('alwaysMultiSelect', alwaysMultiSelect || false);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.componentGroupList,
|
() => props.componentGroupList,
|
||||||
(componentGroupList) => componentGroupList && componentListService.setList(componentGroupList),
|
(componentGroupList) => componentGroupList && componentListService.setList(componentGroupList),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { computed, type ComputedRef, nextTick, type Ref, type ShallowRef } from
|
|||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
|
|
||||||
import { Id, MNode } from '@tmagic/core';
|
import { Id, MNode } from '@tmagic/core';
|
||||||
import { isPage, isPageFragment } from '@tmagic/utils';
|
import { getElById, isPage, isPageFragment } from '@tmagic/utils';
|
||||||
|
|
||||||
import type { LayerNodeStatus, Services, TreeNodeData } from '@editor/type';
|
import type { LayerNodeStatus, Services, TreeNodeData } from '@editor/type';
|
||||||
import { UI_SELECT_MODE_EVENT_NAME } from '@editor/utils/const';
|
import { UI_SELECT_MODE_EVENT_NAME } from '@editor/utils/const';
|
||||||
@ -16,7 +16,9 @@ export const useClick = (
|
|||||||
nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
|
nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
|
||||||
menuRef: ShallowRef<InstanceType<typeof LayerMenu> | null>,
|
menuRef: ShallowRef<InstanceType<typeof LayerMenu> | null>,
|
||||||
) => {
|
) => {
|
||||||
const isMultiSelect = computed(() => isCtrlKeyDown.value && !editorService.get('disabledMultiSelect'));
|
const isMultiSelect = computed(
|
||||||
|
() => !editorService.get('disabledMultiSelect') && (isCtrlKeyDown.value || editorService.get('alwaysMultiSelect')),
|
||||||
|
);
|
||||||
|
|
||||||
// 触发画布选中
|
// 触发画布选中
|
||||||
const select = async (data: MNode) => {
|
const select = async (data: MNode) => {
|
||||||
@ -81,6 +83,19 @@ export const useClick = (
|
|||||||
stageOverlayService.get('stage')?.highlight(data.id);
|
stageOverlayService.get('stage')?.highlight(data.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isNodeCanSelect = async (data: TreeNodeData) => {
|
||||||
|
const canSelect = stageOverlayService.get('stageOptions')?.canSelect;
|
||||||
|
if (!canSelect) return true;
|
||||||
|
|
||||||
|
const doc = editorService.get('stage')?.renderer?.contentWindow?.document;
|
||||||
|
if (!doc) return true;
|
||||||
|
|
||||||
|
const el = getElById()(doc, data.id);
|
||||||
|
if (!el) return true;
|
||||||
|
|
||||||
|
return Boolean(await canSelect(el));
|
||||||
|
};
|
||||||
|
|
||||||
const nodeClickHandler = (event: MouseEvent, data: TreeNodeData): void => {
|
const nodeClickHandler = (event: MouseEvent, data: TreeNodeData): void => {
|
||||||
if (!nodeStatusMap?.value) return;
|
if (!nodeStatusMap?.value) return;
|
||||||
|
|
||||||
@ -95,7 +110,9 @@ export const useClick = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(async () => {
|
||||||
|
if (!(await isNodeCanSelect(data))) return;
|
||||||
|
|
||||||
select(data);
|
select(data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -79,6 +79,7 @@ class Editor extends BaseService {
|
|||||||
pageLength: 0,
|
pageLength: 0,
|
||||||
pageFragmentLength: 0,
|
pageFragmentLength: 0,
|
||||||
disabledMultiSelect: false,
|
disabledMultiSelect: false,
|
||||||
|
alwaysMultiSelect: false,
|
||||||
});
|
});
|
||||||
private isHistoryStateChange = false;
|
private isHistoryStateChange = false;
|
||||||
private selectionBeforeOp: Id[] | null = null;
|
private selectionBeforeOp: Id[] | null = null;
|
||||||
|
|||||||
@ -181,6 +181,11 @@ export interface StageOptions {
|
|||||||
renderType?: RenderType;
|
renderType?: RenderType;
|
||||||
guidesOptions?: Partial<GuidesOptions>;
|
guidesOptions?: Partial<GuidesOptions>;
|
||||||
disabledMultiSelect?: boolean;
|
disabledMultiSelect?: boolean;
|
||||||
|
/**
|
||||||
|
* 始终启用多选模式(无需按住 Ctrl/Meta),默认 false。
|
||||||
|
* 当 `disabledMultiSelect` 为 true 时本配置失效。
|
||||||
|
*/
|
||||||
|
alwaysMultiSelect?: boolean;
|
||||||
disabledRule?: boolean;
|
disabledRule?: boolean;
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||||
@ -200,6 +205,8 @@ export interface StoreState {
|
|||||||
pageLength: number;
|
pageLength: number;
|
||||||
pageFragmentLength: number;
|
pageFragmentLength: number;
|
||||||
disabledMultiSelect: boolean;
|
disabledMultiSelect: boolean;
|
||||||
|
/** 是否始终启用多选模式(无需按住 Ctrl/Meta) */
|
||||||
|
alwaysMultiSelect: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StoreStateKey = keyof StoreState;
|
export type StoreStateKey = keyof StoreState;
|
||||||
|
|||||||
@ -127,6 +127,23 @@ describe('get', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('multiSelect 标志位', () => {
|
||||||
|
test('disabledMultiSelect 默认值为 false', () => {
|
||||||
|
expect(editorService.get('disabledMultiSelect')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('alwaysMultiSelect 默认值为 false', () => {
|
||||||
|
expect(editorService.get('alwaysMultiSelect')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('alwaysMultiSelect 可被 set 修改并通过 get 读取', () => {
|
||||||
|
editorService.set('alwaysMultiSelect', true);
|
||||||
|
expect(editorService.get('alwaysMultiSelect')).toBe(true);
|
||||||
|
editorService.set('alwaysMultiSelect', false);
|
||||||
|
expect(editorService.get('alwaysMultiSelect')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getNodeInfo', () => {
|
describe('getNodeInfo', () => {
|
||||||
beforeAll(() => editorService.set('root', cloneDeep(root)));
|
beforeAll(() => editorService.set('root', cloneDeep(root)));
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,8 @@ export default class ActionManager extends EventEmitter {
|
|||||||
private canDropIn?: CanDropIn;
|
private canDropIn?: CanDropIn;
|
||||||
private getRenderDocument: GetRenderDocument;
|
private getRenderDocument: GetRenderDocument;
|
||||||
private disabledMultiSelect = false;
|
private disabledMultiSelect = false;
|
||||||
|
/** 始终启用多选模式(无需按住 Ctrl/Meta),优先级低于 disabledMultiSelect */
|
||||||
|
private alwaysMultiSelect = false;
|
||||||
private config: ActionManagerConfig;
|
private config: ActionManagerConfig;
|
||||||
|
|
||||||
private mouseMoveHandler = throttle((event: MouseEvent): void => {
|
private mouseMoveHandler = throttle((event: MouseEvent): void => {
|
||||||
@ -123,6 +125,7 @@ export default class ActionManager extends EventEmitter {
|
|||||||
this.containerHighlightDuration = config.containerHighlightDuration || defaultContainerHighlightDuration;
|
this.containerHighlightDuration = config.containerHighlightDuration || defaultContainerHighlightDuration;
|
||||||
this.containerHighlightType = config.containerHighlightType;
|
this.containerHighlightType = config.containerHighlightType;
|
||||||
this.disabledMultiSelect = config.disabledMultiSelect ?? false;
|
this.disabledMultiSelect = config.disabledMultiSelect ?? false;
|
||||||
|
this.alwaysMultiSelect = config.alwaysMultiSelect ?? false;
|
||||||
this.getTargetElement = config.getTargetElement;
|
this.getTargetElement = config.getTargetElement;
|
||||||
this.getElementsFromPoint = config.getElementsFromPoint;
|
this.getElementsFromPoint = config.getElementsFromPoint;
|
||||||
this.canSelect = config.canSelect || ((el: HTMLElement) => Boolean(getIdFromEl()(el)));
|
this.canSelect = config.canSelect || ((el: HTMLElement) => Boolean(getIdFromEl()(el)));
|
||||||
@ -134,6 +137,9 @@ export default class ActionManager extends EventEmitter {
|
|||||||
|
|
||||||
if (!this.disabledMultiSelect) {
|
if (!this.disabledMultiSelect) {
|
||||||
this.multiDr = this.createMultiDr(config);
|
this.multiDr = this.createMultiDr(config);
|
||||||
|
if (this.alwaysMultiSelect) {
|
||||||
|
this.isMultiSelectStatus = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlightLayer = new StageHighlight({
|
this.highlightLayer = new StageHighlight({
|
||||||
@ -148,6 +154,7 @@ export default class ActionManager extends EventEmitter {
|
|||||||
|
|
||||||
public disableMultiSelect() {
|
public disableMultiSelect() {
|
||||||
this.disabledMultiSelect = true;
|
this.disabledMultiSelect = true;
|
||||||
|
this.isMultiSelectStatus = false;
|
||||||
if (this.multiDr) {
|
if (this.multiDr) {
|
||||||
this.multiDr.destroy();
|
this.multiDr.destroy();
|
||||||
this.multiDr = null;
|
this.multiDr = null;
|
||||||
@ -160,6 +167,20 @@ export default class ActionManager extends EventEmitter {
|
|||||||
if (!this.multiDr) {
|
if (!this.multiDr) {
|
||||||
this.multiDr = this.createMultiDr(this.config);
|
this.multiDr = this.createMultiDr(this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.alwaysMultiSelect) {
|
||||||
|
this.isMultiSelectStatus = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否始终启用多选模式(无需按住 Ctrl/Meta),
|
||||||
|
* 当 `disabledMultiSelect` 为 true 时本方法不会启用多选。
|
||||||
|
*/
|
||||||
|
public setAlwaysMultiSelect(value: boolean) {
|
||||||
|
this.alwaysMultiSelect = value;
|
||||||
|
if (this.disabledMultiSelect) return;
|
||||||
|
this.isMultiSelectStatus = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -633,14 +654,14 @@ export default class ActionManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
// ctrl+tab切到其他窗口,需要将多选状态置为false
|
// ctrl+tab切到其他窗口,需要将多选状态置为false
|
||||||
KeyController.global.on('blur', () => {
|
KeyController.global.on('blur', () => {
|
||||||
if (!this.disabledMultiSelect) {
|
if (!this.disabledMultiSelect && !this.alwaysMultiSelect) {
|
||||||
this.isMultiSelectStatus = false;
|
this.isMultiSelectStatus = false;
|
||||||
}
|
}
|
||||||
this.isAltKeydown = false;
|
this.isAltKeydown = false;
|
||||||
});
|
});
|
||||||
KeyController.global.keyup(ctrl, (e) => {
|
KeyController.global.keyup(ctrl, (e) => {
|
||||||
e.inputEvent.preventDefault();
|
e.inputEvent.preventDefault();
|
||||||
if (!this.disabledMultiSelect) {
|
if (!this.disabledMultiSelect && !this.alwaysMultiSelect) {
|
||||||
this.isMultiSelectStatus = false;
|
this.isMultiSelectStatus = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -269,6 +269,14 @@ export default class StageCore extends EventEmitter {
|
|||||||
this.actionManager?.enableMultiSelect();
|
this.actionManager?.enableMultiSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否始终启用多选模式(无需按住 Ctrl/Meta)。
|
||||||
|
* 当多选被 `disabledMultiSelect` 禁用时,本方法不会启用多选。
|
||||||
|
*/
|
||||||
|
public setAlwaysMultiSelect(value: boolean) {
|
||||||
|
this.actionManager?.setAlwaysMultiSelect(value);
|
||||||
|
}
|
||||||
|
|
||||||
public reloadIframe(url: string) {
|
public reloadIframe(url: string) {
|
||||||
this.renderer?.reloadIframe(url);
|
this.renderer?.reloadIframe(url);
|
||||||
}
|
}
|
||||||
@ -346,6 +354,7 @@ export default class StageCore extends EventEmitter {
|
|||||||
container: this.mask!.content,
|
container: this.mask!.content,
|
||||||
disabledDragStart: config.disabledDragStart,
|
disabledDragStart: config.disabledDragStart,
|
||||||
disabledMultiSelect: config.disabledMultiSelect,
|
disabledMultiSelect: config.disabledMultiSelect,
|
||||||
|
alwaysMultiSelect: config.alwaysMultiSelect,
|
||||||
canSelect: config.canSelect,
|
canSelect: config.canSelect,
|
||||||
isContainer: config.isContainer,
|
isContainer: config.isContainer,
|
||||||
canDropIn: config.canDropIn,
|
canDropIn: config.canDropIn,
|
||||||
|
|||||||
@ -84,6 +84,11 @@ export interface StageCoreConfig {
|
|||||||
renderType?: RenderType;
|
renderType?: RenderType;
|
||||||
guidesOptions?: Partial<GuidesOptions>;
|
guidesOptions?: Partial<GuidesOptions>;
|
||||||
disabledMultiSelect?: boolean;
|
disabledMultiSelect?: boolean;
|
||||||
|
/**
|
||||||
|
* 始终启用多选模式(无需按住 Ctrl/Meta),默认 false。
|
||||||
|
* 当 `disabledMultiSelect` 为 true 时,本配置失效。
|
||||||
|
*/
|
||||||
|
alwaysMultiSelect?: boolean;
|
||||||
disabledRule?: boolean;
|
disabledRule?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +100,11 @@ export interface ActionManagerConfig {
|
|||||||
moveableOptions?: CustomizeMoveableOptions;
|
moveableOptions?: CustomizeMoveableOptions;
|
||||||
disabledDragStart?: boolean;
|
disabledDragStart?: boolean;
|
||||||
disabledMultiSelect?: boolean;
|
disabledMultiSelect?: boolean;
|
||||||
|
/**
|
||||||
|
* 始终启用多选模式(无需按住 Ctrl/Meta),默认 false。
|
||||||
|
* 当 `disabledMultiSelect` 为 true 时,本配置失效。
|
||||||
|
*/
|
||||||
|
alwaysMultiSelect?: boolean;
|
||||||
canSelect?: CanSelect;
|
canSelect?: CanSelect;
|
||||||
isContainer?: IsContainer;
|
isContainer?: IsContainer;
|
||||||
/** 见 StageCoreConfig.canDropIn */
|
/** 见 StageCoreConfig.canDropIn */
|
||||||
|
|||||||
132
packages/stage/tests/unit/ActionManager.spec.ts
Normal file
132
packages/stage/tests/unit/ActionManager.spec.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import ActionManager from '../../src/ActionManager';
|
||||||
|
import type { ActionManagerConfig } from '../../src/types';
|
||||||
|
|
||||||
|
// 在 jsdom 环境下 `moveable-helper`/`moveable` 的 ESM 默认导出无法直接被
|
||||||
|
// require/import 调用,这里仅测试 ActionManager 自身的多选状态管理,将其桩掉。
|
||||||
|
// vi.mock 调用会被 vitest 自动提升到模块顶部,因此放在 import 之后无影响。
|
||||||
|
vi.mock('moveable-helper', () => ({
|
||||||
|
default: {
|
||||||
|
create: () => ({ clear: vi.fn() }),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
vi.mock('moveable', () => ({
|
||||||
|
default: class MockMoveable {
|
||||||
|
on() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
off() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
destroy() {}
|
||||||
|
request() {}
|
||||||
|
updateRect() {}
|
||||||
|
updateTarget() {}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createConfig = (overrides: Partial<ActionManagerConfig> = {}): ActionManagerConfig => {
|
||||||
|
const container = globalThis.document.createElement('div');
|
||||||
|
globalThis.document.body.appendChild(container);
|
||||||
|
return {
|
||||||
|
container,
|
||||||
|
getTargetElement: () => null,
|
||||||
|
getElementsFromPoint: () => [],
|
||||||
|
getRenderDocument: () => globalThis.document,
|
||||||
|
getRootContainer: () => undefined,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ActionManager - alwaysMultiSelect', () => {
|
||||||
|
let am: ActionManager | null = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
globalThis.document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
am?.destroy();
|
||||||
|
am = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('默认配置下不会自动开启多选状态', () => {
|
||||||
|
am = new ActionManager(createConfig());
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
expect((am as any).alwaysMultiSelect).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('alwaysMultiSelect=true 构造后即处于多选状态', () => {
|
||||||
|
am = new ActionManager(createConfig({ alwaysMultiSelect: true }));
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(true);
|
||||||
|
expect((am as any).alwaysMultiSelect).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disabledMultiSelect=true 时 alwaysMultiSelect=true 也不会开启多选', () => {
|
||||||
|
am = new ActionManager(
|
||||||
|
createConfig({
|
||||||
|
disabledMultiSelect: true,
|
||||||
|
alwaysMultiSelect: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
expect((am as any).disabledMultiSelect).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setAlwaysMultiSelect(true) 切换为多选状态', () => {
|
||||||
|
am = new ActionManager(createConfig());
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
|
||||||
|
am.setAlwaysMultiSelect(true);
|
||||||
|
expect((am as any).alwaysMultiSelect).toBe(true);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(true);
|
||||||
|
|
||||||
|
am.setAlwaysMultiSelect(false);
|
||||||
|
expect((am as any).alwaysMultiSelect).toBe(false);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setAlwaysMultiSelect 不能突破 disabledMultiSelect 限制', () => {
|
||||||
|
am = new ActionManager(createConfig({ disabledMultiSelect: true }));
|
||||||
|
am.setAlwaysMultiSelect(true);
|
||||||
|
expect((am as any).alwaysMultiSelect).toBe(true);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disableMultiSelect() 会重置多选状态', () => {
|
||||||
|
am = new ActionManager(createConfig({ alwaysMultiSelect: true }));
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(true);
|
||||||
|
|
||||||
|
am.disableMultiSelect();
|
||||||
|
expect((am as any).disabledMultiSelect).toBe(true);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enableMultiSelect() 后若 alwaysMultiSelect=true 恢复多选状态', () => {
|
||||||
|
am = new ActionManager(createConfig({ alwaysMultiSelect: true }));
|
||||||
|
am.disableMultiSelect();
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(false);
|
||||||
|
|
||||||
|
am.enableMultiSelect();
|
||||||
|
expect((am as any).disabledMultiSelect).toBe(false);
|
||||||
|
expect((am as any).isMultiSelectStatus).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user