mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-11 19:13:51 +00:00
feat(editor): 新增"已选组件"面板节点双击事件 layer-node-dblclick 与 beforeLayerNodeDblclick 钩子
- TreeNode/Tree 增加 node-dblclick 事件透传 - LayerPanel 默认双击切换可展开节点的展开/收起状态,并向上抛出 node-dblclick - Sidebar/Editor 暴露 layer-node-dblclick 事件与 beforeLayerNodeDblclick 拦截钩子 - 补充 props/events 文档 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5af9f6e27a
commit
2475a4f901
@ -31,3 +31,25 @@
|
||||
- **事件回调函数:** (e: any) => void
|
||||
|
||||
注意:`Editor.vue` 中该 emit 的类型签名为 `[e: any]`,运行时通常为 `Error` 实例(来自 `submitForm` 抛错),但也可能是 element-plus 校验返回的 `invalidFields` 结构,业务侧消费时建议先做类型判断
|
||||
|
||||
## layer-node-dblclick
|
||||
|
||||
- **详情:** "已选组件"面板中组件树节点被双击时触发
|
||||
|
||||
默认行为(切换可展开节点的展开/收起状态)会先于该事件执行;可通过 [`beforeLayerNodeDblclick`](./props.md#beforelayernodedblclick) 钩子拦截,返回 `false` 时该事件不会被触发
|
||||
|
||||
- **事件回调函数:** (event: MouseEvent, data: [TreeNodeData](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/type.ts)) => void
|
||||
|
||||
- **示例:**
|
||||
|
||||
```html
|
||||
<template>
|
||||
<m-editor @layer-node-dblclick="onLayerNodeDblclick"></m-editor>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const onLayerNodeDblclick = (event, data) => {
|
||||
console.log('双击节点', data.id, data.type);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
@ -1292,6 +1292,49 @@ const layerNodeIsExpandable = (data, nodeStatusMap) => {
|
||||
第三方业务可从 `@tmagic/editor` 直接导入 `defaultIsExpandable` 复用默认逻辑作为兜底。
|
||||
:::
|
||||
|
||||
## beforeLayerNodeDblclick
|
||||
|
||||
- **详情:**
|
||||
|
||||
"已选组件"面板组件树节点双击前的钩子函数
|
||||
|
||||
在用户双击组件树节点时,先于默认行为执行;返回 `false` 时阻止默认行为(默认行为是切换可展开节点的展开/收起状态)。返回其他值(包括 `true`、`undefined`、`Promise`)则继续执行默认行为,并向上抛出 [`layer-node-dblclick`](./events.md#layer-node-dblclick) 事件。
|
||||
|
||||
常见用途:拦截特定类型节点的双击行为,或在双击时执行业务自定义动作(如重命名、打开抽屉等)后阻断默认展开/收起。
|
||||
|
||||
- **默认值:** `undefined`
|
||||
|
||||
- **类型:** `(event: MouseEvent, data: TreeNodeData) => boolean | void | Promise<boolean | void>`
|
||||
|
||||
- **示例:**
|
||||
|
||||
```html
|
||||
<template>
|
||||
<m-editor
|
||||
:before-layer-node-dblclick="beforeLayerNodeDblclick"
|
||||
@layer-node-dblclick="onLayerNodeDblclick"
|
||||
></m-editor>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 双击 page 节点时阻止默认的展开/收起行为
|
||||
const beforeLayerNodeDblclick = (event, data) => {
|
||||
if (data.type === 'page') {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const onLayerNodeDblclick = (event, data) => {
|
||||
console.log('双击节点', data.id);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
::: tip
|
||||
- 该钩子仅作用于"已选组件"面板的组件树节点,不影响画布上的双击行为(画布双击请使用 [`beforeDblclick`](#beforedblclick))。
|
||||
- 返回 `false` 时,会同时阻断默认的"展开/收起"行为以及向上抛出的 [`layer-node-dblclick`](./events.md#layer-node-dblclick) 事件;返回其他值则继续触发默认行为并抛出事件。
|
||||
:::
|
||||
|
||||
## extendFormState
|
||||
|
||||
- **详情:**
|
||||
|
||||
@ -28,6 +28,8 @@
|
||||
:next-level-indent-increment="treeNextLevelIndentIncrement"
|
||||
:layer-node-is-expandable="layerNodeIsExpandable"
|
||||
:can-drop-in="canDropIn"
|
||||
:before-layer-node-dblclick="beforeLayerNodeDblclick"
|
||||
@layer-node-dblclick="layerNodeDblclickHandler"
|
||||
>
|
||||
<template #layer-panel-header>
|
||||
<slot name="layer-panel-header"></slot>
|
||||
@ -157,6 +159,7 @@ import uiService from './services/ui';
|
||||
import keybindingConfig from './utils/keybinding-config';
|
||||
import { defaultEditorProps, EditorProps } from './editorProps';
|
||||
import { initServiceEvents, initServiceState } from './initService';
|
||||
import type { TreeNodeData } from './type';
|
||||
import type { EditorSlots, EventBus, Services, StageOptions } from './type';
|
||||
|
||||
defineSlots<EditorSlots>();
|
||||
@ -171,6 +174,7 @@ const emit = defineEmits<{
|
||||
'update:modelValue': [value: MApp | null];
|
||||
'props-form-error': [e: any];
|
||||
'props-submit-error': [e: any];
|
||||
'layer-node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||||
}>();
|
||||
|
||||
const props = withDefaults(defineProps<EditorProps>(), defaultEditorProps);
|
||||
@ -242,5 +246,9 @@ const propsPanelFormErrorHandler = (e: any) => {
|
||||
emit('props-form-error', e);
|
||||
};
|
||||
|
||||
const layerNodeDblclickHandler = (event: MouseEvent, data: TreeNodeData) => {
|
||||
emit('layer-node-dblclick', event, data);
|
||||
};
|
||||
|
||||
defineExpose(services);
|
||||
</script>
|
||||
|
||||
@ -56,6 +56,7 @@ const emit = defineEmits<{
|
||||
'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-mouseenter': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-click': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||||
}>();
|
||||
|
||||
provide('treeEmit', emit);
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
@click="expandHandler"
|
||||
></MIcon>
|
||||
|
||||
<div class="tree-node-content" @click="nodeClickHandler">
|
||||
<div class="tree-node-content" @click="nodeClickHandler" @dblclick="nodeDblclickHandler">
|
||||
<slot name="tree-node-content" :data="data">
|
||||
<div class="tree-node-label">
|
||||
<slot name="tree-node-label" :data="data">{{ `${data.name} (${data.id})` }}</slot>
|
||||
@ -89,6 +89,7 @@ const emit = defineEmits<{
|
||||
'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-mouseenter': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-click': [event: MouseEvent, data: TreeNodeData];
|
||||
'node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||||
}>();
|
||||
|
||||
const treeEmit = inject<typeof emit>('treeEmit');
|
||||
@ -156,4 +157,8 @@ const expandHandler = () => {
|
||||
const nodeClickHandler = (event: MouseEvent) => {
|
||||
treeEmit?.('node-click', event, props.data);
|
||||
};
|
||||
|
||||
const nodeDblclickHandler = (event: MouseEvent) => {
|
||||
treeEmit?.('node-dblclick', event, props.data);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -22,6 +22,7 @@ import type {
|
||||
PageBarSortOptions,
|
||||
SideBarData,
|
||||
StageRect,
|
||||
TreeNodeData,
|
||||
} from './type';
|
||||
|
||||
export interface EditorProps {
|
||||
@ -114,6 +115,8 @@ export interface EditorProps {
|
||||
canDropIn?: CanDropInFunction;
|
||||
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
||||
/** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeLayerNodeDblclick?: (event: MouseEvent, data: TreeNodeData) => Promise<boolean | void> | boolean | void;
|
||||
extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||
/** 页面顺序拖拽配置参数 */
|
||||
pageBarSortOptions?: PageBarSortOptions;
|
||||
|
||||
@ -173,6 +173,7 @@ import {
|
||||
type SideComponent,
|
||||
type SideItem,
|
||||
SideItemKey,
|
||||
type TreeNodeData,
|
||||
} from '@editor/type';
|
||||
|
||||
import CodeBlockListPanel from './code-block/CodeBlockListPanel.vue';
|
||||
@ -186,6 +187,10 @@ defineOptions({
|
||||
name: 'MEditorSidebar',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'layer-node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data?: SideBarData;
|
||||
@ -197,6 +202,8 @@ const props = withDefaults(
|
||||
layerNodeIsExpandable?: IsExpandableFunction;
|
||||
/** 自定义判断当前正在拖动的源是否可以拖入目标节点内部,详见 EditorProps.canDropIn */
|
||||
canDropIn?: CanDropInFunction;
|
||||
/** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeLayerNodeDblclick?: (_event: MouseEvent, _data: TreeNodeData) => Promise<boolean | void> | boolean | void;
|
||||
}>(),
|
||||
{
|
||||
data: () => ({
|
||||
@ -256,6 +263,10 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
||||
nextLevelIndentIncrement: props.nextLevelIndentIncrement,
|
||||
isExpandable: props.layerNodeIsExpandable,
|
||||
canDropIn: props.canDropIn,
|
||||
beforeNodeDblclick: props.beforeLayerNodeDblclick,
|
||||
},
|
||||
listeners: {
|
||||
'node-dblclick': (event: MouseEvent, nodeData: TreeNodeData) => emit('layer-node-dblclick', event, nodeData),
|
||||
},
|
||||
component: LayerPanel,
|
||||
slots: {},
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
@node-contextmenu="nodeContentMenuHandler"
|
||||
@node-mouseenter="mouseenterHandler"
|
||||
@node-click="nodeClickHandler"
|
||||
@node-dblclick="dblclickHandler"
|
||||
>
|
||||
<template #tree-node-content="{ data: nodeData }">
|
||||
<slot name="layer-node-content" :data="nodeData"> </slot>
|
||||
@ -89,6 +90,12 @@ const props = defineProps<{
|
||||
isExpandable?: IsExpandableFunction;
|
||||
/** 自定义判断当前拖动节点是否可以拖入目标节点内部的函数,返回 false 则禁止拖入 */
|
||||
canDropIn?: CanDropInFunction;
|
||||
/** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeNodeDblclick?: (_event: MouseEvent, _data: TreeNodeData) => Promise<boolean | void> | boolean | void;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||||
}>();
|
||||
|
||||
const services = useServices();
|
||||
@ -134,7 +141,19 @@ const { handleDragStart, handleDragEnd, handleDragLeave, handleDragOver } = useD
|
||||
const menuRef = useTemplateRef<InstanceType<typeof LayerMenu>>('menu');
|
||||
const {
|
||||
nodeClickHandler,
|
||||
nodeDblclickHandler,
|
||||
nodeContentMenuHandler,
|
||||
highlightHandler: mouseenterHandler,
|
||||
} = useClick(services, isCtrlKeyDown, nodeStatusMap, menuRef);
|
||||
|
||||
const dblclickHandler = async (event: MouseEvent, data: TreeNodeData) => {
|
||||
if (props.beforeNodeDblclick) {
|
||||
const result = await props.beforeNodeDblclick(event, data);
|
||||
if (result === false) return;
|
||||
}
|
||||
|
||||
nodeDblclickHandler(event, data);
|
||||
|
||||
emit('node-dblclick', event, data);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -100,11 +100,27 @@ export const useClick = (
|
||||
});
|
||||
};
|
||||
|
||||
const nodeDblclickHandler = (event: MouseEvent, data: TreeNodeData): void => {
|
||||
if (!nodeStatusMap?.value) return;
|
||||
|
||||
if (uiService.get('uiSelectMode')) return;
|
||||
|
||||
// 双击切换展开/收起状态
|
||||
if (data.items && data.items.length > 0) {
|
||||
const status = nodeStatusMap.value.get(data.id);
|
||||
updateStatus(nodeStatusMap.value, data.id, {
|
||||
expand: !status?.expand,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
menuRef,
|
||||
|
||||
nodeClickHandler,
|
||||
|
||||
nodeDblclickHandler,
|
||||
|
||||
nodeContentMenuHandler(event: MouseEvent, data: TreeNodeData): void {
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user