@bian-womp/spark-workbench 0.2.90 → 0.2.92

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,8 +1,7 @@
1
1
  import React from "react";
2
- export declare function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInvalidate, }: {
2
+ export declare function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate, }: {
3
3
  id: string;
4
4
  typeId?: string;
5
- title?: string;
6
5
  validation: {
7
6
  inputs?: any[];
8
7
  outputs?: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultNodeHeader.d.ts","sourceRoot":"","sources":["../../../../src/misc/DefaultNodeHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,MAAM,EACN,KAAK,EACL,UAAU,EACV,KAAK,EACL,MAAM,EACN,YAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE;QAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC;IAChE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,2CAgJA"}
1
+ {"version":3,"file":"DefaultNodeHeader.d.ts","sourceRoot":"","sources":["../../../../src/misc/DefaultNodeHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,MAAM,EACN,UAAU,EACV,KAAK,EACL,MAAM,EACN,YAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC;IAChE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,2CAgJA"}
@@ -2,16 +2,52 @@
2
2
  * Flow thumbnail capture utility
3
3
  * Captures React Flow canvas as SVG image
4
4
  */
5
+ export interface ThumbnailOptions {
6
+ /**
7
+ * If true, export the whole graph instead of just the viewport. Default: true
8
+ */
9
+ ignoreViewport?: boolean;
10
+ /**
11
+ * Padding to add when exporting whole graph (in pixels). Default: 40
12
+ */
13
+ padding?: number;
14
+ /**
15
+ * Whether to export handles. Default: true
16
+ */
17
+ exportHandles?: boolean;
18
+ /**
19
+ * Whether to export node titles. Default: true
20
+ */
21
+ exportNodeTitle?: boolean;
22
+ /**
23
+ * Whether to export background dot pattern. Default: true
24
+ */
25
+ exportBackgroundPattern?: boolean;
26
+ /**
27
+ * Background color of nodes. Default: "#f3f4f6"
28
+ */
29
+ nodeBackgroundColor?: string;
30
+ /**
31
+ * Color of background dot pattern. Default: "#f1f1f1"
32
+ */
33
+ backgroundPatternColor?: string;
34
+ /**
35
+ * Background color of the SVG. Default: "#ffffff"
36
+ */
37
+ backgroundColor?: string;
38
+ }
5
39
  /**
6
40
  * Captures a React Flow container element as an SVG image and returns data URL
7
41
  * @param containerElement - The React Flow container DOM element
42
+ * @param options - Options for thumbnail capture
8
43
  * @returns Promise resolving to SVG data URL string, or null if capture fails
9
44
  */
10
- export declare function captureCanvasThumbnail(containerElement: HTMLElement | null): Promise<string | null>;
45
+ export declare function captureCanvasThumbnail(containerElement: HTMLElement | null, options?: ThumbnailOptions): Promise<string | null>;
11
46
  /**
12
47
  * Captures a React Flow container element as an SVG image and downloads it
13
48
  * @param containerElement - The React Flow container DOM element
49
+ * @param options - Options for thumbnail capture
14
50
  * @returns Promise resolving to true if successful, false otherwise
15
51
  */
16
- export declare function downloadCanvasThumbnail(containerElement: HTMLElement | null): Promise<boolean>;
52
+ export declare function downloadCanvasThumbnail(containerElement: HTMLElement | null, options?: ThumbnailOptions): Promise<boolean>;
17
53
  //# sourceMappingURL=thumbnail-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thumbnail-utils.d.ts","sourceRoot":"","sources":["../../../../src/misc/thumbnail-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+tBH;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,gBAAgB,EAAE,WAAW,GAAG,IAAI,GACnC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6FxB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,gBAAgB,EAAE,WAAW,GAAG,IAAI,GACnC,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
1
+ {"version":3,"file":"thumbnail-utils.d.ts","sourceRoot":"","sources":["../../../../src/misc/thumbnail-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2DH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAgsBD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,gBAAgB,EAAE,WAAW,GAAG,IAAI,EACpC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0IxB;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,gBAAgB,EAAE,WAAW,GAAG,IAAI,EACpC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
package/lib/esm/index.js CHANGED
@@ -3149,11 +3149,12 @@ function extractEdgePaths(viewport, visibleBounds) {
3149
3149
  if (!pathData)
3150
3150
  return;
3151
3151
  const pathBounds = getPathBounds(pathData);
3152
- // Only include edge if it intersects with visible viewport
3153
- if (pathBounds.maxX >= visibleBounds.minX &&
3154
- pathBounds.minX <= visibleBounds.maxX &&
3155
- pathBounds.maxY >= visibleBounds.minY &&
3156
- pathBounds.minY <= visibleBounds.maxY) {
3152
+ // Only include edge if it intersects with visible viewport (or if ignoring viewport)
3153
+ if (!visibleBounds ||
3154
+ (pathBounds.maxX >= visibleBounds.minX &&
3155
+ pathBounds.minX <= visibleBounds.maxX &&
3156
+ pathBounds.maxY >= visibleBounds.minY &&
3157
+ pathBounds.minY <= visibleBounds.maxY)) {
3157
3158
  edges.push({
3158
3159
  d: pathData,
3159
3160
  stroke: extractStrokeColor(pathEl),
@@ -3199,10 +3200,10 @@ function extractNodeDimensions(nodeEl) {
3199
3200
  /**
3200
3201
  * Extracts node styles (colors, border, radius) from computed styles
3201
3202
  */
3202
- function extractNodeStyles(nodeContent) {
3203
+ function extractNodeStyles(nodeContent, nodeBackgroundColor) {
3203
3204
  const computedStyle = window.getComputedStyle(nodeContent);
3204
- // Use gray background for nodes in thumbnail
3205
- const fill = "#f3f4f6"; // gray-100 equivalent
3205
+ // Use provided background color or default gray background for nodes in thumbnail
3206
+ const fill = nodeBackgroundColor || "#f3f4f6"; // gray-100 equivalent
3206
3207
  const stroke = extractStrokeColor(nodeContent);
3207
3208
  const strokeWidth = extractStrokeWidth(nodeContent, 1);
3208
3209
  const borderRadiusStr = computedStyle.borderRadius || "8px";
@@ -3309,7 +3310,8 @@ function extractNodeTitle(nodeEl, nodeX, nodeY) {
3309
3310
  /**
3310
3311
  * Extracts visible nodes from React Flow viewport
3311
3312
  */
3312
- function extractNodes(viewport, visibleBounds) {
3313
+ function extractNodes(viewport, visibleBounds, options = {}) {
3314
+ const { exportHandles = true, exportNodeTitle = true, nodeBackgroundColor, } = options;
3313
3315
  const nodes = [];
3314
3316
  let minX = Infinity;
3315
3317
  let minY = Infinity;
@@ -3323,11 +3325,16 @@ function extractNodes(viewport, visibleBounds) {
3323
3325
  const nodeContent = nodeEl.firstElementChild;
3324
3326
  if (!nodeContent)
3325
3327
  return;
3326
- const styles = extractNodeStyles(nodeContent);
3327
- const handles = extractNodeHandles(nodeEl, position.x, position.y, dimensions.width);
3328
- const title = extractNodeTitle(nodeEl, position.x, position.y);
3329
- // Only include node if it's within visible viewport
3330
- if (isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
3328
+ const styles = extractNodeStyles(nodeContent, nodeBackgroundColor);
3329
+ const handles = exportHandles
3330
+ ? extractNodeHandles(nodeEl, position.x, position.y, dimensions.width)
3331
+ : [];
3332
+ const title = exportNodeTitle
3333
+ ? extractNodeTitle(nodeEl, position.x, position.y)
3334
+ : undefined;
3335
+ // Only include node if it's within visible viewport (or if ignoring viewport)
3336
+ if (!visibleBounds ||
3337
+ isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
3331
3338
  nodes.push({
3332
3339
  x: position.x,
3333
3340
  y: position.y,
@@ -3359,43 +3366,47 @@ function extractNodes(viewport, visibleBounds) {
3359
3366
  /**
3360
3367
  * Creates SVG element with dot pattern background (matching React Flow)
3361
3368
  */
3362
- function createSVGElement(width, height) {
3369
+ function createSVGElement(width, height, options = {}) {
3370
+ const { exportBackgroundPattern = true, backgroundPatternColor = "#f1f1f1", backgroundColor = "#ffffff", } = options;
3363
3371
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3364
3372
  svg.setAttribute("width", String(width));
3365
3373
  svg.setAttribute("height", String(height));
3366
3374
  svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
3367
- // Create defs section for patterns
3368
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3369
- // Create dot pattern (matching React Flow's BackgroundVariant.Dots)
3370
- // React Flow uses gap={12} and size={1} by default
3371
- const pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
3372
- pattern.setAttribute("id", "dot-pattern");
3373
- pattern.setAttribute("x", "0");
3374
- pattern.setAttribute("y", "0");
3375
- pattern.setAttribute("width", "24"); // gap between dots (matching React Flow default)
3376
- pattern.setAttribute("height", "24");
3377
- pattern.setAttribute("patternUnits", "userSpaceOnUse");
3378
- // Create a circle for the dot (centered in the pattern cell)
3379
- const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
3380
- circle.setAttribute("cx", "12"); // Center of 24x24 pattern cell
3381
- circle.setAttribute("cy", "12");
3382
- circle.setAttribute("r", "1"); // dot radius = 1 (matching React Flow size={1})
3383
- circle.setAttribute("fill", "#f1f1f1"); // gray color matching React Flow default
3384
- pattern.appendChild(circle);
3385
- defs.appendChild(pattern);
3386
- svg.appendChild(defs);
3387
- // Create background rectangle with white base
3375
+ // Create background rectangle with configurable background color
3388
3376
  const bgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
3389
3377
  bgRect.setAttribute("width", String(width));
3390
3378
  bgRect.setAttribute("height", String(height));
3391
- bgRect.setAttribute("fill", "#ffffff"); // Base background color
3379
+ bgRect.setAttribute("fill", backgroundColor);
3392
3380
  svg.appendChild(bgRect);
3393
- // Create pattern overlay rectangle
3394
- const patternRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
3395
- patternRect.setAttribute("width", String(width));
3396
- patternRect.setAttribute("height", String(height));
3397
- patternRect.setAttribute("fill", "url(#dot-pattern)");
3398
- svg.appendChild(patternRect);
3381
+ // Create dot pattern if enabled
3382
+ if (exportBackgroundPattern) {
3383
+ // Create defs section for patterns
3384
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3385
+ // Create dot pattern (matching React Flow's BackgroundVariant.Dots)
3386
+ // React Flow uses gap={12} and size={1} by default
3387
+ const pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
3388
+ pattern.setAttribute("id", "dot-pattern");
3389
+ pattern.setAttribute("x", "0");
3390
+ pattern.setAttribute("y", "0");
3391
+ pattern.setAttribute("width", "24"); // gap between dots (matching React Flow default)
3392
+ pattern.setAttribute("height", "24");
3393
+ pattern.setAttribute("patternUnits", "userSpaceOnUse");
3394
+ // Create a circle for the dot (centered in the pattern cell)
3395
+ const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
3396
+ circle.setAttribute("cx", "12"); // Center of 24x24 pattern cell
3397
+ circle.setAttribute("cy", "12");
3398
+ circle.setAttribute("r", "1"); // dot radius = 1 (matching React Flow size={1})
3399
+ circle.setAttribute("fill", backgroundPatternColor);
3400
+ pattern.appendChild(circle);
3401
+ defs.appendChild(pattern);
3402
+ svg.appendChild(defs);
3403
+ // Create pattern overlay rectangle
3404
+ const patternRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
3405
+ patternRect.setAttribute("width", String(width));
3406
+ patternRect.setAttribute("height", String(height));
3407
+ patternRect.setAttribute("fill", "url(#dot-pattern)");
3408
+ svg.appendChild(patternRect);
3409
+ }
3399
3410
  // Create group with transform
3400
3411
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
3401
3412
  svg.appendChild(group);
@@ -3462,13 +3473,16 @@ function renderEdgePath(group, edge) {
3462
3473
  /**
3463
3474
  * Renders all nodes, handles, titles, and edges to SVG group
3464
3475
  */
3465
- function renderContentToSVG(group, nodes, edges, transformX, transformY) {
3476
+ function renderContentToSVG(group, nodes, edges, transformX, transformY, options = {}) {
3477
+ const { exportHandles = true, exportNodeTitle = true } = options;
3466
3478
  group.setAttribute("transform", `translate(${transformX}, ${transformY})`);
3467
3479
  // Render nodes
3468
3480
  nodes.forEach((node) => {
3469
3481
  renderNodeRect(group, node);
3470
- node.handles.forEach((handle) => renderHandle(group, handle));
3471
- if (node.title) {
3482
+ if (exportHandles) {
3483
+ node.handles.forEach((handle) => renderHandle(group, handle));
3484
+ }
3485
+ if (exportNodeTitle && node.title) {
3472
3486
  renderNodeTitle(group, node.title);
3473
3487
  }
3474
3488
  });
@@ -3481,9 +3495,11 @@ function renderContentToSVG(group, nodes, edges, transformX, transformY) {
3481
3495
  /**
3482
3496
  * Captures a React Flow container element as an SVG image and returns data URL
3483
3497
  * @param containerElement - The React Flow container DOM element
3498
+ * @param options - Options for thumbnail capture
3484
3499
  * @returns Promise resolving to SVG data URL string, or null if capture fails
3485
3500
  */
3486
- async function captureCanvasThumbnail(containerElement) {
3501
+ async function captureCanvasThumbnail(containerElement, options = {}) {
3502
+ const { ignoreViewport = true, padding = 40, exportHandles = true, exportNodeTitle = true, exportBackgroundPattern = true, nodeBackgroundColor, backgroundPatternColor, backgroundColor, } = options;
3487
3503
  if (!containerElement) {
3488
3504
  console.warn("[flowThumbnail] Container element is null");
3489
3505
  return null;
@@ -3499,12 +3515,18 @@ async function captureCanvasThumbnail(containerElement) {
3499
3515
  const viewportStyle = window.getComputedStyle(reactFlowViewport);
3500
3516
  const viewportTransform = viewportStyle.transform || viewportStyle.getPropertyValue("transform");
3501
3517
  const transform = parseViewportTransform(viewportTransform);
3502
- // Calculate visible bounds
3518
+ // Calculate visible bounds (null if ignoring viewport)
3503
3519
  const viewportRect = reactFlowViewport.getBoundingClientRect();
3504
- const visibleBounds = calculateVisibleBounds(viewportRect, transform);
3505
- // Extract edges and nodes
3520
+ const visibleBounds = ignoreViewport
3521
+ ? null
3522
+ : calculateVisibleBounds(viewportRect, transform);
3523
+ // Extract edges and nodes (pass null bounds if ignoring viewport to get all)
3506
3524
  const { edges, bounds: edgeBounds } = extractEdgePaths(reactFlowViewport, visibleBounds);
3507
- const { nodes, bounds: nodeBounds } = extractNodes(reactFlowViewport, visibleBounds);
3525
+ const { nodes, bounds: nodeBounds } = extractNodes(reactFlowViewport, visibleBounds, {
3526
+ exportHandles,
3527
+ exportNodeTitle,
3528
+ nodeBackgroundColor,
3529
+ });
3508
3530
  // Calculate overall bounding box
3509
3531
  // Handle case where one or both bounds might be Infinity (no content)
3510
3532
  let minX = Infinity;
@@ -3523,24 +3545,50 @@ async function captureCanvasThumbnail(containerElement) {
3523
3545
  maxX = Math.max(maxX, nodeBounds.maxX);
3524
3546
  maxY = Math.max(maxY, nodeBounds.maxY);
3525
3547
  }
3526
- // If no visible content, use the visible viewport bounds
3548
+ // If no content found, return null
3527
3549
  if (minX === Infinity || (nodes.length === 0 && edges.length === 0)) {
3550
+ if (ignoreViewport) {
3551
+ console.warn("[flowThumbnail] No content found in graph");
3552
+ return null;
3553
+ }
3554
+ // Fallback to visible viewport bounds if not ignoring viewport
3528
3555
  minX = visibleBounds.minX;
3529
3556
  minY = visibleBounds.minY;
3530
3557
  maxX = visibleBounds.maxX;
3531
3558
  maxY = visibleBounds.maxY;
3532
3559
  }
3533
- // Use the visible viewport bounds exactly (what the user sees)
3534
- const contentMinX = visibleBounds.minX;
3535
- const contentMinY = visibleBounds.minY;
3536
- const contentMaxX = visibleBounds.maxX;
3537
- const contentMaxY = visibleBounds.maxY;
3560
+ // Calculate content bounds with padding if ignoring viewport
3561
+ let contentMinX;
3562
+ let contentMinY;
3563
+ let contentMaxX;
3564
+ let contentMaxY;
3565
+ if (ignoreViewport) {
3566
+ // Add padding when exporting whole graph
3567
+ contentMinX = minX - padding;
3568
+ contentMinY = minY - padding;
3569
+ contentMaxX = maxX + padding;
3570
+ contentMaxY = maxY + padding;
3571
+ }
3572
+ else {
3573
+ // Use the visible viewport bounds exactly (what the user sees)
3574
+ contentMinX = visibleBounds.minX;
3575
+ contentMinY = visibleBounds.minY;
3576
+ contentMaxX = visibleBounds.maxX;
3577
+ contentMaxY = visibleBounds.maxY;
3578
+ }
3538
3579
  const contentWidth = contentMaxX - contentMinX;
3539
3580
  const contentHeight = contentMaxY - contentMinY;
3540
- // Create SVG
3541
- const { svg, group } = createSVGElement(contentWidth, contentHeight);
3542
- // Render content
3543
- renderContentToSVG(group, nodes, edges, -contentMinX, -contentMinY);
3581
+ // Create SVG with options
3582
+ const { svg, group } = createSVGElement(contentWidth, contentHeight, {
3583
+ exportBackgroundPattern,
3584
+ backgroundPatternColor,
3585
+ backgroundColor,
3586
+ });
3587
+ // Render content with options
3588
+ renderContentToSVG(group, nodes, edges, -contentMinX, -contentMinY, {
3589
+ exportHandles,
3590
+ exportNodeTitle,
3591
+ });
3544
3592
  // Serialize SVG to string
3545
3593
  const serializer = new XMLSerializer();
3546
3594
  const svgString = serializer.serializeToString(svg);
@@ -3556,10 +3604,11 @@ async function captureCanvasThumbnail(containerElement) {
3556
3604
  /**
3557
3605
  * Captures a React Flow container element as an SVG image and downloads it
3558
3606
  * @param containerElement - The React Flow container DOM element
3607
+ * @param options - Options for thumbnail capture
3559
3608
  * @returns Promise resolving to true if successful, false otherwise
3560
3609
  */
3561
- async function downloadCanvasThumbnail(containerElement) {
3562
- const svgDataUrl = await captureCanvasThumbnail(containerElement);
3610
+ async function downloadCanvasThumbnail(containerElement, options = {}) {
3611
+ const svgDataUrl = await captureCanvasThumbnail(containerElement, options);
3563
3612
  if (!svgDataUrl) {
3564
3613
  return false;
3565
3614
  }
@@ -4518,24 +4567,24 @@ function IssueBadge({ level, title, size = 12, className, }) {
4518
4567
  return (jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? (jsx(XCircleIcon, { size: size, weight: "fill" })) : (jsx(WarningCircleIcon, { size: size, weight: "fill" })) }));
4519
4568
  }
4520
4569
 
4521
- function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInvalidate, }) {
4570
+ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate, }) {
4522
4571
  const ctx = useWorkbenchContext();
4523
4572
  const [isEditing, setIsEditing] = React.useState(false);
4524
4573
  const [editValue, setEditValue] = React.useState("");
4525
4574
  const inputRef = React.useRef(null);
4526
4575
  // Use getNodeDisplayName if typeId is provided, otherwise use title prop
4527
- const displayName = typeId ? ctx.getNodeDisplayName(id) : title ?? id;
4528
- const effectiveTypeId = typeId ?? title ?? id;
4576
+ const displayName = typeId ? ctx.getNodeDisplayName(id) : id;
4577
+ const effectiveTypeId = typeId ?? id;
4529
4578
  // Get the default display name (without custom name) for comparison
4530
4579
  const getDefaultDisplayName = React.useCallback(() => {
4531
4580
  if (!typeId)
4532
- return title ?? id;
4581
+ return id;
4533
4582
  const node = ctx.wb.def.nodes.find((n) => n.nodeId === id);
4534
4583
  if (!node)
4535
4584
  return id;
4536
4585
  const desc = ctx.registry.nodes.get(node.typeId);
4537
4586
  return desc?.displayName || node.typeId;
4538
- }, [ctx, id, typeId, title]);
4587
+ }, [ctx, id, typeId]);
4539
4588
  const handleInvalidate = React.useCallback(() => {
4540
4589
  try {
4541
4590
  if (onInvalidate)