UI④:元素盘点击落子真能落 —— 原型里这条路是坏的

事件流实测:click → drag.init → create.start → create.move → **create.rejected**。
create.move 要有 hover 目标才算 canExecute,而 click 到达时鼠标早已抬起,
拖拽在同一个调用栈里就被拒 —— 工具提示写着「点击后点画布落子」,一颗子也落不下。
点击这条路改为直接 modeling.createShape 到空位(撞车往下让、一列满换列);
拖拽那条路不动。实测:落子 32→33、追加自动连线 33→34、撤销回退干净。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-28 02:49:50 +08:00
co-authored by Claude Fable 5
parent 5a8589046b
commit af789f546c
@@ -441,36 +441,92 @@ export const PALETTE_SPECS: Array<{ key: string; label: string; className: strin
];
class FlowPalette {
static $inject = ["palette", "create", "elementFactory", "elementRegistry", "flowEditorConfig"];
static $inject = ["palette", "create", "elementFactory", "elementRegistry", "flowEditorConfig", "modeling", "canvas"];
private _create: Any;
private _elementFactory: Any;
private _elementRegistry: Any;
private _config: Any;
private _modeling: Any;
private _canvas: Any;
constructor(palette: Any, create: Any, elementFactory: Any, elementRegistry: Any, flowEditorConfig: Any) {
constructor(
palette: Any,
create: Any,
elementFactory: Any,
elementRegistry: Any,
flowEditorConfig: Any,
modeling: Any,
canvas: Any,
) {
this._create = create;
this._elementFactory = elementFactory;
this._elementRegistry = elementRegistry;
this._config = flowEditorConfig;
this._modeling = modeling;
this._canvas = canvas;
palette.registerProvider(this);
}
/** 造一枚待落的新节点(id 现取,避开已用与保留号) */
private _mint(spec: NewNodeSpec): Any {
const bo = specNode(spec);
bo.id = mintNodeId(spec.kind, usedIds(this._elementRegistry), this._config.reserved);
const size = MANUAL_SIZES[spec.kind];
return this._elementFactory.createShape({ width: size.w, height: size.h, businessObject: bo });
}
/**
* 点击落子的落点:视口中心起,撞车就往下让一格,直到空位。
*
* 为什么不复用拖拽那条路:`create.start` 靠鼠标**移动**取 hover 目标才算 `canExecute`,
* 而 click 事件到达时鼠标早已抬起 —— 拖拽在同一个调用栈里就以 `create.rejected` 收场,
* 一颗子也落不下(实测)。所以点击这条路直接建,不走拖拽。
*/
private _freeSpot(shape: Any): { x: number; y: number } {
const viewbox = this._canvas.viewbox();
let x = snap(viewbox.x + viewbox.width / 2);
let y = snap(viewbox.y + Math.min(viewbox.height / 2, 200));
const hits = (px: number, py: number) =>
this._elementRegistry
.getAll()
.some(
(el: Any) =>
!el.waypoints &&
el.parent &&
!el.labelTarget &&
Math.abs(el.x + el.width / 2 - px) < (el.width + shape.width) / 2 + 20 &&
Math.abs(el.y + el.height / 2 - py) < (el.height + shape.height) / 2 + 20,
);
let guard = 0;
while (hits(x, y) && guard < 40) {
y = snap(y + 70);
guard += 1;
// 一列排满就换到右边一列重来,别无限往下掉出视野
if (guard % 8 === 0) {
x = snap(x + 160);
y = snap(viewbox.y + Math.min(viewbox.height / 2, 200));
}
}
return { x, y };
}
getPaletteEntries(): Record<string, Any> {
const entries: Record<string, Any> = {};
for (const item of PALETTE_SPECS) {
const action = (event: Any) => {
const bo = specNode(item.spec);
bo.id = mintNodeId(item.spec.kind, usedIds(this._elementRegistry), this._config.reserved);
const size = MANUAL_SIZES[item.spec.kind];
const shape = this._elementFactory.createShape({ width: size.w, height: size.h, businessObject: bo });
this._create.start(event, shape);
};
entries[item.key] = {
group: "flow",
className: item.className,
title: `${item.label} — 拖到画布或点击后点画布落子`,
action: { dragstart: action, click: action },
title: `${item.label} — 拖到画布,或直接点击(落在空位上,再拖到想要的地方)`,
action: {
// 拖:鼠标一路带着走,落点由手定
dragstart: (event: Any) => this._create.start(event, this._mint(item.spec)),
// 点:当场落在空位上 —— 见 `_freeSpot` 注释,点击走不通拖拽那条路
click: () => {
const shape = this._mint(item.spec);
this._modeling.createShape(shape, this._freeSpot(shape), this._canvas.getRootElement());
},
},
};
}
return entries;