@elicdavis/node-flow 0.1.16 → 0.1.17

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.
package/dist/index.mjs CHANGED
@@ -60,6 +60,14 @@ var Theme = {
60
60
  FontColor: "#afb9bb",
61
61
  DisabledFontColor: "#4a585c"
62
62
  },
63
+ Minimap: {
64
+ BackgroundColor: "rgba(7, 33, 42, 0.85)",
65
+ BorderColor: "#1c1c1c",
66
+ BorderRadius: 4,
67
+ Padding: 6,
68
+ NodeColor: "#6b7f85",
69
+ ViewportColor: "rgba(175, 185, 187, 0.25)"
70
+ },
63
71
  Note: {
64
72
  FontColor: "#afb9bb",
65
73
  FontSize: 16,
@@ -601,6 +609,43 @@ var ContextMenu = class _ContextMenu {
601
609
  }
602
610
  };
603
611
 
612
+ // src/render_scheduler.ts
613
+ var subscribers = /* @__PURE__ */ new Set();
614
+ var frameQueued = false;
615
+ var continuous = 0;
616
+ var canSchedule = () => typeof requestAnimationFrame === "function";
617
+ function onRenderRequested(redraw) {
618
+ subscribers.add(redraw);
619
+ return () => {
620
+ subscribers.delete(redraw);
621
+ };
622
+ }
623
+ function requestRender() {
624
+ if (frameQueued || !canSchedule()) {
625
+ return;
626
+ }
627
+ frameQueued = true;
628
+ requestAnimationFrame(() => {
629
+ frameQueued = false;
630
+ subscribers.forEach((redraw) => redraw());
631
+ if (continuous > 0) {
632
+ requestRender();
633
+ }
634
+ });
635
+ }
636
+ function renderContinuously() {
637
+ continuous++;
638
+ requestRender();
639
+ let released = false;
640
+ return () => {
641
+ if (released) {
642
+ return;
643
+ }
644
+ released = true;
645
+ continuous--;
646
+ };
647
+ }
648
+
604
649
  // src/input.ts
605
650
  var LONG_PRESS_MS = 500;
606
651
  var LONG_PRESS_SLOP = 12;
@@ -634,15 +679,19 @@ var MouseObserver = class {
634
679
  this.#lastTouch = Zero();
635
680
  this.#lastMousePosition = Zero();
636
681
  ele.style.touchAction = "none";
637
- ele.addEventListener("mousedown", this.#down.bind(this), false);
638
- ele.addEventListener("touchstart", this.#touchStart.bind(this), { passive: false });
639
- document.addEventListener("mouseup", this.#up.bind(this), false);
640
- document.addEventListener("touchend", this.#touchEnd.bind(this), false);
641
- document.addEventListener("touchcancel", this.#touchEnd.bind(this), false);
642
- ele.addEventListener("mousemove", this.#move.bind(this), false);
643
- ele.addEventListener("touchmove", this.#touchMove.bind(this), { passive: false });
644
- ele.addEventListener("wheel", this.#wheel.bind(this), { passive: false });
645
- ele.addEventListener("drop", (ev) => {
682
+ const redraws = (handler) => (event) => {
683
+ handler(event);
684
+ requestRender();
685
+ };
686
+ ele.addEventListener("mousedown", redraws(this.#down.bind(this)), false);
687
+ ele.addEventListener("touchstart", redraws(this.#touchStart.bind(this)), { passive: false });
688
+ document.addEventListener("mouseup", redraws(this.#up.bind(this)), false);
689
+ document.addEventListener("touchend", redraws(this.#touchEnd.bind(this)), false);
690
+ document.addEventListener("touchcancel", redraws(this.#touchEnd.bind(this)), false);
691
+ ele.addEventListener("mousemove", redraws(this.#move.bind(this)), false);
692
+ ele.addEventListener("touchmove", redraws(this.#touchMove.bind(this)), { passive: false });
693
+ ele.addEventListener("wheel", redraws(this.#wheel.bind(this)), { passive: false });
694
+ ele.addEventListener("drop", redraws((ev) => {
646
695
  ev.preventDefault();
647
696
  if (ev.dataTransfer?.items) {
648
697
  [...ev.dataTransfer.items].forEach((item, i) => {
@@ -654,18 +703,18 @@ var MouseObserver = class {
654
703
  }
655
704
  });
656
705
  }
657
- });
658
- ele.addEventListener("dragover", (ev) => {
706
+ }));
707
+ ele.addEventListener("dragover", redraws((ev) => {
659
708
  ev.preventDefault();
660
709
  this.#moveCallback(this.#mousePos(ev));
661
- });
662
- ele.addEventListener("contextmenu", (evt) => {
710
+ }));
711
+ ele.addEventListener("contextmenu", redraws((evt) => {
663
712
  evt.preventDefault();
664
713
  if (Date.now() - this.#lastTouchAt < SUPPRESS_MOUSE_MS) {
665
714
  return;
666
715
  }
667
716
  this.#contextMenu(this.#mousePos(evt));
668
- }, false);
717
+ }), false);
669
718
  }
670
719
  #mousePos(event) {
671
720
  const rect = this.#ele.getBoundingClientRect();
@@ -762,6 +811,7 @@ var MouseObserver = class {
762
811
  this.#clicked = false;
763
812
  this.#clickStop();
764
813
  this.#contextMenu({ x, y });
814
+ requestRender();
765
815
  }, LONG_PRESS_MS);
766
816
  this.#clicked = true;
767
817
  this.#moveCallback(pos);
@@ -1198,6 +1248,7 @@ var Text = class _Text {
1198
1248
  if (!document.fonts.check(`16px "${this.#style.getFont()}"`)) {
1199
1249
  document.fonts.addEventListener("loadingdone", () => {
1200
1250
  this.#measured = false;
1251
+ requestRender();
1201
1252
  });
1202
1253
  }
1203
1254
  }
@@ -1409,12 +1460,22 @@ function portsCompatible(a, b) {
1409
1460
  if (!a || !b) {
1410
1461
  return false;
1411
1462
  }
1412
- const left = a.acceptedTypes();
1413
- const right = b.acceptedTypes();
1414
- if (left.length === 0 || right.length === 0) {
1463
+ return typeSetsCompatible(a.acceptedTypes(), b.acceptedTypes());
1464
+ }
1465
+ function typeSetsCompatible(a, b) {
1466
+ if (a.length === 0 || b.length === 0) {
1415
1467
  return true;
1416
1468
  }
1417
- return left.some((type) => right.includes(type));
1469
+ return a.some((type) => b.includes(type));
1470
+ }
1471
+ function portConfigTypes(config) {
1472
+ if (config.anyType === true) {
1473
+ return [];
1474
+ }
1475
+ if (config.acceptedTypes !== void 0 && config.acceptedTypes.length > 0) {
1476
+ return config.acceptedTypes;
1477
+ }
1478
+ return [config.type === void 0 ? "" : config.type];
1418
1479
  }
1419
1480
  function fallbackColor(type, s) {
1420
1481
  let value = 0;
@@ -1529,6 +1590,7 @@ var Port = class {
1529
1590
  this.#filledStyle.fillColor = fallbackColor(this.#dataType, 1);
1530
1591
  }
1531
1592
  this.#dataTypePopupElement = this.#buildTooltip();
1593
+ requestRender();
1532
1594
  }
1533
1595
  setDataType(type) {
1534
1596
  if (this.#dataType === type) {
@@ -2607,6 +2669,7 @@ var ImageWidget = class {
2607
2669
  img.src = url;
2608
2670
  img.onload = () => {
2609
2671
  this.#image = img;
2672
+ requestRender();
2610
2673
  };
2611
2674
  img.onerror = (event) => {
2612
2675
  console.log("error loading image:", url, event);
@@ -3187,6 +3250,13 @@ function TimeExecution(name, callback) {
3187
3250
  }
3188
3251
 
3189
3252
  // src/nodes/publisher.ts
3253
+ function offersMatchingPort(config, filter) {
3254
+ const ports = filter.needs === "INPUT" /* Input */ ? config.inputs : config.outputs;
3255
+ if (ports === void 0) {
3256
+ return false;
3257
+ }
3258
+ return ports.some((port) => typeSetsCompatible(portConfigTypes(port), filter.types));
3259
+ }
3190
3260
  var Publisher = class {
3191
3261
  #name;
3192
3262
  #description;
@@ -3212,19 +3282,23 @@ var Publisher = class {
3212
3282
  unregister(nodeType) {
3213
3283
  return this.#registeredNodes.delete(nodeType);
3214
3284
  }
3215
- #recurseBuildMenu(graph, name, subMenu, position) {
3285
+ #recurseBuildMenu(graph, name, subMenu, position, filter) {
3216
3286
  const items = [];
3217
3287
  const subMenus = /* @__PURE__ */ new Map();
3218
3288
  for (let [key, nodeConfig] of subMenu) {
3219
3289
  const slashIndex = key.indexOf("/");
3220
3290
  const bracketIndex = key.indexOf("[");
3221
3291
  if (slashIndex === -1 || bracketIndex !== -1 && bracketIndex < slashIndex) {
3292
+ if (filter !== void 0 && !offersMatchingPort(nodeConfig, filter)) {
3293
+ continue;
3294
+ }
3222
3295
  items.push({
3223
3296
  name: key,
3224
3297
  callback: () => {
3225
3298
  const node = new FlowNode(nodeConfig);
3226
3299
  node.setPosition(position);
3227
3300
  graph.addNode(node);
3301
+ filter?.onCreated?.(node);
3228
3302
  }
3229
3303
  });
3230
3304
  } else {
@@ -3239,7 +3313,11 @@ var Publisher = class {
3239
3313
  }
3240
3314
  const menus = [];
3241
3315
  for (let [key, nodes] of subMenus) {
3242
- menus.push(this.#recurseBuildMenu(graph, key, nodes, position));
3316
+ const built = this.#recurseBuildMenu(graph, key, nodes, position, filter);
3317
+ if (filter !== void 0 && built.items?.length === 0 && built.subMenus?.length === 0) {
3318
+ continue;
3319
+ }
3320
+ menus.push(built);
3243
3321
  }
3244
3322
  return {
3245
3323
  name,
@@ -3247,8 +3325,8 @@ var Publisher = class {
3247
3325
  subMenus: menus
3248
3326
  };
3249
3327
  }
3250
- contextMenu(graph, position) {
3251
- return this.#recurseBuildMenu(graph, this.#name, this.#registeredNodes, position);
3328
+ contextMenu(graph, position, filter) {
3329
+ return this.#recurseBuildMenu(graph, this.#name, this.#registeredNodes, position, filter);
3252
3330
  }
3253
3331
  create(nodeType) {
3254
3332
  const config = this.#registeredNodes.get(nodeType);
@@ -3303,18 +3381,22 @@ var NodeFactory = class {
3303
3381
  }
3304
3382
  return node;
3305
3383
  }
3306
- newNodeSubmenus(graph, position) {
3384
+ newNodeSubmenus(graph, position, filter) {
3307
3385
  const menus = [];
3308
3386
  for (let [_, publisher] of this.#registeredPublishers) {
3309
- menus.push(publisher.contextMenu(graph, position));
3387
+ const built = publisher.contextMenu(graph, position, filter);
3388
+ if (filter !== void 0 && built.items?.length === 0 && built.subMenus?.length === 0) {
3389
+ continue;
3390
+ }
3391
+ menus.push(built);
3310
3392
  }
3311
3393
  return menus;
3312
3394
  }
3313
- openMenu(graph, position) {
3395
+ openMenu(graph, position, filter) {
3314
3396
  return {
3315
3397
  name: "New Node",
3316
3398
  group: nodeFlowGroup,
3317
- subMenus: this.newNodeSubmenus(graph, position)
3399
+ subMenus: this.newNodeSubmenus(graph, position, filter)
3318
3400
  };
3319
3401
  }
3320
3402
  };
@@ -3453,6 +3535,9 @@ var NodeSubsystem2 = class {
3453
3535
  CopyVector2(this.#boxSelectEnd_graphSpace, this.#boxSelectStart_graphSpace);
3454
3536
  this.#boxSelect = true;
3455
3537
  }
3538
+ if (!hoveringSomething && !ctrlKey) {
3539
+ this.unselectAllNodes();
3540
+ }
3456
3541
  return hoveringSomething || ctrlKey;
3457
3542
  }
3458
3543
  if (this.#portHovering.InputPort) {
@@ -3512,6 +3597,102 @@ var NodeSubsystem2 = class {
3512
3597
  }
3513
3598
  }
3514
3599
  }
3600
+ connectionsWithin(nodes) {
3601
+ const within = [];
3602
+ for (const connection of this.#connections) {
3603
+ const outNode = connection.outNode();
3604
+ const inNode = connection.inNode();
3605
+ if (outNode === null || inNode === null) {
3606
+ continue;
3607
+ }
3608
+ const from = nodes.indexOf(outNode);
3609
+ const to = nodes.indexOf(inNode);
3610
+ if (from === -1 || to === -1) {
3611
+ continue;
3612
+ }
3613
+ const outPort = connection.outPort();
3614
+ const inPort = connection.inPort();
3615
+ if (outPort === null || inPort === null) {
3616
+ continue;
3617
+ }
3618
+ within.push({
3619
+ fromNode: from,
3620
+ fromPort: this.#outputPortIndex(outNode, outPort),
3621
+ toNode: to,
3622
+ toPort: this.#inputPortIndex(inNode, inPort)
3623
+ });
3624
+ }
3625
+ return within;
3626
+ }
3627
+ #onConnectionReleased = null;
3628
+ setConnectionReleasedHandler(handler) {
3629
+ this.#onConnectionReleased = handler;
3630
+ }
3631
+ #describeDanglingConnection(conn) {
3632
+ const outNode = conn.outNode();
3633
+ const inNode = conn.inNode();
3634
+ if (outNode !== null && inNode === null) {
3635
+ const from = conn.outPort();
3636
+ if (from === null) {
3637
+ return null;
3638
+ }
3639
+ const fromIndex = this.#outputPortIndex(outNode, from);
3640
+ return {
3641
+ needs: "INPUT" /* Input */,
3642
+ types: from.acceptedTypes(),
3643
+ connectTo: (node) => {
3644
+ const to = this.#firstCompatiblePort(node, "INPUT" /* Input */, from);
3645
+ if (to !== -1 && fromIndex !== -1) {
3646
+ this.connectNodes(outNode, fromIndex, node, to);
3647
+ }
3648
+ }
3649
+ };
3650
+ }
3651
+ if (inNode !== null && outNode === null) {
3652
+ const to = conn.inPort();
3653
+ if (to === null) {
3654
+ return null;
3655
+ }
3656
+ const toIndex = this.#inputPortIndex(inNode, to);
3657
+ return {
3658
+ needs: "OUTPUT" /* Output */,
3659
+ types: to.acceptedTypes(),
3660
+ connectTo: (node) => {
3661
+ const from = this.#firstCompatiblePort(node, "OUTPUT" /* Output */, to);
3662
+ if (from !== -1 && toIndex !== -1) {
3663
+ this.connectNodes(node, from, inNode, toIndex);
3664
+ }
3665
+ }
3666
+ };
3667
+ }
3668
+ return null;
3669
+ }
3670
+ #outputPortIndex(node, port) {
3671
+ for (let i = 0; i < node.outputs(); i++) {
3672
+ if (node.outputPort(i) === port) {
3673
+ return i;
3674
+ }
3675
+ }
3676
+ return -1;
3677
+ }
3678
+ #inputPortIndex(node, port) {
3679
+ for (let i = 0; i < node.inputs(); i++) {
3680
+ if (node.inputPort(i) === port) {
3681
+ return i;
3682
+ }
3683
+ }
3684
+ return -1;
3685
+ }
3686
+ #firstCompatiblePort(node, side, against) {
3687
+ const count = side === "INPUT" /* Input */ ? node.inputs() : node.outputs();
3688
+ for (let i = 0; i < count; i++) {
3689
+ const port = side === "INPUT" /* Input */ ? node.inputPort(i) : node.outputPort(i);
3690
+ if (portsCompatible(port, against)) {
3691
+ return i;
3692
+ }
3693
+ }
3694
+ return -1;
3695
+ }
3515
3696
  clickEnd() {
3516
3697
  for (let i = 0; i < this.#nodesGrabbed.Count(); i++) {
3517
3698
  const node = this.#nodes[this.#nodesGrabbed.At(i)];
@@ -3533,7 +3714,11 @@ var NodeSubsystem2 = class {
3533
3714
  return;
3534
3715
  }
3535
3716
  if (this.#portHovering === null) {
3717
+ const dangling = this.#describeDanglingConnection(this.#connectionSelected);
3536
3718
  this.#clearCurrentlySelectedConnection();
3719
+ if (dangling !== null) {
3720
+ this.#onConnectionReleased?.(dangling);
3721
+ }
3537
3722
  return;
3538
3723
  }
3539
3724
  const port = this.#portHovering.Port;
@@ -3600,6 +3785,7 @@ var NodeSubsystem2 = class {
3600
3785
  this.#idleConnectionRenderer
3601
3786
  );
3602
3787
  this.#connections.push(connection);
3788
+ requestRender();
3603
3789
  return connection;
3604
3790
  }
3605
3791
  getNodes() {
@@ -3660,6 +3846,7 @@ var NodeSubsystem2 = class {
3660
3846
  }
3661
3847
  addNode(node) {
3662
3848
  this.#nodes.push(node);
3849
+ requestRender();
3663
3850
  for (let i = 0; i < this.#registeredNodeAddedCallbacks.length; i++) {
3664
3851
  const callback = this.#registeredNodeAddedCallbacks[i];
3665
3852
  if (!callback) {
@@ -3706,6 +3893,7 @@ var NodeSubsystem2 = class {
3706
3893
  this.#removeNodeConnections(nodeIndex);
3707
3894
  const node = this.#nodes[nodeIndex];
3708
3895
  this.#nodes.splice(nodeIndex, 1);
3896
+ requestRender();
3709
3897
  for (let i = 0; i < this.#nodeRemovedCallbacks.length; i++) {
3710
3898
  this.#nodeRemovedCallbacks[i](node);
3711
3899
  }
@@ -3715,6 +3903,7 @@ var NodeSubsystem2 = class {
3715
3903
  this.#connections[index].clearPorts();
3716
3904
  }
3717
3905
  this.#connections.splice(index, 1);
3906
+ requestRender();
3718
3907
  }
3719
3908
  #removeConnection(connection, clearPorts) {
3720
3909
  const index = this.#connections.indexOf(connection);
@@ -3760,7 +3949,8 @@ var NodeSubsystem2 = class {
3760
3949
  this.#nodeFactory.openMenu(this, position)
3761
3950
  ]
3762
3951
  };
3763
- if (this.#nodesSelected().length > 0) {
3952
+ const selectedCount = this.#nodesSelected().length;
3953
+ if (selectedCount > 0) {
3764
3954
  organizeNodesSubMenu.items?.push({
3765
3955
  name: "Selected Nodes",
3766
3956
  group: nodeFlowGroup,
@@ -3768,6 +3958,13 @@ var NodeSubsystem2 = class {
3768
3958
  this.#organizeSelected(ctx);
3769
3959
  }
3770
3960
  });
3961
+ config.items?.push({
3962
+ name: selectedCount === 1 ? "Delete Selected Node" : `Delete ${selectedCount} Selected Nodes`,
3963
+ group: nodeFlowGroup,
3964
+ callback: () => {
3965
+ this.removeSelectedNodes();
3966
+ }
3967
+ });
3771
3968
  }
3772
3969
  if (this.#nodeHovering > -1) {
3773
3970
  const nodeToReview = this.#nodeHovering;
@@ -3841,6 +4038,29 @@ var NodeSubsystem2 = class {
3841
4038
  this.#selectNode(inputs[i], false);
3842
4039
  }
3843
4040
  }
4041
+ /** Drops the selection entirely. */
4042
+ unselectAllNodes() {
4043
+ let changed = false;
4044
+ for (let i = 0; i < this.#nodes.length; i++) {
4045
+ if (this.#nodes[i].selected()) {
4046
+ this.#nodes[i].unselect();
4047
+ changed = true;
4048
+ }
4049
+ }
4050
+ if (changed) {
4051
+ requestRender();
4052
+ }
4053
+ }
4054
+ /**
4055
+ * Removes every selected node, and the connections that touched them.
4056
+ */
4057
+ removeSelectedNodes() {
4058
+ const removed = this.getSelectedNodes();
4059
+ for (const node of removed) {
4060
+ this.removeNode(node);
4061
+ }
4062
+ return removed;
4063
+ }
3844
4064
  #selectNodeByIndex(nodeIndex, unselectOthers) {
3845
4065
  this.#selectNode(this.#nodes[nodeIndex], unselectOthers);
3846
4066
  }
@@ -3878,8 +4098,16 @@ var NodeSubsystem2 = class {
3878
4098
  this.#nodeHovering = -1;
3879
4099
  this.#boxSelectionNodes.Clear();
3880
4100
  const selectedBox_Screenspace = this.#boxSelectionScreenspaceBox(camera);
4101
+ const canvas = {
4102
+ Position: { x: 0, y: 0 },
4103
+ Size: { x: ctx.canvas.width, y: ctx.canvas.height }
4104
+ };
3881
4105
  for (let i = 0; i < this.#nodes.length; i++) {
3882
4106
  let state = 0 /* Idle */;
4107
+ const nodeBounds = this.#nodes[i].calculateBounds(ctx, camera);
4108
+ if (!BoxIntersection(nodeBounds, canvas) && this.#nodes[i].layoutPortsWithoutDrawing(nodeBounds, camera.zoom)) {
4109
+ continue;
4110
+ }
3883
4111
  if (mousePosition !== void 0 && !this.#boxSelect) {
3884
4112
  const intersection = this.#nodes[i].inBounds(ctx, camera, mousePosition);
3885
4113
  if (intersection.Node !== void 0 && intersection.PortIndex === void 0 && intersection.Widget === void 0) {
@@ -3900,8 +4128,8 @@ var NodeSubsystem2 = class {
3900
4128
  };
3901
4129
  }
3902
4130
  } else if (this.#boxSelect) {
3903
- const nodeBounds = this.#nodes[i].calculateBounds(ctx, camera);
3904
- if (BoxIntersection(selectedBox_Screenspace, nodeBounds)) {
4131
+ const nodeBounds2 = this.#nodes[i].calculateBounds(ctx, camera);
4132
+ if (BoxIntersection(selectedBox_Screenspace, nodeBounds2)) {
3905
4133
  state = 1 /* MouseOver */;
3906
4134
  this.#boxSelectionNodes.Push(i);
3907
4135
  }
@@ -3923,12 +4151,16 @@ var NodeSubsystem2 = class {
3923
4151
  render(ctx, camera, mousePosition) {
3924
4152
  this.#lastCtx = ctx;
3925
4153
  this.#cursor = "default" /* Default */;
4154
+ const awaitingLayout = this.#nodes.some((node) => !node.hasBeenLaidOut());
3926
4155
  TimeExecution("Render_Connections", () => {
3927
4156
  this.#renderConnections(ctx, camera, mousePosition);
3928
4157
  });
3929
4158
  TimeExecution("Render_Nodes", () => {
3930
4159
  this.#renderNodes(ctx, camera, mousePosition);
3931
4160
  });
4161
+ if (awaitingLayout) {
4162
+ requestRender();
4163
+ }
3932
4164
  if (this.#boxSelect) {
3933
4165
  const box = this.#boxSelectionScreenspaceBox(camera);
3934
4166
  ctx.setLineDash([Theme.BoxSelect.LineDashLength]);
@@ -4204,6 +4436,7 @@ var FlowNode = class {
4204
4436
  return;
4205
4437
  }
4206
4438
  this.#selected = true;
4439
+ requestRender();
4207
4440
  for (let i = 0; i < this.#onSelect.length; i++) {
4208
4441
  this.#onSelect[i]();
4209
4442
  }
@@ -4249,6 +4482,7 @@ var FlowNode = class {
4249
4482
  return;
4250
4483
  }
4251
4484
  this.#data[name] = value;
4485
+ requestRender();
4252
4486
  for (let i = 0; i < this.#registeredAnyPropertyChangeCallbacks.length; i++) {
4253
4487
  this.#registeredAnyPropertyChangeCallbacks[i](name, oldValue, value);
4254
4488
  }
@@ -4509,6 +4743,7 @@ var FlowNode = class {
4509
4743
  return;
4510
4744
  }
4511
4745
  this.#selected = false;
4746
+ requestRender();
4512
4747
  for (let i = 0; i < this.#onUnselect.length; i++) {
4513
4748
  this.#onUnselect[i]();
4514
4749
  }
@@ -4538,6 +4773,7 @@ var FlowNode = class {
4538
4773
  }
4539
4774
  setPosition(position) {
4540
4775
  CopyVector2(this.#position, position);
4776
+ requestRender();
4541
4777
  }
4542
4778
  getPosition() {
4543
4779
  return this.#position;
@@ -4600,10 +4836,12 @@ var FlowNode = class {
4600
4836
  }
4601
4837
  addWidget(widget) {
4602
4838
  this.#widgets.push(widget);
4839
+ requestRender();
4603
4840
  }
4604
4841
  insertWidget(index, widget) {
4605
4842
  const at = Math.max(0, Math.min(index, this.#widgets.length));
4606
4843
  this.#widgets.splice(at, 0, widget);
4844
+ requestRender();
4607
4845
  }
4608
4846
  removeWidget(widget) {
4609
4847
  const index = this.#widgets.indexOf(widget, 0);
@@ -4611,12 +4849,15 @@ var FlowNode = class {
4611
4849
  throw new Error("node does not contain widget");
4612
4850
  }
4613
4851
  this.#widgets.splice(index, 1);
4852
+ requestRender();
4614
4853
  }
4615
4854
  removeWidgetAt(index) {
4616
4855
  if (index < 0 || index >= this.#widgets.length) {
4617
4856
  return void 0;
4618
4857
  }
4619
- return this.#widgets.splice(index, 1)[0];
4858
+ const removed = this.#widgets.splice(index, 1)[0];
4859
+ requestRender();
4860
+ return removed;
4620
4861
  }
4621
4862
  moveWidget(from, to) {
4622
4863
  if (from < 0 || from >= this.#widgets.length) {
@@ -4627,9 +4868,11 @@ var FlowNode = class {
4627
4868
  }
4628
4869
  const [widget] = this.#widgets.splice(from, 1);
4629
4870
  this.#widgets.splice(to, 0, widget);
4871
+ requestRender();
4630
4872
  }
4631
4873
  clearWidgets() {
4632
4874
  this.#widgets.length = 0;
4875
+ requestRender();
4633
4876
  }
4634
4877
  getWidget(index) {
4635
4878
  return this.#widgets[index];
@@ -4701,6 +4944,7 @@ var FlowNode = class {
4701
4944
  return;
4702
4945
  }
4703
4946
  this.#title.set(cleaned);
4947
+ requestRender();
4704
4948
  for (let i = 0; i < this.#titleChangeCallback.length; i++) {
4705
4949
  const callback = this.#titleChangeCallback[i];
4706
4950
  callback(this, old, cleaned);
@@ -4719,6 +4963,7 @@ var FlowNode = class {
4719
4963
  }
4720
4964
  const old = this.#infoText;
4721
4965
  this.#infoText = cleaned;
4966
+ requestRender();
4722
4967
  for (let i = 0; i < this.#infoChangeCallback.length; i++) {
4723
4968
  const callback = this.#infoChangeCallback[i];
4724
4969
  callback(this, old, cleaned);
@@ -4758,7 +5003,12 @@ var FlowNode = class {
4758
5003
  clearMessages() {
4759
5004
  this.#messages = [];
4760
5005
  }
5006
+ hasBeenLaidOut() {
5007
+ return this.#laidOut;
5008
+ }
5009
+ #laidOut = false;
4761
5010
  render(ctx, camera, state, mousePosition, postProcess) {
5011
+ this.#laidOut = true;
4762
5012
  VectorPool.run(() => {
4763
5013
  const tempMeasurement = VectorPool.get();
4764
5014
  this.#inputPortPositions.Clear();
@@ -4894,8 +5144,57 @@ var FlowNode = class {
4894
5144
  const message = this.#messages[i];
4895
5145
  messageStart.y += message.render(ctx, camera.zoom, messageStart, !(state === 0 /* Idle */)) + 10 * camera.zoom;
4896
5146
  }
5147
+ this.#rememberPortOffsets(nodeBounds, camera.zoom);
4897
5148
  });
4898
5149
  }
5150
+ #portOffsets = { input: [], output: [] };
5151
+ #rememberPortOffsets(bounds, zoom) {
5152
+ if (zoom <= 0) {
5153
+ return;
5154
+ }
5155
+ const record = (positions, into) => {
5156
+ into.length = 0;
5157
+ for (let i = 0; i < positions.Count(); i++) {
5158
+ const box = positions.At(i);
5159
+ into.push({
5160
+ Position: {
5161
+ x: (box.Position.x - bounds.Position.x) / zoom,
5162
+ y: (box.Position.y - bounds.Position.y) / zoom
5163
+ },
5164
+ Size: {
5165
+ x: box.Size.x / zoom,
5166
+ y: box.Size.y / zoom
5167
+ }
5168
+ });
5169
+ }
5170
+ };
5171
+ record(this.#inputPortPositions, this.#portOffsets.input);
5172
+ record(this.#outputPortPositions, this.#portOffsets.output);
5173
+ }
5174
+ layoutPortsWithoutDrawing(bounds, zoom) {
5175
+ if (this.#portOffsets.input.length === 0 && this.#portOffsets.output.length === 0) {
5176
+ return false;
5177
+ }
5178
+ const project = (offsets, into) => {
5179
+ into.Clear();
5180
+ for (let i = 0; i < offsets.length; i++) {
5181
+ const offset = offsets[i];
5182
+ into.Push({
5183
+ Position: {
5184
+ x: bounds.Position.x + offset.Position.x * zoom,
5185
+ y: bounds.Position.y + offset.Position.y * zoom
5186
+ },
5187
+ Size: {
5188
+ x: offset.Size.x * zoom,
5189
+ y: offset.Size.y * zoom
5190
+ }
5191
+ });
5192
+ }
5193
+ };
5194
+ project(this.#portOffsets.input, this.#inputPortPositions);
5195
+ project(this.#portOffsets.output, this.#outputPortPositions);
5196
+ return true;
5197
+ }
4899
5198
  };
4900
5199
 
4901
5200
  // src/markdown/token.ts
@@ -5036,7 +5335,10 @@ function onFontsLoaded(callback) {
5036
5335
  if (typeof document === "undefined" || document.fonts === void 0) {
5037
5336
  return;
5038
5337
  }
5039
- document.fonts.addEventListener("loadingdone", () => callback());
5338
+ document.fonts.addEventListener("loadingdone", () => {
5339
+ callback();
5340
+ requestRender();
5341
+ });
5040
5342
  }
5041
5343
  var CodeBlockEntry = class {
5042
5344
  #text;
@@ -5786,6 +6088,7 @@ var NoteSubsystem = class {
5786
6088
  return;
5787
6089
  }
5788
6090
  this.#notes.push(note);
6091
+ requestRender();
5789
6092
  for (let i = 0; i < this.#onNoteAddedCallbacks.length; i++) {
5790
6093
  this.#onNoteAddedCallbacks[i](note);
5791
6094
  }
@@ -5908,6 +6211,7 @@ var NoteSubsystem = class {
5908
6211
  if (index > -1) {
5909
6212
  const noteRemoved = this.#notes[index];
5910
6213
  this.#notes.splice(index, 1);
6214
+ requestRender();
5911
6215
  for (let i = 0; i < this.#onNoteRemovedCallbacks.length; i++) {
5912
6216
  this.#onNoteRemovedCallbacks[i](noteRemoved);
5913
6217
  }
@@ -6244,6 +6548,124 @@ var QuickMenu = class {
6244
6548
  }
6245
6549
  };
6246
6550
 
6551
+ // src/minimap.ts
6552
+ var Minimap = class {
6553
+ #enabled;
6554
+ #width;
6555
+ #height;
6556
+ #margin;
6557
+ #backgroundColor;
6558
+ #borderColor;
6559
+ #nodeColor;
6560
+ #viewportColor;
6561
+ #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
6562
+ #scale = 1;
6563
+ #originX = 0;
6564
+ #originY = 0;
6565
+ // The padding the last frame was laid out with, so a click maps back
6566
+ // through the same geometry it was drawn with.
6567
+ #padding = Theme.Minimap.Padding;
6568
+ #drawn = false;
6569
+ constructor(config) {
6570
+ this.#enabled = config?.enabled === true;
6571
+ this.#width = config?.width === void 0 ? 200 : config.width;
6572
+ this.#height = config?.height === void 0 ? 150 : config.height;
6573
+ this.#margin = config?.margin === void 0 ? 16 : config.margin;
6574
+ this.#backgroundColor = config?.backgroundColor;
6575
+ this.#borderColor = config?.borderColor;
6576
+ this.#nodeColor = config?.nodeColor;
6577
+ this.#viewportColor = config?.viewportColor;
6578
+ }
6579
+ enabled() {
6580
+ return this.#enabled;
6581
+ }
6582
+ setEnabled(enabled) {
6583
+ this.#enabled = enabled;
6584
+ this.#drawn = false;
6585
+ }
6586
+ contains(position) {
6587
+ return this.#enabled && this.#drawn && InBox(this.#box, position);
6588
+ }
6589
+ graphPositionOf(position, out) {
6590
+ out.x = (position.x - this.#box.Position.x - this.#padding) / this.#scale + this.#originX;
6591
+ out.y = (position.y - this.#box.Position.y - this.#padding) / this.#scale + this.#originY;
6592
+ }
6593
+ render(ctx, canvas, camera, nodes) {
6594
+ if (!this.#enabled) {
6595
+ this.#drawn = false;
6596
+ return;
6597
+ }
6598
+ const flat = new Camera();
6599
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
6600
+ const boxes = [];
6601
+ for (let i = 0; i < nodes.length; i++) {
6602
+ const bounds = nodes[i].calculateBounds(ctx, flat);
6603
+ boxes.push(bounds);
6604
+ minX = Math.min(minX, bounds.Position.x);
6605
+ minY = Math.min(minY, bounds.Position.y);
6606
+ maxX = Math.max(maxX, bounds.Position.x + bounds.Size.x);
6607
+ maxY = Math.max(maxY, bounds.Position.y + bounds.Size.y);
6608
+ }
6609
+ const topLeft = { x: 0, y: 0 };
6610
+ const bottomRight = { x: 0, y: 0 };
6611
+ camera.screenSpaceToGraphSpace({ x: 0, y: 0 }, topLeft);
6612
+ camera.screenSpaceToGraphSpace({ x: canvas.width, y: canvas.height }, bottomRight);
6613
+ minX = Math.min(minX, topLeft.x);
6614
+ minY = Math.min(minY, topLeft.y);
6615
+ maxX = Math.max(maxX, bottomRight.x);
6616
+ maxY = Math.max(maxY, bottomRight.y);
6617
+ if (!isFinite(minX) || maxX <= minX || maxY <= minY) {
6618
+ this.#drawn = false;
6619
+ return;
6620
+ }
6621
+ this.#box.Size.x = this.#width;
6622
+ this.#box.Size.y = this.#height;
6623
+ this.#box.Position.x = canvas.width - this.#width - this.#margin;
6624
+ this.#box.Position.y = canvas.height - this.#height - this.#margin;
6625
+ this.#padding = Theme.Minimap.Padding;
6626
+ const usableWidth = this.#width - this.#padding * 2;
6627
+ const usableHeight = this.#height - this.#padding * 2;
6628
+ this.#scale = Math.min(usableWidth / (maxX - minX), usableHeight / (maxY - minY));
6629
+ this.#originX = minX;
6630
+ this.#originY = minY;
6631
+ const left = this.#box.Position.x + this.#padding;
6632
+ const top = this.#box.Position.y + this.#padding;
6633
+ const toMapX = (x) => left + (x - minX) * this.#scale;
6634
+ const toMapY = (y) => top + (y - minY) * this.#scale;
6635
+ ctx.fillStyle = this.#backgroundColor ?? Theme.Minimap.BackgroundColor;
6636
+ ctx.strokeStyle = this.#borderColor ?? Theme.Minimap.BorderColor;
6637
+ ctx.lineWidth = 1;
6638
+ ctx.beginPath();
6639
+ ctx.roundRect(
6640
+ this.#box.Position.x,
6641
+ this.#box.Position.y,
6642
+ this.#width,
6643
+ this.#height,
6644
+ Theme.Minimap.BorderRadius
6645
+ );
6646
+ ctx.fill();
6647
+ ctx.stroke();
6648
+ ctx.fillStyle = this.#nodeColor ?? Theme.Minimap.NodeColor;
6649
+ for (let i = 0; i < boxes.length; i++) {
6650
+ const b = boxes[i];
6651
+ ctx.fillRect(
6652
+ toMapX(b.Position.x),
6653
+ toMapY(b.Position.y),
6654
+ Math.max(1, b.Size.x * this.#scale),
6655
+ Math.max(1, b.Size.y * this.#scale)
6656
+ );
6657
+ }
6658
+ ctx.fillStyle = this.#viewportColor ?? Theme.Minimap.ViewportColor;
6659
+ ctx.fillRect(
6660
+ toMapX(topLeft.x),
6661
+ toMapY(topLeft.y),
6662
+ Math.max(1, (bottomRight.x - topLeft.x) * this.#scale),
6663
+ Math.max(1, (bottomRight.y - topLeft.y) * this.#scale)
6664
+ );
6665
+ this.#drawn = true;
6666
+ }
6667
+ };
6668
+
6247
6669
  // src/graph.ts
6248
6670
  function BuildBackgroundRenderer(backgroundColor) {
6249
6671
  return (canvas, context, position, scale) => {
@@ -6381,7 +6803,13 @@ var NodeFlowGraph = class {
6381
6803
  const backgroundColor = config?.backgroundColor === void 0 ? Theme.Graph.BackgroundColor : config.backgroundColor;
6382
6804
  this.#backgroundRenderer = BuildBackgroundRenderer(backgroundColor);
6383
6805
  }
6384
- window.requestAnimationFrame(this.#render.bind(this));
6806
+ this.#minimap = new Minimap(config?.minimap);
6807
+ this.#mainNodeSubsystem.setConnectionReleasedHandler((dangling) => {
6808
+ this.#openScopedMenu(dangling);
6809
+ });
6810
+ this.#watchCanvasSize();
6811
+ this.#stopRendering = onRenderRequested(this.#render.bind(this));
6812
+ requestRender();
6385
6813
  new MouseObserver(
6386
6814
  this.#canvas,
6387
6815
  this.#mouseDragEvent.bind(this),
@@ -6399,39 +6827,90 @@ var NodeFlowGraph = class {
6399
6827
  document.addEventListener(
6400
6828
  "keydown",
6401
6829
  (e) => {
6402
- if (document.activeElement) {
6403
- if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
6404
- }
6405
- if (this.#openQuickMenu) {
6406
- if (e.code === "Escape") {
6407
- this.#openQuickMenu = null;
6408
- return;
6409
- }
6410
- if (e.code === "Enter") {
6411
- this.#openQuickMenu.Menu.execute();
6412
- this.#openQuickMenu = null;
6413
- return;
6414
- }
6415
- this.#openQuickMenu.Menu.keyboardEvent(e);
6830
+ this.#keyDown(e);
6831
+ requestRender();
6832
+ }
6833
+ );
6834
+ }
6835
+ #copyCallbacks = [];
6836
+ #pasteCallbacks = [];
6837
+ addCopyListener(callback) {
6838
+ this.#copyCallbacks.push(callback);
6839
+ }
6840
+ addPasteListener(callback) {
6841
+ this.#pasteCallbacks.push(callback);
6842
+ }
6843
+ #raiseCopy() {
6844
+ const nodes = this.#mainNodeSubsystem.getSelectedNodes();
6845
+ if (nodes.length === 0) {
6846
+ return;
6847
+ }
6848
+ const connections = this.#mainNodeSubsystem.connectionsWithin(nodes);
6849
+ this.#copyCallbacks.forEach((callback) => callback(nodes, connections));
6850
+ }
6851
+ #raisePaste() {
6852
+ const position = this.#mousePosition === void 0 ? { x: 0, y: 0 } : this.#sceenPositionToGraphPosition(this.#mousePosition);
6853
+ this.#pasteCallbacks.forEach((callback) => callback(position));
6854
+ }
6855
+ #deleteKey(e) {
6856
+ return e.code === "Delete" || e.code === "Backspace" || e.key === "Delete" || e.key === "Backspace";
6857
+ }
6858
+ #keyDown(e) {
6859
+ if (document.activeElement) {
6860
+ if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
6861
+ }
6862
+ if (this.#deleteKey(e)) {
6863
+ if (this.#mainNodeSubsystem.getSelectedNodes().length > 0) {
6864
+ e.preventDefault();
6865
+ this.#mainNodeSubsystem.removeSelectedNodes();
6866
+ return;
6867
+ }
6868
+ }
6869
+ if (e.ctrlKey || e.metaKey) {
6870
+ const letter = e.code.startsWith("Key") ? e.code.substring(3).toLowerCase() : (e.key ?? "").toLowerCase();
6871
+ switch (letter) {
6872
+ case "c":
6873
+ this.#raiseCopy();
6416
6874
  return;
6417
- }
6418
- const spacePressed = e.key == " " || e.code == "Space";
6419
- if (!spacePressed) {
6875
+ case "v":
6876
+ this.#raisePaste();
6420
6877
  return;
6421
- }
6422
- if (!this.#mousePosition) {
6878
+ // duplicate
6879
+ case "d":
6880
+ e.preventDefault();
6881
+ this.#raiseCopy();
6882
+ this.#raisePaste();
6423
6883
  return;
6424
- }
6425
- const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
6426
- let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
6427
- this.#openQuickMenu = {
6428
- Menu: new QuickMenu({
6429
- subMenus: items
6430
- }),
6431
- Position: contextMenuPosition
6432
- };
6433
6884
  }
6434
- );
6885
+ }
6886
+ if (this.#openQuickMenu) {
6887
+ if (e.code === "Escape") {
6888
+ this.#openQuickMenu = null;
6889
+ return;
6890
+ }
6891
+ if (e.code === "Enter") {
6892
+ this.#openQuickMenu.Menu.execute();
6893
+ this.#openQuickMenu = null;
6894
+ return;
6895
+ }
6896
+ this.#openQuickMenu.Menu.keyboardEvent(e);
6897
+ return;
6898
+ }
6899
+ const spacePressed = e.key == " " || e.code == "Space";
6900
+ if (!spacePressed) {
6901
+ return;
6902
+ }
6903
+ if (!this.#mousePosition) {
6904
+ return;
6905
+ }
6906
+ const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
6907
+ let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
6908
+ this.#openQuickMenu = {
6909
+ Menu: new QuickMenu({
6910
+ subMenus: items
6911
+ }),
6912
+ Position: contextMenuPosition
6913
+ };
6435
6914
  }
6436
6915
  #fileDrop(file) {
6437
6916
  if (this.currentView().fileDrop(file)) {
@@ -6497,8 +6976,25 @@ var NodeFlowGraph = class {
6497
6976
  this.#openQuickMenu = null;
6498
6977
  this.#contextMenuEntryHovering = null;
6499
6978
  this.#mousePosition = mousePosition;
6979
+ if (this.#minimap.contains(mousePosition)) {
6980
+ this.#lookAtFromMinimap(mousePosition);
6981
+ this.#draggingMinimap = true;
6982
+ return;
6983
+ }
6500
6984
  this.currentView().clickStart(mousePosition, this.#camera, ctrlKey);
6501
6985
  }
6986
+ #draggingMinimap = false;
6987
+ #lookAtFromMinimap(mousePosition) {
6988
+ const target = { x: 0, y: 0 };
6989
+ this.#minimap.graphPositionOf(mousePosition, target);
6990
+ this.#camera.position.x = -(target.x * this.#camera.zoom) + this.#canvas.width / 2;
6991
+ this.#camera.position.y = -(target.y * this.#camera.zoom) + this.#canvas.height / 2;
6992
+ requestRender();
6993
+ }
6994
+ /** The minimap, so it can be turned on and sized by the host. */
6995
+ minimap() {
6996
+ return this.#minimap;
6997
+ }
6502
6998
  currentView() {
6503
6999
  return this.#views[this.#currentView];
6504
7000
  }
@@ -6518,6 +7014,12 @@ var NodeFlowGraph = class {
6518
7014
  getSelectedNodes() {
6519
7015
  return this.#mainNodeSubsystem.getSelectedNodes();
6520
7016
  }
7017
+ unselectAllNodes() {
7018
+ this.#mainNodeSubsystem.unselectAllNodes();
7019
+ }
7020
+ removeSelectedNodes() {
7021
+ return this.#mainNodeSubsystem.removeSelectedNodes();
7022
+ }
6521
7023
  /**
6522
7024
  * Returns a copy of the current camera orientation (screen-space pan + zoom).
6523
7025
  */
@@ -6633,10 +7135,37 @@ var NodeFlowGraph = class {
6633
7135
  Position: contextMenuPosition
6634
7136
  };
6635
7137
  }
7138
+ #openScopedMenu(dangling) {
7139
+ if (this.#mousePosition === void 0) {
7140
+ return;
7141
+ }
7142
+ const position = this.#sceenPositionToGraphPosition(this.#mousePosition);
7143
+ const menu = this.#mainNodeSubsystem.nodeFactory().openMenu(
7144
+ this.#mainNodeSubsystem,
7145
+ position,
7146
+ {
7147
+ needs: dangling.needs,
7148
+ types: dangling.types,
7149
+ onCreated: dangling.connectTo
7150
+ }
7151
+ );
7152
+ this.#openedContextMenu = {
7153
+ Menu: new ContextMenu(menu),
7154
+ Position: position
7155
+ };
7156
+ requestRender();
7157
+ }
6636
7158
  #clickEnd() {
7159
+ this.#draggingMinimap = false;
6637
7160
  this.currentView().clickEnd();
6638
7161
  }
6639
7162
  #mouseDragEvent(delta) {
7163
+ if (this.#draggingMinimap) {
7164
+ if (this.#mousePosition !== void 0) {
7165
+ this.#lookAtFromMinimap(this.#mousePosition);
7166
+ }
7167
+ return;
7168
+ }
6640
7169
  let draggingSomething = this.currentView().mouseDragEvent(delta, this.#camera.zoom);
6641
7170
  if (!draggingSomething) {
6642
7171
  this.#camera.position.x += delta.x;
@@ -6645,12 +7174,45 @@ var NodeFlowGraph = class {
6645
7174
  }
6646
7175
  #lastFrameCursor;
6647
7176
  #cursor;
6648
- #render() {
6649
- if (this.#canvas.parentNode !== null) {
6650
- var rect = this.#canvas.parentNode.getBoundingClientRect();
6651
- this.#canvas.width = rect.width;
6652
- this.#canvas.height = rect.height;
7177
+ #minimap;
7178
+ #stopRendering;
7179
+ #sizeObserver;
7180
+ #matchCanvasToParent() {
7181
+ const parent = this.#canvas.parentNode;
7182
+ if (parent === null) {
7183
+ return;
6653
7184
  }
7185
+ const rect = parent.getBoundingClientRect();
7186
+ if (this.#canvas.width === rect.width && this.#canvas.height === rect.height) {
7187
+ return;
7188
+ }
7189
+ this.#canvas.width = rect.width;
7190
+ this.#canvas.height = rect.height;
7191
+ }
7192
+ #watchCanvasSize() {
7193
+ this.#matchCanvasToParent();
7194
+ const parent = this.#canvas.parentNode;
7195
+ if (parent === null || typeof ResizeObserver === "undefined") {
7196
+ return;
7197
+ }
7198
+ this.#sizeObserver = new ResizeObserver(() => {
7199
+ this.#matchCanvasToParent();
7200
+ requestRender();
7201
+ });
7202
+ this.#sizeObserver.observe(parent);
7203
+ }
7204
+ requestRender() {
7205
+ requestRender();
7206
+ }
7207
+ renderContinuously() {
7208
+ return renderContinuously();
7209
+ }
7210
+ dispose() {
7211
+ this.#stopRendering();
7212
+ this.#sizeObserver?.disconnect();
7213
+ }
7214
+ #render() {
7215
+ this.#matchCanvasToParent();
6654
7216
  this.#cursor = "default" /* Default */;
6655
7217
  TimeExecution("Render_Background", this.#renderBackground.bind(this));
6656
7218
  TimeExecution("Render_View_" + this.#currentView, () => {
@@ -6659,13 +7221,15 @@ var NodeFlowGraph = class {
6659
7221
  this.#cursor = results?.cursorStyle;
6660
7222
  }
6661
7223
  });
7224
+ TimeExecution("Render_Minimap", () => {
7225
+ this.#minimap.render(this.#ctx, this.#canvas, this.#camera, this.#mainNodeSubsystem.getNodes());
7226
+ });
6662
7227
  TimeExecution("Render_Context", this.#renderContextMenu.bind(this));
6663
7228
  TimeExecution("Render_QuickMenu", this.#renderQuickMenu.bind(this));
6664
7229
  if (this.#lastFrameCursor !== this.#cursor) {
6665
7230
  this.#canvas.style.cursor = this.#cursor;
6666
7231
  }
6667
7232
  this.#lastFrameCursor = this.#cursor;
6668
- window.requestAnimationFrame(this.#render.bind(this));
6669
7233
  }
6670
7234
  #renderBackground() {
6671
7235
  this.#backgroundRenderer(this.#canvas, this.#ctx, this.#camera.position, this.#camera.zoom);
@@ -6706,6 +7270,7 @@ export {
6706
7270
  GlobalWidgetFactory,
6707
7271
  ImageWidget,
6708
7272
  MessageType,
7273
+ Minimap,
6709
7274
  NodeFactory,
6710
7275
  NodeFlowGraph,
6711
7276
  NodeState,
@@ -6719,6 +7284,10 @@ export {
6719
7284
  Theme,
6720
7285
  ToggleWidget,
6721
7286
  WidgetFactory,
6722
- portsCompatible
7287
+ portConfigTypes,
7288
+ portsCompatible,
7289
+ renderContinuously,
7290
+ requestRender,
7291
+ typeSetsCompatible
6723
7292
  };
6724
7293
  //# sourceMappingURL=index.mjs.map