@arronqzy/vue-blueprint 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { useI18n } from "@arronqzy/i18n/vue";
1
2
  import {
2
3
  addEdge,
3
4
  applyEdgeChanges,
@@ -8,10 +9,14 @@ import {
8
9
  type Node,
9
10
  type NodeChange,
10
11
  } from "@vue-flow/core";
12
+ import { message } from "ant-design-vue";
11
13
  import { onBeforeUnmount, ref, watch, type Ref } from "vue";
12
14
 
13
15
  import type { BlueprintGraph } from "../graph/blueprint-graph";
14
- import { sanitizeBlueprintDocument } from "../graph/document";
16
+ import {
17
+ resolveBlueprintConnectionIssue,
18
+ sanitizeBlueprintDocument,
19
+ } from "../graph/document";
15
20
  import {
16
21
  applyFlowNodePositions,
17
22
  BP_EDGE_STYLE,
@@ -424,6 +429,7 @@ export function useBlueprintFlowState({
424
429
  }
425
430
 
426
431
  function onConnect(connection: Connection) {
432
+ pendingConnectionIssue = null;
427
433
  const resolved = resolveConnection(
428
434
  connection,
429
435
  nodes.value as Node<BlueprintFlowNodeData>[]
@@ -456,6 +462,11 @@ export function useBlueprintFlowState({
456
462
  });
457
463
  }
458
464
 
465
+ const { t } = useI18n();
466
+ let pendingConnectionIssue: ReturnType<
467
+ typeof resolveBlueprintConnectionIssue
468
+ > = null;
469
+
459
470
  function isValidConnection(connection: Edge | Connection) {
460
471
  if (!connection.source || !connection.target) return false;
461
472
  if (connection.source === connection.target) return false;
@@ -467,11 +478,26 @@ export function useBlueprintFlowState({
467
478
  }
468
479
 
469
480
  const targetNode = rfNodes.find((n) => n.id === connection.target);
470
- if (targetNode?.data?.role === "lifecycle") {
471
- return false;
472
- }
481
+ const sourceNode = rfNodes.find((n) => n.id === connection.source);
482
+ const issue = resolveBlueprintConnectionIssue(
483
+ sourceNode?.data,
484
+ targetNode?.data
485
+ );
486
+ pendingConnectionIssue = issue;
487
+ return !issue;
488
+ }
473
489
 
474
- return true;
490
+ function onConnectEnd(event?: MouseEvent | TouchEvent) {
491
+ const issue = pendingConnectionIssue;
492
+ pendingConnectionIssue = null;
493
+ if (!issue || !isPointerOverFlowNode(event)) return;
494
+ if (issue === "event-requires-lifecycle") {
495
+ message.warning(t("blueprint.config.eventConnectNeedLifecycle"));
496
+ return;
497
+ }
498
+ if (issue === "lifecycle-no-input") {
499
+ message.warning(t("blueprint.config.lifecycleNoInput"));
500
+ }
475
501
  }
476
502
 
477
503
  return {
@@ -481,6 +507,26 @@ export function useBlueprintFlowState({
481
507
  onEdgesChange,
482
508
  onNodeDragStop,
483
509
  onConnect,
510
+ onConnectEnd,
484
511
  isValidConnection,
485
512
  };
486
513
  }
514
+
515
+ function isPointerOverFlowNode(event?: MouseEvent | TouchEvent) {
516
+ if (!event) return false;
517
+ const point =
518
+ "changedTouches" in event
519
+ ? event.changedTouches[0]
520
+ : "clientX" in event
521
+ ? event
522
+ : undefined;
523
+ const el =
524
+ point && typeof document !== "undefined"
525
+ ? document.elementFromPoint(point.clientX, point.clientY)
526
+ : event.target instanceof Element
527
+ ? event.target
528
+ : null;
529
+ return Boolean(
530
+ el?.closest(".vue-flow__node") || el?.closest(".vue-flow__handle")
531
+ );
532
+ }
@@ -1,12 +1,17 @@
1
1
  import { onUnmounted, ref, shallowRef, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue";
2
2
  import {
3
3
  BlueprintGraphRunner,
4
+ collectArmedViewEventNodeIds,
4
5
  detectBlueprintReferenceCycle,
6
+ EVENT_NODE_TYPE,
7
+ LIFECYCLE_NODE_TYPE,
5
8
  type LibraryBlueprintResolver,
6
9
  type PageLifecyclePhase,
10
+ type ViewEventSignal,
7
11
  } from "@arronqzy/blueprint-dsl";
8
12
 
9
13
  import type { BlueprintGraph } from "../graph/blueprint-graph";
14
+ import { resolveRunnableNodeType } from "../graph/document";
10
15
  import { documentToRunnableGraph } from "../runtime/document-to-runnable-graph";
11
16
 
12
17
  const PAGE_BOOT_PHASES: PageLifecyclePhase[] = [
@@ -87,12 +92,30 @@ function createRunner(graph: BlueprintGraph, options: RunnerOptions) {
87
92
  );
88
93
  }
89
94
 
95
+ function collectArmedIds(
96
+ graph: BlueprintGraph,
97
+ firedPhases: ReadonlySet<string>
98
+ ) {
99
+ return collectArmedViewEventNodeIds(
100
+ graph.document.nodes.map((node) => ({
101
+ id: node.id,
102
+ nodeType: resolveRunnableNodeType(node),
103
+ lifecyclePhase: node.lifecyclePhase,
104
+ })),
105
+ graph.document.edges,
106
+ EVENT_NODE_TYPE,
107
+ LIFECYCLE_NODE_TYPE,
108
+ firedPhases
109
+ );
110
+ }
111
+
90
112
  async function emitPhases(
91
113
  graph: BlueprintGraph,
92
114
  phases: PageLifecyclePhase[],
93
- options: RunnerOptions
94
- ) {
95
- if (!phases.length) return;
115
+ options: RunnerOptions,
116
+ onPhaseFired?: (phase: PageLifecyclePhase) => void
117
+ ): Promise<boolean> {
118
+ if (!phases.length) return true;
96
119
 
97
120
  const rootLibraryBlueprintId = toValue(options.rootLibraryBlueprintId ?? null);
98
121
  const libraryNameById = toValue(options.libraryNameById);
@@ -109,14 +132,16 @@ async function emitPhases(
109
132
  });
110
133
  if (!result.ok) {
111
134
  options.onExecutionBlocked?.(result.message);
112
- return;
135
+ return false;
113
136
  }
114
137
  }
115
138
 
116
139
  const runner = createRunner(graph, options);
117
140
  for (const phase of phases) {
118
141
  await runner.emitLifecycle(phase);
142
+ onPhaseFired?.(phase);
119
143
  }
144
+ return true;
120
145
  }
121
146
 
122
147
  export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOptions) {
@@ -133,10 +158,22 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
133
158
  const bootPhasesKey = bootPhases.join("|");
134
159
  const bootTrigger = options.bootKey ?? "__initial__";
135
160
  const bootCompleted = ref(false);
161
+ const firedLifecyclePhases = ref<ReadonlySet<PageLifecyclePhase>>(new Set());
136
162
  const hadBoot = ref(false);
137
163
  const prevActive = ref<boolean | undefined>(undefined);
138
164
  let bootCancelled = false;
139
165
 
166
+ const resetFiredPhases = () => {
167
+ firedLifecyclePhases.value = new Set();
168
+ };
169
+
170
+ const markPhaseFired = (phase: PageLifecyclePhase) => {
171
+ if (firedLifecyclePhases.value.has(phase)) return;
172
+ const next = new Set(firedLifecyclePhases.value);
173
+ next.add(phase);
174
+ firedLifecyclePhases.value = next;
175
+ };
176
+
140
177
  watch(
141
178
  () => resolveValue(options.graph),
142
179
  (next) => {
@@ -163,10 +200,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
163
200
  const enabled = resolveValue(options.enabled ?? true);
164
201
  if (!enabled) {
165
202
  bootCompleted.value = false;
203
+ resetFiredPhases();
166
204
  return;
167
205
  }
168
206
 
169
207
  bootCancelled = false;
208
+ resetFiredPhases();
170
209
  const waitForPageReady = options.waitForPageReady ?? false;
171
210
 
172
211
  if (waitForPageReady) {
@@ -174,12 +213,27 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
174
213
  }
175
214
  if (bootCancelled) return;
176
215
 
177
- await emitPhases(graphRef.value, bootPhases, runnerOptionsRef.value);
216
+ const bootOk = await emitPhases(
217
+ graphRef.value,
218
+ bootPhases,
219
+ runnerOptionsRef.value,
220
+ (phase) => {
221
+ if (!bootCancelled) markPhaseFired(phase);
222
+ }
223
+ );
178
224
  if (bootCancelled) return;
225
+ if (!bootOk) return;
179
226
 
180
227
  const active = resolveValue(options.active ?? true);
181
228
  if (active) {
182
- await emitPhases(graphRef.value, ["activated"], runnerOptionsRef.value);
229
+ await emitPhases(
230
+ graphRef.value,
231
+ ["activated"],
232
+ runnerOptionsRef.value,
233
+ (phase) => {
234
+ if (!bootCancelled) markPhaseFired(phase);
235
+ }
236
+ );
183
237
  }
184
238
  if (bootCancelled) return;
185
239
 
@@ -191,6 +245,7 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
191
245
  const teardownBoot = () => {
192
246
  bootCancelled = true;
193
247
  bootCompleted.value = false;
248
+ resetFiredPhases();
194
249
  prevActive.value = undefined;
195
250
  if (hadBoot.value) {
196
251
  hadBoot.value = false;
@@ -222,10 +277,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
222
277
  return;
223
278
  }
224
279
  prevActive.value = active;
280
+ const phase: PageLifecyclePhase = active ? "activated" : "deactivated";
225
281
  void emitPhases(
226
282
  graphRef.value,
227
- [active ? "activated" : "deactivated"],
228
- runnerOptionsRef.value
283
+ [phase],
284
+ runnerOptionsRef.value,
285
+ markPhaseFired
229
286
  );
230
287
  }
231
288
  );
@@ -234,7 +291,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
234
291
  () => [bootCompleted.value, resolveValue(options.enabled ?? true), resolveValue(options.onUpdated)],
235
292
  ([completed, enabled]) => {
236
293
  if (!enabled || !completed) return;
237
- void emitPhases(graphRef.value, ["updated"], runnerOptionsRef.value);
294
+ void emitPhases(
295
+ graphRef.value,
296
+ ["updated"],
297
+ runnerOptionsRef.value,
298
+ markPhaseFired
299
+ );
238
300
  }
239
301
  );
240
302
 
@@ -267,5 +329,32 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
267
329
  await runner.triggerNode(id, inputValue);
268
330
  }
269
331
 
270
- return { triggerBlueprintNode };
332
+ async function emitViewEvent(payload: ViewEventSignal) {
333
+ if (!payload?.node?.id || !payload.eventType) return;
334
+ const opts = runnerOptionsRef.value;
335
+ const libraryNameById = toValue(opts.libraryNameById);
336
+ const rootLibraryBlueprintId = toValue(opts.rootLibraryBlueprintId ?? null);
337
+ if (opts.resolveLibraryBlueprint) {
338
+ const result = await detectBlueprintReferenceCycle({
339
+ rootGraph: documentToRunnableGraph(graphRef.value.document, {
340
+ libraryNameById,
341
+ }),
342
+ rootLibraryBlueprintId,
343
+ resolveLibraryBlueprint: opts.resolveLibraryBlueprint,
344
+ resolveBlueprintName: (libId) =>
345
+ resolveBlueprintName(libraryNameById, libId),
346
+ });
347
+ if (!result.ok) {
348
+ opts.onExecutionBlocked?.(result.message);
349
+ return;
350
+ }
351
+ }
352
+ const runner = createRunner(graphRef.value, opts);
353
+ await runner.emitViewEvent(
354
+ payload,
355
+ collectArmedIds(graphRef.value, firedLifecyclePhases.value)
356
+ );
357
+ }
358
+
359
+ return { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases };
271
360
  }
@@ -5,6 +5,7 @@ import {
5
5
  filterInvalidBlueprintEdges,
6
6
  patchNodeConfigSource,
7
7
  resolveNodeClockConfig,
8
+ resolveNodeEventConfig,
8
9
  resolveNodeFetchConfig,
9
10
  resolveNodeJsonConfig,
10
11
  resolveNodeLogicConfig,
@@ -198,6 +199,7 @@ export class BlueprintGraph {
198
199
  | "jsonConfig"
199
200
  | "logicConfig"
200
201
  | "clockConfig"
202
+ | "eventConfig"
201
203
  >
202
204
  >
203
205
  ) {
@@ -251,6 +253,12 @@ export class BlueprintGraph {
251
253
  ...patch.clockConfig,
252
254
  };
253
255
  }
256
+ if (patch.eventConfig) {
257
+ nextNode.eventConfig = {
258
+ ...resolveNodeEventConfig(n),
259
+ ...patch.eventConfig,
260
+ };
261
+ }
254
262
  return {
255
263
  ...nextNode,
256
264
  nodeType: resolveRunnableNodeType(nextNode),
@@ -4,6 +4,8 @@ import type {
4
4
  JsonNodeConfig,
5
5
  LogicNodeConfig,
6
6
  PageLifecyclePhase,
7
+ ViewEventNodeConfig,
8
+ ViewEventType,
7
9
  } from "@arronqzy/blueprint-dsl";
8
10
  import {
9
11
  BLUEPRINT_NODE_TYPE,
@@ -14,10 +16,13 @@ import {
14
16
  DEFAULT_JSON_NODE_CONFIG,
15
17
  DEFAULT_LOGIC_NODE_CONFIG,
16
18
  DEFAULT_LOGIC_NODE_TYPE,
19
+ DEFAULT_VIEW_EVENT_NODE_CONFIG,
20
+ EVENT_NODE_TYPE,
17
21
  FETCH_NODE_TYPE,
18
22
  JSON_NODE_TYPE,
19
23
  LIFECYCLE_NODE_TYPE,
20
24
  normalizeClockConfig,
25
+ normalizeViewEventConfig,
21
26
  VIEW_NODE_TYPE,
22
27
  } from "@arronqzy/blueprint-dsl";
23
28
 
@@ -25,8 +30,9 @@ export type { FetchRequestConfig as BlueprintFetchConfig } from "@arronqzy/bluep
25
30
  export type { JsonNodeConfig as BlueprintJsonConfig } from "@arronqzy/blueprint-dsl";
26
31
  export type { LogicNodeConfig as BlueprintLogicConfig } from "@arronqzy/blueprint-dsl";
27
32
  export type { ClockNodeConfig as BlueprintClockConfig } from "@arronqzy/blueprint-dsl";
33
+ export type { ViewEventNodeConfig as BlueprintEventConfig } from "@arronqzy/blueprint-dsl";
28
34
 
29
- export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock";
35
+ export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock" | "event";
30
36
 
31
37
  /** 决定右侧配置面板展示哪类配置 */
32
38
  export type BlueprintConfigSource =
@@ -37,7 +43,8 @@ export type BlueprintConfigSource =
37
43
  | "view"
38
44
  | "fetch"
39
45
  | "json"
40
- | "clock";
46
+ | "clock"
47
+ | "event";
41
48
 
42
49
  export type BlueprintGraphNode = {
43
50
  id: string;
@@ -68,6 +75,8 @@ export type BlueprintGraphNode = {
68
75
  logicConfig?: LogicNodeConfig;
69
76
  /** 时钟节点的间隔与时间格式配置 */
70
77
  clockConfig?: ClockNodeConfig;
78
+ /** 视图事件节点监听的事件类型 */
79
+ eventConfig?: ViewEventNodeConfig;
71
80
  };
72
81
 
73
82
  export function resolveViewElementIds(
@@ -94,6 +103,7 @@ export function resolveBlueprintConfigSource(
94
103
  ): BlueprintConfigSource {
95
104
  if (node.configSource) return node.configSource;
96
105
  if (node.role === "lifecycle") return "lifecycle";
106
+ if (node.role === "event") return "event";
97
107
  if (node.role === "clock") return "clock";
98
108
  if (node.role === "and") return "and";
99
109
  if (node.role === "fetch") return "fetch";
@@ -101,11 +111,60 @@ export function resolveBlueprintConfigSource(
101
111
  if (node.nodeType === FETCH_NODE_TYPE) return "fetch";
102
112
  if (node.nodeType === JSON_NODE_TYPE) return "json";
103
113
  if (node.nodeType === CLOCK_NODE_TYPE) return "clock";
114
+ if (node.nodeType === EVENT_NODE_TYPE) return "event";
104
115
  if (node.nodeType === VIEW_NODE_TYPE) return "view";
105
116
  if (resolveViewElementIds(node).length > 0) return "view";
106
117
  return node.role === "logic" ? "logic" : "blueprint";
107
118
  }
108
119
 
120
+ export type BlueprintConfigSourceNode = Pick<
121
+ BlueprintGraphNode,
122
+ "role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds"
123
+ >;
124
+
125
+ export type BlueprintConnectionIssue =
126
+ | "lifecycle-no-input"
127
+ | "event-requires-lifecycle";
128
+
129
+ /** 连入校验:生命周期无输入;事件节点输入必须全部来自生命周期节点 */
130
+ export function resolveBlueprintConnectionIssue(
131
+ source: BlueprintConfigSourceNode | undefined,
132
+ target: BlueprintConfigSourceNode | undefined
133
+ ): BlueprintConnectionIssue | null {
134
+ if (!source || !target) return null;
135
+ const targetSource = resolveBlueprintConfigSource(target);
136
+ if (targetSource === "lifecycle") return "lifecycle-no-input";
137
+ if (
138
+ targetSource === "event" &&
139
+ resolveBlueprintConfigSource(source) !== "lifecycle"
140
+ ) {
141
+ return "event-requires-lifecycle";
142
+ }
143
+ return null;
144
+ }
145
+
146
+ export function inspectEventNodeInputs(
147
+ nodeId: string,
148
+ nodes: Array<BlueprintConfigSourceNode & { id: string }>,
149
+ edges: Array<{ source: string; target: string }>
150
+ ) {
151
+ const byId = new Map(nodes.map((node) => [node.id, node]));
152
+ let incomingCount = 0;
153
+ let lifecycleCount = 0;
154
+ let nonLifecycleCount = 0;
155
+ for (const edge of edges) {
156
+ if (edge.target !== nodeId) continue;
157
+ incomingCount += 1;
158
+ const source = byId.get(edge.source);
159
+ if (source && resolveRunnableNodeType(source) === LIFECYCLE_NODE_TYPE) {
160
+ lifecycleCount += 1;
161
+ } else {
162
+ nonLifecycleCount += 1;
163
+ }
164
+ }
165
+ return { incomingCount, lifecycleCount, nonLifecycleCount };
166
+ }
167
+
109
168
  /** 可执行图节点类型:与配置类型对齐,避免文档里残留旧 nodeType */
110
169
  export function resolveRunnableNodeType(
111
170
  node: Pick<
@@ -116,6 +175,8 @@ export function resolveRunnableNodeType(
116
175
  switch (resolveBlueprintConfigSource(node)) {
117
176
  case "view":
118
177
  return VIEW_NODE_TYPE;
178
+ case "event":
179
+ return EVENT_NODE_TYPE;
119
180
  case "lifecycle":
120
181
  return LIFECYCLE_NODE_TYPE;
121
182
  case "clock":
@@ -146,6 +207,7 @@ export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
146
207
  and: "blueprint.node.typeAnd",
147
208
  lifecycle: "blueprint.node.typeLifecycle",
148
209
  view: "blueprint.node.typeView",
210
+ event: "blueprint.node.typeEvent",
149
211
  fetch: "blueprint.node.typeFetch",
150
212
  json: "blueprint.node.typeJson",
151
213
  clock: "blueprint.node.typeClock",
@@ -164,6 +226,14 @@ export const BLUEPRINT_LIFECYCLE_PHASE_KEYS: Record<PageLifecyclePhase, string>
164
226
  blueprintActivated: "blueprint.node.phaseBlueprintActivated",
165
227
  };
166
228
 
229
+ export const BLUEPRINT_VIEW_EVENT_TYPE_KEYS: Record<ViewEventType, string> = {
230
+ click: "blueprint.node.eventClick",
231
+ dblclick: "blueprint.node.eventDblclick",
232
+ contextmenu: "blueprint.node.eventContextmenu",
233
+ mouseenter: "blueprint.node.eventMouseenter",
234
+ mouseleave: "blueprint.node.eventMouseleave",
235
+ };
236
+
167
237
  function defaultTranslate(): TranslateFn {
168
238
  return tForLocale(resolveLocale());
169
239
  }
@@ -183,6 +253,11 @@ export function getLifecyclePhaseLabel(
183
253
  return key ? t(key) : phase;
184
254
  }
185
255
 
256
+ export function getViewEventTypeLabel(t: TranslateFn, eventType: ViewEventType): string {
257
+ const key = BLUEPRINT_VIEW_EVENT_TYPE_KEYS[eventType];
258
+ return key ? t(key) : eventType;
259
+ }
260
+
186
261
  export function resolveBlueprintNodeTypeLabel(
187
262
  node: Pick<
188
263
  BlueprintGraphNode,
@@ -243,6 +318,15 @@ export function resolveNodeClockConfig(
243
318
  });
244
319
  }
245
320
 
321
+ export function resolveNodeEventConfig(
322
+ node: Pick<BlueprintGraphNode, "eventConfig">
323
+ ): ViewEventNodeConfig {
324
+ return normalizeViewEventConfig({
325
+ ...DEFAULT_VIEW_EVENT_NODE_CONFIG,
326
+ ...node.eventConfig,
327
+ });
328
+ }
329
+
246
330
  export type BlueprintGraphEdge = {
247
331
  id: string;
248
332
  source: string;
@@ -275,12 +359,18 @@ const noInputTargetNodeIds = (document: BlueprintDocument) =>
275
359
  .map((node) => node.id)
276
360
  );
277
361
 
278
- /** 生命周期节点无输入口:移除连入边,并修正节点配置 */
362
+ function isLifecycleSourceNode(node: BlueprintGraphNode | undefined): boolean {
363
+ return Boolean(node) && resolveRunnableNodeType(node!) === LIFECYCLE_NODE_TYPE;
364
+ }
365
+
366
+ function isEventTargetNode(node: BlueprintGraphNode | undefined): boolean {
367
+ return Boolean(node) && resolveBlueprintConfigSource(node!) === "event";
368
+ }
369
+
370
+ /** 生命周期节点无输入口;事件节点输入必须来自生命周期节点 */
279
371
  export function sanitizeBlueprintDocument(
280
372
  document: BlueprintDocument
281
373
  ): BlueprintDocument {
282
- const blockedTargets = noInputTargetNodeIds(document);
283
-
284
374
  const nodes = document.nodes.map((node) => {
285
375
  let next = node;
286
376
  if (node.viewElementId && !node.viewElementIds?.length) {
@@ -297,6 +387,9 @@ export function sanitizeBlueprintDocument(
297
387
  if (next.role === "lifecycle") {
298
388
  return { ...next, configSource: "lifecycle" as const };
299
389
  }
390
+ if (next.role === "event") {
391
+ return { ...next, configSource: "event" as const };
392
+ }
300
393
  if (next.role === "clock") {
301
394
  return { ...next, configSource: "clock" as const };
302
395
  }
@@ -309,13 +402,16 @@ export function sanitizeBlueprintDocument(
309
402
  if (next.role === "and") {
310
403
  return { ...next, configSource: "and" as const };
311
404
  }
312
- if (next.configSource === "lifecycle" || next.configSource === "clock") {
405
+ if (next.configSource === "lifecycle" || next.configSource === "clock" || next.configSource === "event") {
313
406
  return { ...next, configSource: undefined };
314
407
  }
315
408
  return next;
316
409
  });
317
410
 
318
- const edges = document.edges.filter((edge) => !blockedTargets.has(edge.target));
411
+ const edges = filterInvalidBlueprintEdges(
412
+ { ...document, nodes },
413
+ document.edges
414
+ );
319
415
 
320
416
  return { ...document, nodes, edges };
321
417
  }
@@ -325,7 +421,15 @@ export function filterInvalidBlueprintEdges(
325
421
  edges: BlueprintGraphEdge[]
326
422
  ): BlueprintGraphEdge[] {
327
423
  const blockedTargets = noInputTargetNodeIds(document);
328
- return edges.filter((edge) => !blockedTargets.has(edge.target));
424
+ const byId = new Map(document.nodes.map((node) => [node.id, node]));
425
+ return edges.filter((edge) => {
426
+ if (blockedTargets.has(edge.target)) return false;
427
+ const target = byId.get(edge.target);
428
+ if (isEventTargetNode(target)) {
429
+ return isLifecycleSourceNode(byId.get(edge.source));
430
+ }
431
+ return true;
432
+ });
329
433
  }
330
434
 
331
435
  /** 切换配置类型时同步节点 role / nodeType,确保生命周期节点渲染为无输入口 */
@@ -347,6 +451,7 @@ export function patchNodeConfigSource(
347
451
  jsonConfig: undefined,
348
452
  logicConfig: undefined,
349
453
  clockConfig: undefined,
454
+ eventConfig: undefined,
350
455
  };
351
456
  }
352
457
 
@@ -363,6 +468,7 @@ export function patchNodeConfigSource(
363
468
  fetchConfig: undefined,
364
469
  jsonConfig: undefined,
365
470
  logicConfig: undefined,
471
+ eventConfig: undefined,
366
472
  };
367
473
  }
368
474
 
@@ -379,6 +485,7 @@ export function patchNodeConfigSource(
379
485
  jsonConfig: undefined,
380
486
  logicConfig: undefined,
381
487
  clockConfig: undefined,
488
+ eventConfig: undefined,
382
489
  };
383
490
  }
384
491
 
@@ -395,6 +502,7 @@ export function patchNodeConfigSource(
395
502
  jsonConfig: resolveNodeJsonConfig(node),
396
503
  logicConfig: undefined,
397
504
  clockConfig: undefined,
505
+ eventConfig: undefined,
398
506
  };
399
507
  }
400
508
 
@@ -411,6 +519,7 @@ export function patchNodeConfigSource(
411
519
  jsonConfig: undefined,
412
520
  logicConfig: undefined,
413
521
  clockConfig: undefined,
522
+ eventConfig: undefined,
414
523
  };
415
524
  }
416
525
 
@@ -427,6 +536,7 @@ export function patchNodeConfigSource(
427
536
  jsonConfig: undefined,
428
537
  logicConfig: resolveNodeLogicConfig(node),
429
538
  clockConfig: undefined,
539
+ eventConfig: undefined,
430
540
  };
431
541
  }
432
542
 
@@ -443,6 +553,22 @@ export function patchNodeConfigSource(
443
553
  jsonConfig: undefined,
444
554
  logicConfig: undefined,
445
555
  clockConfig: undefined,
556
+ eventConfig: undefined,
557
+ };
558
+ }
559
+
560
+ if (configSource === "event") {
561
+ return {
562
+ role: "event",
563
+ nodeType: EVENT_NODE_TYPE,
564
+ configSource: "event",
565
+ eventConfig: resolveNodeEventConfig(node),
566
+ lifecyclePhase: undefined,
567
+ libraryBlueprintId: undefined,
568
+ fetchConfig: undefined,
569
+ jsonConfig: undefined,
570
+ logicConfig: undefined,
571
+ clockConfig: undefined,
446
572
  };
447
573
  }
448
574
 
@@ -456,5 +582,6 @@ export function patchNodeConfigSource(
456
582
  jsonConfig: undefined,
457
583
  logicConfig: undefined,
458
584
  clockConfig: undefined,
585
+ eventConfig: undefined,
459
586
  };
460
587
  }
@@ -10,6 +10,8 @@ import {
10
10
  resolveNodeJsonConfig,
11
11
  resolveNodeLogicConfig,
12
12
  resolveViewElementIds,
13
+ resolveNodeEventConfig,
14
+ getViewEventTypeLabel,
13
15
  } from "./document";
14
16
  import type { BlueprintFlowNodeData } from "./vue-flow-adapter";
15
17
 
@@ -94,6 +96,24 @@ export function resolveBlueprintNodeSummary(
94
96
  }
95
97
  return t("blueprint.node.summaryViewMany", { count: ids.length });
96
98
  }
99
+ case "event": {
100
+ const ids = resolveViewElementIds(data);
101
+ const types = resolveNodeEventConfig(data).eventTypes;
102
+ const typeLabel = types.map((type) => getViewEventTypeLabel(t, type)).join(" / ");
103
+ if (ids.length === 0) {
104
+ return truncate(
105
+ t("blueprint.node.summaryEventNoView", { events: typeLabel }),
106
+ 44
107
+ );
108
+ }
109
+ return truncate(
110
+ t("blueprint.node.summaryEvent", {
111
+ events: typeLabel,
112
+ count: ids.length,
113
+ }),
114
+ 44
115
+ );
116
+ }
97
117
  case "and":
98
118
  return t("blueprint.node.summaryAnd");
99
119
  default:
@@ -6,4 +6,5 @@ export {
6
6
  JSON_NODE_TYPE,
7
7
  CLOCK_NODE_TYPE,
8
8
  AND_NODE_TYPE,
9
+ EVENT_NODE_TYPE,
9
10
  } from "@arronqzy/blueprint-dsl";