Skip to content

Commit 844ca78

Browse files
liujupingJackLian
authored andcommitted
feat(context-menu): add context-menu css theme, help config, ts define
1 parent 6f9359e commit 844ca78

File tree

8 files changed

+81
-46
lines changed

8 files changed

+81
-46
lines changed

docs/docs/guide/expand/editor/theme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ sidebar_position: 9
5353
- `--color-text-reverse`: 反色情况下,文字颜色
5454
- `--color-text-disabled`: 禁用态文字颜色
5555

56+
#### 菜单颜色
57+
- `--color-context-menu-text`: 菜单项颜色
58+
- `--color-context-menu-text-hover`: 菜单项 hover 颜色
59+
- `--color-context-menu-text-disabled`: 菜单项 disabled 颜色
60+
5661
#### 字段和边框颜色
5762

5863
- `--color-field-label`: field 标签颜色

packages/designer/src/builtin-simulator/host.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,16 +832,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
832832
doc.addEventListener('contextmenu', (e: MouseEvent) => {
833833
const targetElement = e.target as HTMLElement;
834834
const nodeInst = this.getNodeInstanceFromElement(targetElement);
835+
const editor = this.designer?.editor;
835836
if (!nodeInst) {
837+
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
838+
originalEvent: e,
839+
});
836840
return;
837841
}
838842
const node = nodeInst.node || this.project.currentDocument?.focusNode;
839843
if (!node) {
844+
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
845+
originalEvent: e,
846+
});
840847
return;
841848
}
842849

843850
// dirty code should refector
844-
const editor = this.designer?.editor;
845851
const npm = node?.componentMeta?.npm;
846852
const selected =
847853
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||

packages/designer/src/context-menu-actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ export class ContextMenuActions implements IContextMenuActions {
201201
node: INode;
202202
originalEvent: MouseEvent;
203203
}) => {
204+
originalEvent.stopPropagation();
205+
originalEvent.preventDefault();
204206
// 如果右键的节点不在 当前选中的节点中,选中该节点
205207
if (!designer.currentSelection.has(node.id)) {
206208
designer.currentSelection.select(node.id);

packages/engine/src/inner-plugins/default-context-menu.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
6060
material.addContextMenuOption({
6161
name: 'selectComponent',
6262
title: intl('SelectComponents'),
63-
condition: (nodes) => {
63+
condition: (nodes = []) => {
6464
return nodes.length === 1;
6565
},
6666
items: [
@@ -74,14 +74,17 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
7474
material.addContextMenuOption({
7575
name: 'copyAndPaste',
7676
title: intl('CopyAndPaste'),
77-
disabled: (nodes) => {
77+
disabled: (nodes = []) => {
7878
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
7979
},
8080
condition: (nodes) => {
81-
return nodes.length === 1;
81+
return nodes?.length === 1;
8282
},
8383
action(nodes) {
84-
const node = nodes[0];
84+
const node = nodes?.[0];
85+
if (!node) {
86+
return;
87+
}
8588
const { document: doc, parent, index } = node;
8689
const data = getNodesSchema(nodes);
8790
clipboard.setData(data);
@@ -96,11 +99,11 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
9699
material.addContextMenuOption({
97100
name: 'copy',
98101
title: intl('Copy'),
99-
disabled: (nodes) => {
102+
disabled: (nodes = []) => {
100103
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
101104
},
102-
condition(nodes) {
103-
return nodes.length > 0;
105+
condition(nodes = []) {
106+
return nodes?.length > 0;
104107
},
105108
action(nodes) {
106109
if (!nodes || nodes.length < 1) {
@@ -116,7 +119,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
116119
name: 'pasteToBottom',
117120
title: intl('PasteToTheBottom'),
118121
condition: (nodes) => {
119-
return nodes.length === 1;
122+
return nodes?.length === 1;
120123
},
121124
async action(nodes) {
122125
if (!nodes || nodes.length < 1) {
@@ -163,15 +166,18 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
163166
name: 'pasteToInner',
164167
title: intl('PasteToTheInside'),
165168
condition: (nodes) => {
166-
return nodes.length === 1;
169+
return nodes?.length === 1;
167170
},
168-
disabled: (nodes) => {
171+
disabled: (nodes = []) => {
169172
// 获取粘贴数据
170-
const node = nodes[0];
173+
const node = nodes?.[0];
171174
return !node.isContainerNode;
172175
},
173176
async action(nodes) {
174-
const node = nodes[0];
177+
const node = nodes?.[0];
178+
if (!node) {
179+
return;
180+
}
175181
const { document: doc } = node;
176182

177183
try {
@@ -210,14 +216,14 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
210216
material.addContextMenuOption({
211217
name: 'delete',
212218
title: intl('Delete'),
213-
disabled(nodes) {
219+
disabled(nodes = []) {
214220
return nodes?.filter((node) => !node?.canPerformAction('remove')).length > 0;
215221
},
216-
condition(nodes) {
222+
condition(nodes = []) {
217223
return nodes.length > 0;
218224
},
219225
action(nodes) {
220-
nodes.forEach((node) => {
226+
nodes?.forEach((node) => {
221227
node.remove();
222228
});
223229
},

packages/shell/src/components/context-menu.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,13 @@ export function ContextMenu({ children, menus, pluginContext }: {
2525
const children: React.ReactNode[] = parseContextMenuAsReactNode(parseContextMenuProperties(menus, {
2626
destroy,
2727
pluginContext,
28-
}), {
29-
pluginContext,
30-
});
28+
}), { pluginContext });
3129

3230
if (!children?.length) {
3331
return;
3432
}
3533

36-
destroyFn = createContextMenu(children, {
37-
event,
38-
});
34+
destroyFn = createContextMenu(children, { event });
3935
};
4036

4137
// 克隆 children 并添加 onContextMenu 事件处理器

packages/types/src/shell/type/context-menu.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IPublicEnumContextMenuType } from '../enum';
22
import { IPublicModelNode } from '../model';
33
import { IPublicTypeI18nData } from './i8n-data';
4+
import { IPublicTypeHelpTipConfig } from './widget-base-config';
45

56
export interface IPublicTypeContextMenuItem extends Omit<IPublicTypeContextMenuAction, 'condition' | 'disabled' | 'items'> {
67
disabled?: boolean;
@@ -34,24 +35,29 @@ export interface IPublicTypeContextMenuAction {
3435
* 点击时执行的动作,可选
3536
* Action to execute on click, optional
3637
*/
37-
action?: (nodes: IPublicModelNode[], event?: MouseEvent) => void;
38+
action?: (nodes?: IPublicModelNode[], event?: MouseEvent) => void;
3839

3940
/**
4041
* 子菜单项或生成子节点的函数,可选,仅支持两级
4142
* Sub-menu items or function to generate child node, optional
4243
*/
43-
items?: Omit<IPublicTypeContextMenuAction, 'items'>[] | ((nodes: IPublicModelNode[]) => Omit<IPublicTypeContextMenuAction, 'items'>[]);
44+
items?: Omit<IPublicTypeContextMenuAction, 'items'>[] | ((nodes?: IPublicModelNode[]) => Omit<IPublicTypeContextMenuAction, 'items'>[]);
4445

4546
/**
4647
* 显示条件函数
4748
* Function to determine display condition
4849
*/
49-
condition?: (nodes: IPublicModelNode[]) => boolean;
50+
condition?: (nodes?: IPublicModelNode[]) => boolean;
5051

5152
/**
5253
* 禁用条件函数,可选
5354
* Function to determine disabled condition, optional
5455
*/
55-
disabled?: (nodes: IPublicModelNode[]) => boolean;
56+
disabled?: (nodes?: IPublicModelNode[]) => boolean;
57+
58+
/**
59+
* 帮助提示,可选
60+
*/
61+
help?: IPublicTypeHelpTipConfig;
5662
}
5763

packages/utils/src/context-menu.scss

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,31 @@
1010

1111
.engine-context-menu-item {
1212
.engine-context-menu-text {
13-
color: var(--color-text);
13+
color: var(--color-context-menu-text, var(--color-text));
14+
display: flex;
15+
align-items: center;
16+
17+
.lc-help-tip {
18+
margin-left: 4px;
19+
opacity: 0.8;
20+
}
1421
}
1522

16-
&:hover {
17-
.engine-context-menu-text {
18-
color: var(--color-title);
23+
&.disabled {
24+
&:hover .engine-context-menu-text, .engine-context-menu-text {
25+
color: var(--color-context-menu-text-disabled, var(--color-text-disabled));
1926
}
2027
}
2128

22-
&.disbale {
29+
&:hover {
2330
.engine-context-menu-text {
24-
color: var(--color-text-disabled);
31+
color: var(--color-context-menu-text-hover, var(--color-title));
2532
}
2633
}
2734
}
2835

2936
.engine-context-menu-title {
30-
color: var(--color-text);
37+
color: var(--color-context-menu-text, var(--color-text));
3138
cursor: pointer;
3239

3340
&:hover {

packages/utils/src/context-menu.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ const Tree = (props: {
6464
let destroyFn: Function | undefined;
6565

6666
export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[], options: IOptions): React.ReactNode[] {
67-
const { common } = options.pluginContext || {};
67+
const { common, commonUI } = options.pluginContext || {};
6868
const { intl = (title: any) => title } = common?.utils || {};
69+
const { HelpTip } = commonUI || {};
6970

7071
const children: React.ReactNode[] = [];
7172
menus.forEach((menu, index) => {
@@ -79,7 +80,7 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
7980
children.push((
8081
<PopupItem
8182
className={classNames('engine-context-menu-item', {
82-
disbale: menu.disabled,
83+
disabled: menu.disabled,
8384
})}
8485
key={menu.name}
8586
label={<div className="engine-context-menu-text">{intl(menu.title)}</div>}
@@ -93,14 +94,17 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
9394
children.push((
9495
<Item
9596
className={classNames('engine-context-menu-item', {
96-
disbale: menu.disabled,
97+
disabled: menu.disabled,
9798
})}
9899
disabled={menu.disabled}
99-
onClick={menu.action}
100+
onClick={() => {
101+
menu.action?.();
102+
}}
100103
key={menu.name}
101104
>
102105
<div className="engine-context-menu-text">
103-
{intl(menu.title)}
106+
{ menu.title ? intl(menu.title) : null }
107+
{ menu.help ? <HelpTip size="xs" help={menu.help} direction="right" /> : null }
104108
</div>
105109
</Item>
106110
));
@@ -135,12 +139,14 @@ export function parseContextMenuProperties(menus: (IPublicTypeContextMenuAction
135139
name,
136140
title,
137141
type = IPublicEnumContextMenuType.MENU_ITEM,
142+
help,
138143
} = menu;
139144

140145
const result: IPublicTypeContextMenuItem = {
141146
name,
142147
title,
143148
type,
149+
help,
144150
action: () => {
145151
destroy?.();
146152
menu.action?.(nodes || [], options.event);
@@ -193,26 +199,27 @@ export function createContextMenu(children: React.ReactNode[], {
193199
event: MouseEvent | React.MouseEvent;
194200
offset?: [number, number];
195201
}) {
202+
event.preventDefault();
203+
event.stopPropagation();
204+
196205
const viewportWidth = window.innerWidth;
197206
const viewportHeight = window.innerHeight;
198207
const dividerCount = React.Children.count(children.filter(child => React.isValidElement(child) && child.type === Divider));
199208
const popupItemCount = React.Children.count(children.filter(child => React.isValidElement(child) && (child.type === PopupItem || child.type === Item)));
200209
const menuHeight = popupItemCount * parseInt(getMenuItemHeight(), 10) + dividerCount * 8 + 16;
201210
const menuWidthLimit = 200;
202-
const target = event.target;
203-
const { top, left } = (target as any)?.getBoundingClientRect();
204-
let x = event.clientX - left + offset[0];
205-
let y = event.clientY - top + offset[1];
206-
if (x + menuWidthLimit + left > viewportWidth) {
211+
let x = event.clientX + offset[0];
212+
let y = event.clientY + offset[1];
213+
if (x + menuWidthLimit > viewportWidth) {
207214
x = x - menuWidthLimit;
208215
}
209-
if (y + menuHeight + top > viewportHeight) {
216+
if (y + menuHeight > viewportHeight) {
210217
y = y - menuHeight;
211218
}
212219

213220
const menuInstance = Menu.create({
214-
target,
215-
offset: [x, y, 0, 0],
221+
target: document.body,
222+
offset: [x, y],
216223
children,
217224
className: 'engine-context-menu',
218225
});

0 commit comments

Comments
 (0)