@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.js CHANGED
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  GlobalWidgetFactory: () => GlobalWidgetFactory,
31
31
  ImageWidget: () => ImageWidget,
32
32
  MessageType: () => MessageType,
33
+ Minimap: () => Minimap,
33
34
  NodeFactory: () => NodeFactory,
34
35
  NodeFlowGraph: () => NodeFlowGraph,
35
36
  NodeState: () => NodeState,
@@ -43,7 +44,11 @@ __export(index_exports, {
43
44
  Theme: () => Theme,
44
45
  ToggleWidget: () => ToggleWidget,
45
46
  WidgetFactory: () => WidgetFactory,
46
- portsCompatible: () => portsCompatible
47
+ portConfigTypes: () => portConfigTypes,
48
+ portsCompatible: () => portsCompatible,
49
+ renderContinuously: () => renderContinuously,
50
+ requestRender: () => requestRender,
51
+ typeSetsCompatible: () => typeSetsCompatible
47
52
  });
48
53
  module.exports = __toCommonJS(index_exports);
49
54
 
@@ -109,6 +114,14 @@ var Theme = {
109
114
  FontColor: "#afb9bb",
110
115
  DisabledFontColor: "#4a585c"
111
116
  },
117
+ Minimap: {
118
+ BackgroundColor: "rgba(7, 33, 42, 0.85)",
119
+ BorderColor: "#1c1c1c",
120
+ BorderRadius: 4,
121
+ Padding: 6,
122
+ NodeColor: "#6b7f85",
123
+ ViewportColor: "rgba(175, 185, 187, 0.25)"
124
+ },
112
125
  Note: {
113
126
  FontColor: "#afb9bb",
114
127
  FontSize: 16,
@@ -650,6 +663,43 @@ var ContextMenu = class _ContextMenu {
650
663
  }
651
664
  };
652
665
 
666
+ // src/render_scheduler.ts
667
+ var subscribers = /* @__PURE__ */ new Set();
668
+ var frameQueued = false;
669
+ var continuous = 0;
670
+ var canSchedule = () => typeof requestAnimationFrame === "function";
671
+ function onRenderRequested(redraw) {
672
+ subscribers.add(redraw);
673
+ return () => {
674
+ subscribers.delete(redraw);
675
+ };
676
+ }
677
+ function requestRender() {
678
+ if (frameQueued || !canSchedule()) {
679
+ return;
680
+ }
681
+ frameQueued = true;
682
+ requestAnimationFrame(() => {
683
+ frameQueued = false;
684
+ subscribers.forEach((redraw) => redraw());
685
+ if (continuous > 0) {
686
+ requestRender();
687
+ }
688
+ });
689
+ }
690
+ function renderContinuously() {
691
+ continuous++;
692
+ requestRender();
693
+ let released = false;
694
+ return () => {
695
+ if (released) {
696
+ return;
697
+ }
698
+ released = true;
699
+ continuous--;
700
+ };
701
+ }
702
+
653
703
  // src/input.ts
654
704
  var LONG_PRESS_MS = 500;
655
705
  var LONG_PRESS_SLOP = 12;
@@ -683,15 +733,19 @@ var MouseObserver = class {
683
733
  this.#lastTouch = Zero();
684
734
  this.#lastMousePosition = Zero();
685
735
  ele.style.touchAction = "none";
686
- ele.addEventListener("mousedown", this.#down.bind(this), false);
687
- ele.addEventListener("touchstart", this.#touchStart.bind(this), { passive: false });
688
- document.addEventListener("mouseup", this.#up.bind(this), false);
689
- document.addEventListener("touchend", this.#touchEnd.bind(this), false);
690
- document.addEventListener("touchcancel", this.#touchEnd.bind(this), false);
691
- ele.addEventListener("mousemove", this.#move.bind(this), false);
692
- ele.addEventListener("touchmove", this.#touchMove.bind(this), { passive: false });
693
- ele.addEventListener("wheel", this.#wheel.bind(this), { passive: false });
694
- ele.addEventListener("drop", (ev) => {
736
+ const redraws = (handler) => (event) => {
737
+ handler(event);
738
+ requestRender();
739
+ };
740
+ ele.addEventListener("mousedown", redraws(this.#down.bind(this)), false);
741
+ ele.addEventListener("touchstart", redraws(this.#touchStart.bind(this)), { passive: false });
742
+ document.addEventListener("mouseup", redraws(this.#up.bind(this)), false);
743
+ document.addEventListener("touchend", redraws(this.#touchEnd.bind(this)), false);
744
+ document.addEventListener("touchcancel", redraws(this.#touchEnd.bind(this)), false);
745
+ ele.addEventListener("mousemove", redraws(this.#move.bind(this)), false);
746
+ ele.addEventListener("touchmove", redraws(this.#touchMove.bind(this)), { passive: false });
747
+ ele.addEventListener("wheel", redraws(this.#wheel.bind(this)), { passive: false });
748
+ ele.addEventListener("drop", redraws((ev) => {
695
749
  ev.preventDefault();
696
750
  if (ev.dataTransfer?.items) {
697
751
  [...ev.dataTransfer.items].forEach((item, i) => {
@@ -703,18 +757,18 @@ var MouseObserver = class {
703
757
  }
704
758
  });
705
759
  }
706
- });
707
- ele.addEventListener("dragover", (ev) => {
760
+ }));
761
+ ele.addEventListener("dragover", redraws((ev) => {
708
762
  ev.preventDefault();
709
763
  this.#moveCallback(this.#mousePos(ev));
710
- });
711
- ele.addEventListener("contextmenu", (evt) => {
764
+ }));
765
+ ele.addEventListener("contextmenu", redraws((evt) => {
712
766
  evt.preventDefault();
713
767
  if (Date.now() - this.#lastTouchAt < SUPPRESS_MOUSE_MS) {
714
768
  return;
715
769
  }
716
770
  this.#contextMenu(this.#mousePos(evt));
717
- }, false);
771
+ }), false);
718
772
  }
719
773
  #mousePos(event) {
720
774
  const rect = this.#ele.getBoundingClientRect();
@@ -811,6 +865,7 @@ var MouseObserver = class {
811
865
  this.#clicked = false;
812
866
  this.#clickStop();
813
867
  this.#contextMenu({ x, y });
868
+ requestRender();
814
869
  }, LONG_PRESS_MS);
815
870
  this.#clicked = true;
816
871
  this.#moveCallback(pos);
@@ -1247,6 +1302,7 @@ var Text = class _Text {
1247
1302
  if (!document.fonts.check(`16px "${this.#style.getFont()}"`)) {
1248
1303
  document.fonts.addEventListener("loadingdone", () => {
1249
1304
  this.#measured = false;
1305
+ requestRender();
1250
1306
  });
1251
1307
  }
1252
1308
  }
@@ -1458,12 +1514,22 @@ function portsCompatible(a, b) {
1458
1514
  if (!a || !b) {
1459
1515
  return false;
1460
1516
  }
1461
- const left = a.acceptedTypes();
1462
- const right = b.acceptedTypes();
1463
- if (left.length === 0 || right.length === 0) {
1517
+ return typeSetsCompatible(a.acceptedTypes(), b.acceptedTypes());
1518
+ }
1519
+ function typeSetsCompatible(a, b) {
1520
+ if (a.length === 0 || b.length === 0) {
1464
1521
  return true;
1465
1522
  }
1466
- return left.some((type) => right.includes(type));
1523
+ return a.some((type) => b.includes(type));
1524
+ }
1525
+ function portConfigTypes(config) {
1526
+ if (config.anyType === true) {
1527
+ return [];
1528
+ }
1529
+ if (config.acceptedTypes !== void 0 && config.acceptedTypes.length > 0) {
1530
+ return config.acceptedTypes;
1531
+ }
1532
+ return [config.type === void 0 ? "" : config.type];
1467
1533
  }
1468
1534
  function fallbackColor(type, s) {
1469
1535
  let value = 0;
@@ -1578,6 +1644,7 @@ var Port = class {
1578
1644
  this.#filledStyle.fillColor = fallbackColor(this.#dataType, 1);
1579
1645
  }
1580
1646
  this.#dataTypePopupElement = this.#buildTooltip();
1647
+ requestRender();
1581
1648
  }
1582
1649
  setDataType(type) {
1583
1650
  if (this.#dataType === type) {
@@ -2656,6 +2723,7 @@ var ImageWidget = class {
2656
2723
  img.src = url;
2657
2724
  img.onload = () => {
2658
2725
  this.#image = img;
2726
+ requestRender();
2659
2727
  };
2660
2728
  img.onerror = (event) => {
2661
2729
  console.log("error loading image:", url, event);
@@ -3236,6 +3304,13 @@ function TimeExecution(name, callback) {
3236
3304
  }
3237
3305
 
3238
3306
  // src/nodes/publisher.ts
3307
+ function offersMatchingPort(config, filter) {
3308
+ const ports = filter.needs === "INPUT" /* Input */ ? config.inputs : config.outputs;
3309
+ if (ports === void 0) {
3310
+ return false;
3311
+ }
3312
+ return ports.some((port) => typeSetsCompatible(portConfigTypes(port), filter.types));
3313
+ }
3239
3314
  var Publisher = class {
3240
3315
  #name;
3241
3316
  #description;
@@ -3261,19 +3336,23 @@ var Publisher = class {
3261
3336
  unregister(nodeType) {
3262
3337
  return this.#registeredNodes.delete(nodeType);
3263
3338
  }
3264
- #recurseBuildMenu(graph, name, subMenu, position) {
3339
+ #recurseBuildMenu(graph, name, subMenu, position, filter) {
3265
3340
  const items = [];
3266
3341
  const subMenus = /* @__PURE__ */ new Map();
3267
3342
  for (let [key, nodeConfig] of subMenu) {
3268
3343
  const slashIndex = key.indexOf("/");
3269
3344
  const bracketIndex = key.indexOf("[");
3270
3345
  if (slashIndex === -1 || bracketIndex !== -1 && bracketIndex < slashIndex) {
3346
+ if (filter !== void 0 && !offersMatchingPort(nodeConfig, filter)) {
3347
+ continue;
3348
+ }
3271
3349
  items.push({
3272
3350
  name: key,
3273
3351
  callback: () => {
3274
3352
  const node = new FlowNode(nodeConfig);
3275
3353
  node.setPosition(position);
3276
3354
  graph.addNode(node);
3355
+ filter?.onCreated?.(node);
3277
3356
  }
3278
3357
  });
3279
3358
  } else {
@@ -3288,7 +3367,11 @@ var Publisher = class {
3288
3367
  }
3289
3368
  const menus = [];
3290
3369
  for (let [key, nodes] of subMenus) {
3291
- menus.push(this.#recurseBuildMenu(graph, key, nodes, position));
3370
+ const built = this.#recurseBuildMenu(graph, key, nodes, position, filter);
3371
+ if (filter !== void 0 && built.items?.length === 0 && built.subMenus?.length === 0) {
3372
+ continue;
3373
+ }
3374
+ menus.push(built);
3292
3375
  }
3293
3376
  return {
3294
3377
  name,
@@ -3296,8 +3379,8 @@ var Publisher = class {
3296
3379
  subMenus: menus
3297
3380
  };
3298
3381
  }
3299
- contextMenu(graph, position) {
3300
- return this.#recurseBuildMenu(graph, this.#name, this.#registeredNodes, position);
3382
+ contextMenu(graph, position, filter) {
3383
+ return this.#recurseBuildMenu(graph, this.#name, this.#registeredNodes, position, filter);
3301
3384
  }
3302
3385
  create(nodeType) {
3303
3386
  const config = this.#registeredNodes.get(nodeType);
@@ -3352,18 +3435,22 @@ var NodeFactory = class {
3352
3435
  }
3353
3436
  return node;
3354
3437
  }
3355
- newNodeSubmenus(graph, position) {
3438
+ newNodeSubmenus(graph, position, filter) {
3356
3439
  const menus = [];
3357
3440
  for (let [_, publisher] of this.#registeredPublishers) {
3358
- menus.push(publisher.contextMenu(graph, position));
3441
+ const built = publisher.contextMenu(graph, position, filter);
3442
+ if (filter !== void 0 && built.items?.length === 0 && built.subMenus?.length === 0) {
3443
+ continue;
3444
+ }
3445
+ menus.push(built);
3359
3446
  }
3360
3447
  return menus;
3361
3448
  }
3362
- openMenu(graph, position) {
3449
+ openMenu(graph, position, filter) {
3363
3450
  return {
3364
3451
  name: "New Node",
3365
3452
  group: nodeFlowGroup,
3366
- subMenus: this.newNodeSubmenus(graph, position)
3453
+ subMenus: this.newNodeSubmenus(graph, position, filter)
3367
3454
  };
3368
3455
  }
3369
3456
  };
@@ -3502,6 +3589,9 @@ var NodeSubsystem2 = class {
3502
3589
  CopyVector2(this.#boxSelectEnd_graphSpace, this.#boxSelectStart_graphSpace);
3503
3590
  this.#boxSelect = true;
3504
3591
  }
3592
+ if (!hoveringSomething && !ctrlKey) {
3593
+ this.unselectAllNodes();
3594
+ }
3505
3595
  return hoveringSomething || ctrlKey;
3506
3596
  }
3507
3597
  if (this.#portHovering.InputPort) {
@@ -3561,6 +3651,102 @@ var NodeSubsystem2 = class {
3561
3651
  }
3562
3652
  }
3563
3653
  }
3654
+ connectionsWithin(nodes) {
3655
+ const within = [];
3656
+ for (const connection of this.#connections) {
3657
+ const outNode = connection.outNode();
3658
+ const inNode = connection.inNode();
3659
+ if (outNode === null || inNode === null) {
3660
+ continue;
3661
+ }
3662
+ const from = nodes.indexOf(outNode);
3663
+ const to = nodes.indexOf(inNode);
3664
+ if (from === -1 || to === -1) {
3665
+ continue;
3666
+ }
3667
+ const outPort = connection.outPort();
3668
+ const inPort = connection.inPort();
3669
+ if (outPort === null || inPort === null) {
3670
+ continue;
3671
+ }
3672
+ within.push({
3673
+ fromNode: from,
3674
+ fromPort: this.#outputPortIndex(outNode, outPort),
3675
+ toNode: to,
3676
+ toPort: this.#inputPortIndex(inNode, inPort)
3677
+ });
3678
+ }
3679
+ return within;
3680
+ }
3681
+ #onConnectionReleased = null;
3682
+ setConnectionReleasedHandler(handler) {
3683
+ this.#onConnectionReleased = handler;
3684
+ }
3685
+ #describeDanglingConnection(conn) {
3686
+ const outNode = conn.outNode();
3687
+ const inNode = conn.inNode();
3688
+ if (outNode !== null && inNode === null) {
3689
+ const from = conn.outPort();
3690
+ if (from === null) {
3691
+ return null;
3692
+ }
3693
+ const fromIndex = this.#outputPortIndex(outNode, from);
3694
+ return {
3695
+ needs: "INPUT" /* Input */,
3696
+ types: from.acceptedTypes(),
3697
+ connectTo: (node) => {
3698
+ const to = this.#firstCompatiblePort(node, "INPUT" /* Input */, from);
3699
+ if (to !== -1 && fromIndex !== -1) {
3700
+ this.connectNodes(outNode, fromIndex, node, to);
3701
+ }
3702
+ }
3703
+ };
3704
+ }
3705
+ if (inNode !== null && outNode === null) {
3706
+ const to = conn.inPort();
3707
+ if (to === null) {
3708
+ return null;
3709
+ }
3710
+ const toIndex = this.#inputPortIndex(inNode, to);
3711
+ return {
3712
+ needs: "OUTPUT" /* Output */,
3713
+ types: to.acceptedTypes(),
3714
+ connectTo: (node) => {
3715
+ const from = this.#firstCompatiblePort(node, "OUTPUT" /* Output */, to);
3716
+ if (from !== -1 && toIndex !== -1) {
3717
+ this.connectNodes(node, from, inNode, toIndex);
3718
+ }
3719
+ }
3720
+ };
3721
+ }
3722
+ return null;
3723
+ }
3724
+ #outputPortIndex(node, port) {
3725
+ for (let i = 0; i < node.outputs(); i++) {
3726
+ if (node.outputPort(i) === port) {
3727
+ return i;
3728
+ }
3729
+ }
3730
+ return -1;
3731
+ }
3732
+ #inputPortIndex(node, port) {
3733
+ for (let i = 0; i < node.inputs(); i++) {
3734
+ if (node.inputPort(i) === port) {
3735
+ return i;
3736
+ }
3737
+ }
3738
+ return -1;
3739
+ }
3740
+ #firstCompatiblePort(node, side, against) {
3741
+ const count = side === "INPUT" /* Input */ ? node.inputs() : node.outputs();
3742
+ for (let i = 0; i < count; i++) {
3743
+ const port = side === "INPUT" /* Input */ ? node.inputPort(i) : node.outputPort(i);
3744
+ if (portsCompatible(port, against)) {
3745
+ return i;
3746
+ }
3747
+ }
3748
+ return -1;
3749
+ }
3564
3750
  clickEnd() {
3565
3751
  for (let i = 0; i < this.#nodesGrabbed.Count(); i++) {
3566
3752
  const node = this.#nodes[this.#nodesGrabbed.At(i)];
@@ -3582,7 +3768,11 @@ var NodeSubsystem2 = class {
3582
3768
  return;
3583
3769
  }
3584
3770
  if (this.#portHovering === null) {
3771
+ const dangling = this.#describeDanglingConnection(this.#connectionSelected);
3585
3772
  this.#clearCurrentlySelectedConnection();
3773
+ if (dangling !== null) {
3774
+ this.#onConnectionReleased?.(dangling);
3775
+ }
3586
3776
  return;
3587
3777
  }
3588
3778
  const port = this.#portHovering.Port;
@@ -3649,6 +3839,7 @@ var NodeSubsystem2 = class {
3649
3839
  this.#idleConnectionRenderer
3650
3840
  );
3651
3841
  this.#connections.push(connection);
3842
+ requestRender();
3652
3843
  return connection;
3653
3844
  }
3654
3845
  getNodes() {
@@ -3709,6 +3900,7 @@ var NodeSubsystem2 = class {
3709
3900
  }
3710
3901
  addNode(node) {
3711
3902
  this.#nodes.push(node);
3903
+ requestRender();
3712
3904
  for (let i = 0; i < this.#registeredNodeAddedCallbacks.length; i++) {
3713
3905
  const callback = this.#registeredNodeAddedCallbacks[i];
3714
3906
  if (!callback) {
@@ -3755,6 +3947,7 @@ var NodeSubsystem2 = class {
3755
3947
  this.#removeNodeConnections(nodeIndex);
3756
3948
  const node = this.#nodes[nodeIndex];
3757
3949
  this.#nodes.splice(nodeIndex, 1);
3950
+ requestRender();
3758
3951
  for (let i = 0; i < this.#nodeRemovedCallbacks.length; i++) {
3759
3952
  this.#nodeRemovedCallbacks[i](node);
3760
3953
  }
@@ -3764,6 +3957,7 @@ var NodeSubsystem2 = class {
3764
3957
  this.#connections[index].clearPorts();
3765
3958
  }
3766
3959
  this.#connections.splice(index, 1);
3960
+ requestRender();
3767
3961
  }
3768
3962
  #removeConnection(connection, clearPorts) {
3769
3963
  const index = this.#connections.indexOf(connection);
@@ -3809,7 +4003,8 @@ var NodeSubsystem2 = class {
3809
4003
  this.#nodeFactory.openMenu(this, position)
3810
4004
  ]
3811
4005
  };
3812
- if (this.#nodesSelected().length > 0) {
4006
+ const selectedCount = this.#nodesSelected().length;
4007
+ if (selectedCount > 0) {
3813
4008
  organizeNodesSubMenu.items?.push({
3814
4009
  name: "Selected Nodes",
3815
4010
  group: nodeFlowGroup,
@@ -3817,6 +4012,13 @@ var NodeSubsystem2 = class {
3817
4012
  this.#organizeSelected(ctx);
3818
4013
  }
3819
4014
  });
4015
+ config.items?.push({
4016
+ name: selectedCount === 1 ? "Delete Selected Node" : `Delete ${selectedCount} Selected Nodes`,
4017
+ group: nodeFlowGroup,
4018
+ callback: () => {
4019
+ this.removeSelectedNodes();
4020
+ }
4021
+ });
3820
4022
  }
3821
4023
  if (this.#nodeHovering > -1) {
3822
4024
  const nodeToReview = this.#nodeHovering;
@@ -3890,6 +4092,29 @@ var NodeSubsystem2 = class {
3890
4092
  this.#selectNode(inputs[i], false);
3891
4093
  }
3892
4094
  }
4095
+ /** Drops the selection entirely. */
4096
+ unselectAllNodes() {
4097
+ let changed = false;
4098
+ for (let i = 0; i < this.#nodes.length; i++) {
4099
+ if (this.#nodes[i].selected()) {
4100
+ this.#nodes[i].unselect();
4101
+ changed = true;
4102
+ }
4103
+ }
4104
+ if (changed) {
4105
+ requestRender();
4106
+ }
4107
+ }
4108
+ /**
4109
+ * Removes every selected node, and the connections that touched them.
4110
+ */
4111
+ removeSelectedNodes() {
4112
+ const removed = this.getSelectedNodes();
4113
+ for (const node of removed) {
4114
+ this.removeNode(node);
4115
+ }
4116
+ return removed;
4117
+ }
3893
4118
  #selectNodeByIndex(nodeIndex, unselectOthers) {
3894
4119
  this.#selectNode(this.#nodes[nodeIndex], unselectOthers);
3895
4120
  }
@@ -3927,8 +4152,16 @@ var NodeSubsystem2 = class {
3927
4152
  this.#nodeHovering = -1;
3928
4153
  this.#boxSelectionNodes.Clear();
3929
4154
  const selectedBox_Screenspace = this.#boxSelectionScreenspaceBox(camera);
4155
+ const canvas = {
4156
+ Position: { x: 0, y: 0 },
4157
+ Size: { x: ctx.canvas.width, y: ctx.canvas.height }
4158
+ };
3930
4159
  for (let i = 0; i < this.#nodes.length; i++) {
3931
4160
  let state = 0 /* Idle */;
4161
+ const nodeBounds = this.#nodes[i].calculateBounds(ctx, camera);
4162
+ if (!BoxIntersection(nodeBounds, canvas) && this.#nodes[i].layoutPortsWithoutDrawing(nodeBounds, camera.zoom)) {
4163
+ continue;
4164
+ }
3932
4165
  if (mousePosition !== void 0 && !this.#boxSelect) {
3933
4166
  const intersection = this.#nodes[i].inBounds(ctx, camera, mousePosition);
3934
4167
  if (intersection.Node !== void 0 && intersection.PortIndex === void 0 && intersection.Widget === void 0) {
@@ -3949,8 +4182,8 @@ var NodeSubsystem2 = class {
3949
4182
  };
3950
4183
  }
3951
4184
  } else if (this.#boxSelect) {
3952
- const nodeBounds = this.#nodes[i].calculateBounds(ctx, camera);
3953
- if (BoxIntersection(selectedBox_Screenspace, nodeBounds)) {
4185
+ const nodeBounds2 = this.#nodes[i].calculateBounds(ctx, camera);
4186
+ if (BoxIntersection(selectedBox_Screenspace, nodeBounds2)) {
3954
4187
  state = 1 /* MouseOver */;
3955
4188
  this.#boxSelectionNodes.Push(i);
3956
4189
  }
@@ -3972,12 +4205,16 @@ var NodeSubsystem2 = class {
3972
4205
  render(ctx, camera, mousePosition) {
3973
4206
  this.#lastCtx = ctx;
3974
4207
  this.#cursor = "default" /* Default */;
4208
+ const awaitingLayout = this.#nodes.some((node) => !node.hasBeenLaidOut());
3975
4209
  TimeExecution("Render_Connections", () => {
3976
4210
  this.#renderConnections(ctx, camera, mousePosition);
3977
4211
  });
3978
4212
  TimeExecution("Render_Nodes", () => {
3979
4213
  this.#renderNodes(ctx, camera, mousePosition);
3980
4214
  });
4215
+ if (awaitingLayout) {
4216
+ requestRender();
4217
+ }
3981
4218
  if (this.#boxSelect) {
3982
4219
  const box = this.#boxSelectionScreenspaceBox(camera);
3983
4220
  ctx.setLineDash([Theme.BoxSelect.LineDashLength]);
@@ -4253,6 +4490,7 @@ var FlowNode = class {
4253
4490
  return;
4254
4491
  }
4255
4492
  this.#selected = true;
4493
+ requestRender();
4256
4494
  for (let i = 0; i < this.#onSelect.length; i++) {
4257
4495
  this.#onSelect[i]();
4258
4496
  }
@@ -4298,6 +4536,7 @@ var FlowNode = class {
4298
4536
  return;
4299
4537
  }
4300
4538
  this.#data[name] = value;
4539
+ requestRender();
4301
4540
  for (let i = 0; i < this.#registeredAnyPropertyChangeCallbacks.length; i++) {
4302
4541
  this.#registeredAnyPropertyChangeCallbacks[i](name, oldValue, value);
4303
4542
  }
@@ -4558,6 +4797,7 @@ var FlowNode = class {
4558
4797
  return;
4559
4798
  }
4560
4799
  this.#selected = false;
4800
+ requestRender();
4561
4801
  for (let i = 0; i < this.#onUnselect.length; i++) {
4562
4802
  this.#onUnselect[i]();
4563
4803
  }
@@ -4587,6 +4827,7 @@ var FlowNode = class {
4587
4827
  }
4588
4828
  setPosition(position) {
4589
4829
  CopyVector2(this.#position, position);
4830
+ requestRender();
4590
4831
  }
4591
4832
  getPosition() {
4592
4833
  return this.#position;
@@ -4649,10 +4890,12 @@ var FlowNode = class {
4649
4890
  }
4650
4891
  addWidget(widget) {
4651
4892
  this.#widgets.push(widget);
4893
+ requestRender();
4652
4894
  }
4653
4895
  insertWidget(index, widget) {
4654
4896
  const at = Math.max(0, Math.min(index, this.#widgets.length));
4655
4897
  this.#widgets.splice(at, 0, widget);
4898
+ requestRender();
4656
4899
  }
4657
4900
  removeWidget(widget) {
4658
4901
  const index = this.#widgets.indexOf(widget, 0);
@@ -4660,12 +4903,15 @@ var FlowNode = class {
4660
4903
  throw new Error("node does not contain widget");
4661
4904
  }
4662
4905
  this.#widgets.splice(index, 1);
4906
+ requestRender();
4663
4907
  }
4664
4908
  removeWidgetAt(index) {
4665
4909
  if (index < 0 || index >= this.#widgets.length) {
4666
4910
  return void 0;
4667
4911
  }
4668
- return this.#widgets.splice(index, 1)[0];
4912
+ const removed = this.#widgets.splice(index, 1)[0];
4913
+ requestRender();
4914
+ return removed;
4669
4915
  }
4670
4916
  moveWidget(from, to) {
4671
4917
  if (from < 0 || from >= this.#widgets.length) {
@@ -4676,9 +4922,11 @@ var FlowNode = class {
4676
4922
  }
4677
4923
  const [widget] = this.#widgets.splice(from, 1);
4678
4924
  this.#widgets.splice(to, 0, widget);
4925
+ requestRender();
4679
4926
  }
4680
4927
  clearWidgets() {
4681
4928
  this.#widgets.length = 0;
4929
+ requestRender();
4682
4930
  }
4683
4931
  getWidget(index) {
4684
4932
  return this.#widgets[index];
@@ -4750,6 +4998,7 @@ var FlowNode = class {
4750
4998
  return;
4751
4999
  }
4752
5000
  this.#title.set(cleaned);
5001
+ requestRender();
4753
5002
  for (let i = 0; i < this.#titleChangeCallback.length; i++) {
4754
5003
  const callback = this.#titleChangeCallback[i];
4755
5004
  callback(this, old, cleaned);
@@ -4768,6 +5017,7 @@ var FlowNode = class {
4768
5017
  }
4769
5018
  const old = this.#infoText;
4770
5019
  this.#infoText = cleaned;
5020
+ requestRender();
4771
5021
  for (let i = 0; i < this.#infoChangeCallback.length; i++) {
4772
5022
  const callback = this.#infoChangeCallback[i];
4773
5023
  callback(this, old, cleaned);
@@ -4807,7 +5057,12 @@ var FlowNode = class {
4807
5057
  clearMessages() {
4808
5058
  this.#messages = [];
4809
5059
  }
5060
+ hasBeenLaidOut() {
5061
+ return this.#laidOut;
5062
+ }
5063
+ #laidOut = false;
4810
5064
  render(ctx, camera, state, mousePosition, postProcess) {
5065
+ this.#laidOut = true;
4811
5066
  VectorPool.run(() => {
4812
5067
  const tempMeasurement = VectorPool.get();
4813
5068
  this.#inputPortPositions.Clear();
@@ -4943,8 +5198,57 @@ var FlowNode = class {
4943
5198
  const message = this.#messages[i];
4944
5199
  messageStart.y += message.render(ctx, camera.zoom, messageStart, !(state === 0 /* Idle */)) + 10 * camera.zoom;
4945
5200
  }
5201
+ this.#rememberPortOffsets(nodeBounds, camera.zoom);
4946
5202
  });
4947
5203
  }
5204
+ #portOffsets = { input: [], output: [] };
5205
+ #rememberPortOffsets(bounds, zoom) {
5206
+ if (zoom <= 0) {
5207
+ return;
5208
+ }
5209
+ const record = (positions, into) => {
5210
+ into.length = 0;
5211
+ for (let i = 0; i < positions.Count(); i++) {
5212
+ const box = positions.At(i);
5213
+ into.push({
5214
+ Position: {
5215
+ x: (box.Position.x - bounds.Position.x) / zoom,
5216
+ y: (box.Position.y - bounds.Position.y) / zoom
5217
+ },
5218
+ Size: {
5219
+ x: box.Size.x / zoom,
5220
+ y: box.Size.y / zoom
5221
+ }
5222
+ });
5223
+ }
5224
+ };
5225
+ record(this.#inputPortPositions, this.#portOffsets.input);
5226
+ record(this.#outputPortPositions, this.#portOffsets.output);
5227
+ }
5228
+ layoutPortsWithoutDrawing(bounds, zoom) {
5229
+ if (this.#portOffsets.input.length === 0 && this.#portOffsets.output.length === 0) {
5230
+ return false;
5231
+ }
5232
+ const project = (offsets, into) => {
5233
+ into.Clear();
5234
+ for (let i = 0; i < offsets.length; i++) {
5235
+ const offset = offsets[i];
5236
+ into.Push({
5237
+ Position: {
5238
+ x: bounds.Position.x + offset.Position.x * zoom,
5239
+ y: bounds.Position.y + offset.Position.y * zoom
5240
+ },
5241
+ Size: {
5242
+ x: offset.Size.x * zoom,
5243
+ y: offset.Size.y * zoom
5244
+ }
5245
+ });
5246
+ }
5247
+ };
5248
+ project(this.#portOffsets.input, this.#inputPortPositions);
5249
+ project(this.#portOffsets.output, this.#outputPortPositions);
5250
+ return true;
5251
+ }
4948
5252
  };
4949
5253
 
4950
5254
  // src/markdown/token.ts
@@ -5085,7 +5389,10 @@ function onFontsLoaded(callback) {
5085
5389
  if (typeof document === "undefined" || document.fonts === void 0) {
5086
5390
  return;
5087
5391
  }
5088
- document.fonts.addEventListener("loadingdone", () => callback());
5392
+ document.fonts.addEventListener("loadingdone", () => {
5393
+ callback();
5394
+ requestRender();
5395
+ });
5089
5396
  }
5090
5397
  var CodeBlockEntry = class {
5091
5398
  #text;
@@ -5835,6 +6142,7 @@ var NoteSubsystem = class {
5835
6142
  return;
5836
6143
  }
5837
6144
  this.#notes.push(note);
6145
+ requestRender();
5838
6146
  for (let i = 0; i < this.#onNoteAddedCallbacks.length; i++) {
5839
6147
  this.#onNoteAddedCallbacks[i](note);
5840
6148
  }
@@ -5957,6 +6265,7 @@ var NoteSubsystem = class {
5957
6265
  if (index > -1) {
5958
6266
  const noteRemoved = this.#notes[index];
5959
6267
  this.#notes.splice(index, 1);
6268
+ requestRender();
5960
6269
  for (let i = 0; i < this.#onNoteRemovedCallbacks.length; i++) {
5961
6270
  this.#onNoteRemovedCallbacks[i](noteRemoved);
5962
6271
  }
@@ -6293,6 +6602,124 @@ var QuickMenu = class {
6293
6602
  }
6294
6603
  };
6295
6604
 
6605
+ // src/minimap.ts
6606
+ var Minimap = class {
6607
+ #enabled;
6608
+ #width;
6609
+ #height;
6610
+ #margin;
6611
+ #backgroundColor;
6612
+ #borderColor;
6613
+ #nodeColor;
6614
+ #viewportColor;
6615
+ #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
6616
+ #scale = 1;
6617
+ #originX = 0;
6618
+ #originY = 0;
6619
+ // The padding the last frame was laid out with, so a click maps back
6620
+ // through the same geometry it was drawn with.
6621
+ #padding = Theme.Minimap.Padding;
6622
+ #drawn = false;
6623
+ constructor(config) {
6624
+ this.#enabled = config?.enabled === true;
6625
+ this.#width = config?.width === void 0 ? 200 : config.width;
6626
+ this.#height = config?.height === void 0 ? 150 : config.height;
6627
+ this.#margin = config?.margin === void 0 ? 16 : config.margin;
6628
+ this.#backgroundColor = config?.backgroundColor;
6629
+ this.#borderColor = config?.borderColor;
6630
+ this.#nodeColor = config?.nodeColor;
6631
+ this.#viewportColor = config?.viewportColor;
6632
+ }
6633
+ enabled() {
6634
+ return this.#enabled;
6635
+ }
6636
+ setEnabled(enabled) {
6637
+ this.#enabled = enabled;
6638
+ this.#drawn = false;
6639
+ }
6640
+ contains(position) {
6641
+ return this.#enabled && this.#drawn && InBox(this.#box, position);
6642
+ }
6643
+ graphPositionOf(position, out) {
6644
+ out.x = (position.x - this.#box.Position.x - this.#padding) / this.#scale + this.#originX;
6645
+ out.y = (position.y - this.#box.Position.y - this.#padding) / this.#scale + this.#originY;
6646
+ }
6647
+ render(ctx, canvas, camera, nodes) {
6648
+ if (!this.#enabled) {
6649
+ this.#drawn = false;
6650
+ return;
6651
+ }
6652
+ const flat = new Camera();
6653
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
6654
+ const boxes = [];
6655
+ for (let i = 0; i < nodes.length; i++) {
6656
+ const bounds = nodes[i].calculateBounds(ctx, flat);
6657
+ boxes.push(bounds);
6658
+ minX = Math.min(minX, bounds.Position.x);
6659
+ minY = Math.min(minY, bounds.Position.y);
6660
+ maxX = Math.max(maxX, bounds.Position.x + bounds.Size.x);
6661
+ maxY = Math.max(maxY, bounds.Position.y + bounds.Size.y);
6662
+ }
6663
+ const topLeft = { x: 0, y: 0 };
6664
+ const bottomRight = { x: 0, y: 0 };
6665
+ camera.screenSpaceToGraphSpace({ x: 0, y: 0 }, topLeft);
6666
+ camera.screenSpaceToGraphSpace({ x: canvas.width, y: canvas.height }, bottomRight);
6667
+ minX = Math.min(minX, topLeft.x);
6668
+ minY = Math.min(minY, topLeft.y);
6669
+ maxX = Math.max(maxX, bottomRight.x);
6670
+ maxY = Math.max(maxY, bottomRight.y);
6671
+ if (!isFinite(minX) || maxX <= minX || maxY <= minY) {
6672
+ this.#drawn = false;
6673
+ return;
6674
+ }
6675
+ this.#box.Size.x = this.#width;
6676
+ this.#box.Size.y = this.#height;
6677
+ this.#box.Position.x = canvas.width - this.#width - this.#margin;
6678
+ this.#box.Position.y = canvas.height - this.#height - this.#margin;
6679
+ this.#padding = Theme.Minimap.Padding;
6680
+ const usableWidth = this.#width - this.#padding * 2;
6681
+ const usableHeight = this.#height - this.#padding * 2;
6682
+ this.#scale = Math.min(usableWidth / (maxX - minX), usableHeight / (maxY - minY));
6683
+ this.#originX = minX;
6684
+ this.#originY = minY;
6685
+ const left = this.#box.Position.x + this.#padding;
6686
+ const top = this.#box.Position.y + this.#padding;
6687
+ const toMapX = (x) => left + (x - minX) * this.#scale;
6688
+ const toMapY = (y) => top + (y - minY) * this.#scale;
6689
+ ctx.fillStyle = this.#backgroundColor ?? Theme.Minimap.BackgroundColor;
6690
+ ctx.strokeStyle = this.#borderColor ?? Theme.Minimap.BorderColor;
6691
+ ctx.lineWidth = 1;
6692
+ ctx.beginPath();
6693
+ ctx.roundRect(
6694
+ this.#box.Position.x,
6695
+ this.#box.Position.y,
6696
+ this.#width,
6697
+ this.#height,
6698
+ Theme.Minimap.BorderRadius
6699
+ );
6700
+ ctx.fill();
6701
+ ctx.stroke();
6702
+ ctx.fillStyle = this.#nodeColor ?? Theme.Minimap.NodeColor;
6703
+ for (let i = 0; i < boxes.length; i++) {
6704
+ const b = boxes[i];
6705
+ ctx.fillRect(
6706
+ toMapX(b.Position.x),
6707
+ toMapY(b.Position.y),
6708
+ Math.max(1, b.Size.x * this.#scale),
6709
+ Math.max(1, b.Size.y * this.#scale)
6710
+ );
6711
+ }
6712
+ ctx.fillStyle = this.#viewportColor ?? Theme.Minimap.ViewportColor;
6713
+ ctx.fillRect(
6714
+ toMapX(topLeft.x),
6715
+ toMapY(topLeft.y),
6716
+ Math.max(1, (bottomRight.x - topLeft.x) * this.#scale),
6717
+ Math.max(1, (bottomRight.y - topLeft.y) * this.#scale)
6718
+ );
6719
+ this.#drawn = true;
6720
+ }
6721
+ };
6722
+
6296
6723
  // src/graph.ts
6297
6724
  function BuildBackgroundRenderer(backgroundColor) {
6298
6725
  return (canvas, context, position, scale) => {
@@ -6430,7 +6857,13 @@ var NodeFlowGraph = class {
6430
6857
  const backgroundColor = config?.backgroundColor === void 0 ? Theme.Graph.BackgroundColor : config.backgroundColor;
6431
6858
  this.#backgroundRenderer = BuildBackgroundRenderer(backgroundColor);
6432
6859
  }
6433
- window.requestAnimationFrame(this.#render.bind(this));
6860
+ this.#minimap = new Minimap(config?.minimap);
6861
+ this.#mainNodeSubsystem.setConnectionReleasedHandler((dangling) => {
6862
+ this.#openScopedMenu(dangling);
6863
+ });
6864
+ this.#watchCanvasSize();
6865
+ this.#stopRendering = onRenderRequested(this.#render.bind(this));
6866
+ requestRender();
6434
6867
  new MouseObserver(
6435
6868
  this.#canvas,
6436
6869
  this.#mouseDragEvent.bind(this),
@@ -6448,39 +6881,90 @@ var NodeFlowGraph = class {
6448
6881
  document.addEventListener(
6449
6882
  "keydown",
6450
6883
  (e) => {
6451
- if (document.activeElement) {
6452
- if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
6453
- }
6454
- if (this.#openQuickMenu) {
6455
- if (e.code === "Escape") {
6456
- this.#openQuickMenu = null;
6457
- return;
6458
- }
6459
- if (e.code === "Enter") {
6460
- this.#openQuickMenu.Menu.execute();
6461
- this.#openQuickMenu = null;
6462
- return;
6463
- }
6464
- this.#openQuickMenu.Menu.keyboardEvent(e);
6884
+ this.#keyDown(e);
6885
+ requestRender();
6886
+ }
6887
+ );
6888
+ }
6889
+ #copyCallbacks = [];
6890
+ #pasteCallbacks = [];
6891
+ addCopyListener(callback) {
6892
+ this.#copyCallbacks.push(callback);
6893
+ }
6894
+ addPasteListener(callback) {
6895
+ this.#pasteCallbacks.push(callback);
6896
+ }
6897
+ #raiseCopy() {
6898
+ const nodes = this.#mainNodeSubsystem.getSelectedNodes();
6899
+ if (nodes.length === 0) {
6900
+ return;
6901
+ }
6902
+ const connections = this.#mainNodeSubsystem.connectionsWithin(nodes);
6903
+ this.#copyCallbacks.forEach((callback) => callback(nodes, connections));
6904
+ }
6905
+ #raisePaste() {
6906
+ const position = this.#mousePosition === void 0 ? { x: 0, y: 0 } : this.#sceenPositionToGraphPosition(this.#mousePosition);
6907
+ this.#pasteCallbacks.forEach((callback) => callback(position));
6908
+ }
6909
+ #deleteKey(e) {
6910
+ return e.code === "Delete" || e.code === "Backspace" || e.key === "Delete" || e.key === "Backspace";
6911
+ }
6912
+ #keyDown(e) {
6913
+ if (document.activeElement) {
6914
+ if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
6915
+ }
6916
+ if (this.#deleteKey(e)) {
6917
+ if (this.#mainNodeSubsystem.getSelectedNodes().length > 0) {
6918
+ e.preventDefault();
6919
+ this.#mainNodeSubsystem.removeSelectedNodes();
6920
+ return;
6921
+ }
6922
+ }
6923
+ if (e.ctrlKey || e.metaKey) {
6924
+ const letter = e.code.startsWith("Key") ? e.code.substring(3).toLowerCase() : (e.key ?? "").toLowerCase();
6925
+ switch (letter) {
6926
+ case "c":
6927
+ this.#raiseCopy();
6465
6928
  return;
6466
- }
6467
- const spacePressed = e.key == " " || e.code == "Space";
6468
- if (!spacePressed) {
6929
+ case "v":
6930
+ this.#raisePaste();
6469
6931
  return;
6470
- }
6471
- if (!this.#mousePosition) {
6932
+ // duplicate
6933
+ case "d":
6934
+ e.preventDefault();
6935
+ this.#raiseCopy();
6936
+ this.#raisePaste();
6472
6937
  return;
6473
- }
6474
- const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
6475
- let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
6476
- this.#openQuickMenu = {
6477
- Menu: new QuickMenu({
6478
- subMenus: items
6479
- }),
6480
- Position: contextMenuPosition
6481
- };
6482
6938
  }
6483
- );
6939
+ }
6940
+ if (this.#openQuickMenu) {
6941
+ if (e.code === "Escape") {
6942
+ this.#openQuickMenu = null;
6943
+ return;
6944
+ }
6945
+ if (e.code === "Enter") {
6946
+ this.#openQuickMenu.Menu.execute();
6947
+ this.#openQuickMenu = null;
6948
+ return;
6949
+ }
6950
+ this.#openQuickMenu.Menu.keyboardEvent(e);
6951
+ return;
6952
+ }
6953
+ const spacePressed = e.key == " " || e.code == "Space";
6954
+ if (!spacePressed) {
6955
+ return;
6956
+ }
6957
+ if (!this.#mousePosition) {
6958
+ return;
6959
+ }
6960
+ const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
6961
+ let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
6962
+ this.#openQuickMenu = {
6963
+ Menu: new QuickMenu({
6964
+ subMenus: items
6965
+ }),
6966
+ Position: contextMenuPosition
6967
+ };
6484
6968
  }
6485
6969
  #fileDrop(file) {
6486
6970
  if (this.currentView().fileDrop(file)) {
@@ -6546,8 +7030,25 @@ var NodeFlowGraph = class {
6546
7030
  this.#openQuickMenu = null;
6547
7031
  this.#contextMenuEntryHovering = null;
6548
7032
  this.#mousePosition = mousePosition;
7033
+ if (this.#minimap.contains(mousePosition)) {
7034
+ this.#lookAtFromMinimap(mousePosition);
7035
+ this.#draggingMinimap = true;
7036
+ return;
7037
+ }
6549
7038
  this.currentView().clickStart(mousePosition, this.#camera, ctrlKey);
6550
7039
  }
7040
+ #draggingMinimap = false;
7041
+ #lookAtFromMinimap(mousePosition) {
7042
+ const target = { x: 0, y: 0 };
7043
+ this.#minimap.graphPositionOf(mousePosition, target);
7044
+ this.#camera.position.x = -(target.x * this.#camera.zoom) + this.#canvas.width / 2;
7045
+ this.#camera.position.y = -(target.y * this.#camera.zoom) + this.#canvas.height / 2;
7046
+ requestRender();
7047
+ }
7048
+ /** The minimap, so it can be turned on and sized by the host. */
7049
+ minimap() {
7050
+ return this.#minimap;
7051
+ }
6551
7052
  currentView() {
6552
7053
  return this.#views[this.#currentView];
6553
7054
  }
@@ -6567,6 +7068,12 @@ var NodeFlowGraph = class {
6567
7068
  getSelectedNodes() {
6568
7069
  return this.#mainNodeSubsystem.getSelectedNodes();
6569
7070
  }
7071
+ unselectAllNodes() {
7072
+ this.#mainNodeSubsystem.unselectAllNodes();
7073
+ }
7074
+ removeSelectedNodes() {
7075
+ return this.#mainNodeSubsystem.removeSelectedNodes();
7076
+ }
6570
7077
  /**
6571
7078
  * Returns a copy of the current camera orientation (screen-space pan + zoom).
6572
7079
  */
@@ -6682,10 +7189,37 @@ var NodeFlowGraph = class {
6682
7189
  Position: contextMenuPosition
6683
7190
  };
6684
7191
  }
7192
+ #openScopedMenu(dangling) {
7193
+ if (this.#mousePosition === void 0) {
7194
+ return;
7195
+ }
7196
+ const position = this.#sceenPositionToGraphPosition(this.#mousePosition);
7197
+ const menu = this.#mainNodeSubsystem.nodeFactory().openMenu(
7198
+ this.#mainNodeSubsystem,
7199
+ position,
7200
+ {
7201
+ needs: dangling.needs,
7202
+ types: dangling.types,
7203
+ onCreated: dangling.connectTo
7204
+ }
7205
+ );
7206
+ this.#openedContextMenu = {
7207
+ Menu: new ContextMenu(menu),
7208
+ Position: position
7209
+ };
7210
+ requestRender();
7211
+ }
6685
7212
  #clickEnd() {
7213
+ this.#draggingMinimap = false;
6686
7214
  this.currentView().clickEnd();
6687
7215
  }
6688
7216
  #mouseDragEvent(delta) {
7217
+ if (this.#draggingMinimap) {
7218
+ if (this.#mousePosition !== void 0) {
7219
+ this.#lookAtFromMinimap(this.#mousePosition);
7220
+ }
7221
+ return;
7222
+ }
6689
7223
  let draggingSomething = this.currentView().mouseDragEvent(delta, this.#camera.zoom);
6690
7224
  if (!draggingSomething) {
6691
7225
  this.#camera.position.x += delta.x;
@@ -6694,12 +7228,45 @@ var NodeFlowGraph = class {
6694
7228
  }
6695
7229
  #lastFrameCursor;
6696
7230
  #cursor;
6697
- #render() {
6698
- if (this.#canvas.parentNode !== null) {
6699
- var rect = this.#canvas.parentNode.getBoundingClientRect();
6700
- this.#canvas.width = rect.width;
6701
- this.#canvas.height = rect.height;
7231
+ #minimap;
7232
+ #stopRendering;
7233
+ #sizeObserver;
7234
+ #matchCanvasToParent() {
7235
+ const parent = this.#canvas.parentNode;
7236
+ if (parent === null) {
7237
+ return;
6702
7238
  }
7239
+ const rect = parent.getBoundingClientRect();
7240
+ if (this.#canvas.width === rect.width && this.#canvas.height === rect.height) {
7241
+ return;
7242
+ }
7243
+ this.#canvas.width = rect.width;
7244
+ this.#canvas.height = rect.height;
7245
+ }
7246
+ #watchCanvasSize() {
7247
+ this.#matchCanvasToParent();
7248
+ const parent = this.#canvas.parentNode;
7249
+ if (parent === null || typeof ResizeObserver === "undefined") {
7250
+ return;
7251
+ }
7252
+ this.#sizeObserver = new ResizeObserver(() => {
7253
+ this.#matchCanvasToParent();
7254
+ requestRender();
7255
+ });
7256
+ this.#sizeObserver.observe(parent);
7257
+ }
7258
+ requestRender() {
7259
+ requestRender();
7260
+ }
7261
+ renderContinuously() {
7262
+ return renderContinuously();
7263
+ }
7264
+ dispose() {
7265
+ this.#stopRendering();
7266
+ this.#sizeObserver?.disconnect();
7267
+ }
7268
+ #render() {
7269
+ this.#matchCanvasToParent();
6703
7270
  this.#cursor = "default" /* Default */;
6704
7271
  TimeExecution("Render_Background", this.#renderBackground.bind(this));
6705
7272
  TimeExecution("Render_View_" + this.#currentView, () => {
@@ -6708,13 +7275,15 @@ var NodeFlowGraph = class {
6708
7275
  this.#cursor = results?.cursorStyle;
6709
7276
  }
6710
7277
  });
7278
+ TimeExecution("Render_Minimap", () => {
7279
+ this.#minimap.render(this.#ctx, this.#canvas, this.#camera, this.#mainNodeSubsystem.getNodes());
7280
+ });
6711
7281
  TimeExecution("Render_Context", this.#renderContextMenu.bind(this));
6712
7282
  TimeExecution("Render_QuickMenu", this.#renderQuickMenu.bind(this));
6713
7283
  if (this.#lastFrameCursor !== this.#cursor) {
6714
7284
  this.#canvas.style.cursor = this.#cursor;
6715
7285
  }
6716
7286
  this.#lastFrameCursor = this.#cursor;
6717
- window.requestAnimationFrame(this.#render.bind(this));
6718
7287
  }
6719
7288
  #renderBackground() {
6720
7289
  this.#backgroundRenderer(this.#canvas, this.#ctx, this.#camera.position, this.#camera.zoom);
@@ -6756,6 +7325,7 @@ var NodeFlowGraph = class {
6756
7325
  GlobalWidgetFactory,
6757
7326
  ImageWidget,
6758
7327
  MessageType,
7328
+ Minimap,
6759
7329
  NodeFactory,
6760
7330
  NodeFlowGraph,
6761
7331
  NodeState,
@@ -6769,6 +7339,10 @@ var NodeFlowGraph = class {
6769
7339
  Theme,
6770
7340
  ToggleWidget,
6771
7341
  WidgetFactory,
6772
- portsCompatible
7342
+ portConfigTypes,
7343
+ portsCompatible,
7344
+ renderContinuously,
7345
+ requestRender,
7346
+ typeSetsCompatible
6773
7347
  });
6774
7348
  //# sourceMappingURL=index.js.map