@elicdavis/node-flow 0.2.0 → 0.2.2

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
@@ -735,7 +735,7 @@ var ContextMenu = class _ContextMenu {
735
735
  // src/input.ts
736
736
  var LONG_PRESS_MS = 500;
737
737
  var LONG_PRESS_SLOP = 12;
738
- var PINCH_SENSITIVITY = 50;
738
+ var WHEEL_ZOOM_PER_PIXEL = 5e-4;
739
739
  var SUPPRESS_MOUSE_MS = 600;
740
740
  var MouseObserver = class {
741
741
  #clicked;
@@ -833,13 +833,12 @@ var MouseObserver = class {
833
833
  } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
834
834
  dy *= this.#ele.clientHeight;
835
835
  }
836
- const amount = -dy / 100;
837
- if (amount === 0) {
836
+ if (dy === 0) {
838
837
  return;
839
838
  }
840
839
  const anchor = this.#mousePos(event);
841
840
  this.#moveCallback(anchor);
842
- this.#zoomCallback(amount, anchor);
841
+ this.#zoomCallback(Math.exp(-dy * WHEEL_ZOOM_PER_PIXEL), anchor);
843
842
  }
844
843
  #move(event) {
845
844
  if (this.#fromTouch()) {
@@ -915,7 +914,7 @@ var MouseObserver = class {
915
914
  const anchor = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
916
915
  if (this.#pinchDist > 0) {
917
916
  this.#moveCallback(anchor);
918
- this.#zoomCallback((dist - this.#pinchDist) / PINCH_SENSITIVITY, anchor);
917
+ this.#zoomCallback(dist / this.#pinchDist, anchor);
919
918
  }
920
919
  this.#pinchDist = dist;
921
920
  return;
@@ -1765,9 +1764,13 @@ var Port = class {
1765
1764
  }
1766
1765
  }
1767
1766
  /**
1768
- * A port asked to hide still shows while anything is connected to it.
1767
+ * A port asked to hide still shows while anything is connected to it,
1768
+ * and while its node is showing its hidden ports.
1769
1769
  */
1770
1770
  hidden() {
1771
+ return this.tucked() && !(this.#node?.hiddenPortsShown() ?? false);
1772
+ }
1773
+ tucked() {
1771
1774
  return this.#hidden && this.#connections.length === 0;
1772
1775
  }
1773
1776
  addConnectionRemovedListener(callback) {
@@ -1869,6 +1872,281 @@ var Port = class {
1869
1872
  }
1870
1873
  };
1871
1874
 
1875
+ // src/styles/cursor.ts
1876
+ var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
1877
+ CursorStyle2["Default"] = "default";
1878
+ CursorStyle2["Pointer"] = "pointer";
1879
+ CursorStyle2["Grab"] = "grab";
1880
+ CursorStyle2["Grabbing"] = "grabbing";
1881
+ CursorStyle2["Wait"] = "wait";
1882
+ CursorStyle2["Help"] = "help";
1883
+ CursorStyle2["Crosshair"] = "crosshair";
1884
+ CursorStyle2["Cell"] = "cell";
1885
+ CursorStyle2["Text"] = "text";
1886
+ CursorStyle2["Move"] = "move";
1887
+ CursorStyle2["NotAllowed"] = "not-allowed";
1888
+ CursorStyle2["ZoomIn"] = "zoom-in";
1889
+ CursorStyle2["ZoomOut"] = "zoom-out";
1890
+ CursorStyle2["None"] = "none";
1891
+ CursorStyle2["ContextMenu"] = "context-menu";
1892
+ CursorStyle2["EWResize"] = "ew-resize";
1893
+ return CursorStyle2;
1894
+ })(CursorStyle || {});
1895
+
1896
+ // src/widgets/inplace.ts
1897
+ var open = /* @__PURE__ */ new Set();
1898
+ function placeEditsDuring(canvas, draw) {
1899
+ open.forEach((edit) => {
1900
+ if (edit.canvas === canvas) {
1901
+ edit.placed = false;
1902
+ }
1903
+ });
1904
+ draw();
1905
+ open.forEach((edit) => {
1906
+ if (edit.canvas === canvas && !edit.placed) {
1907
+ edit.input.style.opacity = "0";
1908
+ edit.input.style.pointerEvents = "none";
1909
+ }
1910
+ });
1911
+ }
1912
+ function editInPlace(config) {
1913
+ const canvas = config.canvas;
1914
+ if (canvas === null) {
1915
+ return null;
1916
+ }
1917
+ const input = document.createElement("input");
1918
+ input.type = "text";
1919
+ input.value = config.value;
1920
+ const edit = { canvas, input, placed: true };
1921
+ const place = (box, scale) => {
1922
+ const rect = canvas.getBoundingClientRect();
1923
+ const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
1924
+ const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
1925
+ const left = rect.left + box.Position.x * sx;
1926
+ const top = rect.top + box.Position.y * sy;
1927
+ const width2 = box.Size.x * sx;
1928
+ const height2 = box.Size.y * sy;
1929
+ const clip = [
1930
+ Math.max(0, rect.top - top),
1931
+ Math.max(0, left + width2 - (rect.left + rect.width)),
1932
+ Math.max(0, top + height2 - (rect.top + rect.height)),
1933
+ Math.max(0, rect.left - left)
1934
+ ];
1935
+ edit.placed = true;
1936
+ input.style.cssText = [
1937
+ "position: fixed",
1938
+ `left: ${left}px`,
1939
+ `top: ${top}px`,
1940
+ `width: ${width2}px`,
1941
+ `height: ${height2}px`,
1942
+ `clip-path: inset(${clip.map((c) => `${c}px`).join(" ")})`,
1943
+ "box-sizing: border-box",
1944
+ "margin: 0",
1945
+ `padding: 0 ${Theme.Widget.Padding * scale}px`,
1946
+ `font: ${Theme.Widget.FontSize * scale * sy}px ${Theme.FontFamily}`,
1947
+ `color: ${Theme.Widget.FontColor}`,
1948
+ `background: ${Theme.Widget.Hover.BackgroundColor}`,
1949
+ `border: ${Theme.Widget.Border.Size * scale}px solid ${Theme.Widget.Border.Color}`,
1950
+ `border-radius: ${Theme.Widget.Border.Radius * scale}px`,
1951
+ "outline: none",
1952
+ "z-index: 1000"
1953
+ ].join(";");
1954
+ };
1955
+ place(config.box, config.scale);
1956
+ let done = false;
1957
+ const finish = (keep) => {
1958
+ if (done) {
1959
+ return;
1960
+ }
1961
+ done = true;
1962
+ open.delete(edit);
1963
+ input.remove();
1964
+ if (keep) {
1965
+ config.onCommit(input.value);
1966
+ }
1967
+ requestRender();
1968
+ };
1969
+ input.addEventListener("keydown", (event) => {
1970
+ event.stopPropagation();
1971
+ if (event.key === "Enter") {
1972
+ event.preventDefault();
1973
+ finish(true);
1974
+ } else if (event.key === "Escape") {
1975
+ event.preventDefault();
1976
+ finish(false);
1977
+ }
1978
+ });
1979
+ input.addEventListener("blur", () => finish(true));
1980
+ open.add(edit);
1981
+ document.body.appendChild(input);
1982
+ input.focus();
1983
+ input.select();
1984
+ return { input, place };
1985
+ }
1986
+
1987
+ // src/widgets/widget.ts
1988
+ var width = 150;
1989
+ var height = 25;
1990
+ var WidgetBase = class {
1991
+ #hidden = false;
1992
+ #disabled = false;
1993
+ #hovered = false;
1994
+ #pressed = false;
1995
+ #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
1996
+ #canvas = null;
1997
+ #scale = 1;
1998
+ #editing = null;
1999
+ Draw(ctx, box, scale, mousePosition) {
2000
+ this.#box = { Position: { x: box.Position.x, y: box.Position.y }, Size: { x: box.Size.x, y: box.Size.y } };
2001
+ this.#canvas = ctx.canvas ?? null;
2002
+ this.#scale = scale;
2003
+ this.#hovered = !this.#disabled && mousePosition !== void 0 && InBox(box, mousePosition);
2004
+ if (this.#editing !== null) {
2005
+ if (this.#editing.input.isConnected) {
2006
+ this.#editing.place(this.#box, scale);
2007
+ } else {
2008
+ this.#editing = null;
2009
+ }
2010
+ }
2011
+ ctx.save();
2012
+ if (this.#disabled) {
2013
+ ctx.globalAlpha *= 0.4;
2014
+ }
2015
+ this.render(ctx, box, scale);
2016
+ ctx.restore();
2017
+ }
2018
+ PointerDown(pointer) {
2019
+ if (this.#disabled) {
2020
+ return;
2021
+ }
2022
+ this.#pressed = true;
2023
+ this.onPointerDown(pointer);
2024
+ requestRender();
2025
+ }
2026
+ PointerMove(pointer) {
2027
+ if (!this.#pressed) {
2028
+ return;
2029
+ }
2030
+ this.onPointerMove(pointer);
2031
+ requestRender();
2032
+ }
2033
+ PointerUp(pointer) {
2034
+ if (!this.#pressed) {
2035
+ return;
2036
+ }
2037
+ this.#pressed = false;
2038
+ this.onPointerUp(pointer);
2039
+ if (pointer.inside) {
2040
+ this.onClick(pointer);
2041
+ }
2042
+ requestRender();
2043
+ }
2044
+ Cursor() {
2045
+ return "pointer" /* Pointer */;
2046
+ }
2047
+ Hidden() {
2048
+ return this.#hidden;
2049
+ }
2050
+ setHidden(hidden) {
2051
+ this.#hidden = hidden;
2052
+ requestRender();
2053
+ }
2054
+ Disabled() {
2055
+ return this.#disabled;
2056
+ }
2057
+ setDisabled(disabled) {
2058
+ this.#disabled = disabled;
2059
+ if (disabled) {
2060
+ this.#pressed = false;
2061
+ }
2062
+ requestRender();
2063
+ }
2064
+ hovered() {
2065
+ return this.#hovered;
2066
+ }
2067
+ pressed() {
2068
+ return this.#pressed;
2069
+ }
2070
+ /**
2071
+ * Where the widget was last drawn, in canvas pixels.
2072
+ */
2073
+ box() {
2074
+ return this.#box;
2075
+ }
2076
+ canvas() {
2077
+ return this.#canvas;
2078
+ }
2079
+ scale() {
2080
+ return this.#scale;
2081
+ }
2082
+ editText(value, onCommit) {
2083
+ this.#editing = editInPlace({
2084
+ canvas: this.#canvas,
2085
+ box: this.#box,
2086
+ scale: this.#scale,
2087
+ value,
2088
+ onCommit
2089
+ });
2090
+ }
2091
+ onPointerDown(pointer) {
2092
+ }
2093
+ onPointerMove(pointer) {
2094
+ }
2095
+ onPointerUp(pointer) {
2096
+ }
2097
+ /**
2098
+ * A press and release that both landed on the widget.
2099
+ */
2100
+ onClick(pointer) {
2101
+ }
2102
+ };
2103
+ function startingWidgetValue(node, property, configured, fallback) {
2104
+ if (configured !== void 0) {
2105
+ return configured;
2106
+ }
2107
+ if (property !== void 0 && property !== null) {
2108
+ const existing = node.getProperty(property);
2109
+ if (existing !== void 0 && existing !== null) {
2110
+ return existing;
2111
+ }
2112
+ }
2113
+ return fallback;
2114
+ }
2115
+
2116
+ // src/widgets/hidden_ports.ts
2117
+ var HiddenPortsToggle = class extends WidgetBase {
2118
+ constructor(owner) {
2119
+ super();
2120
+ this.owner = owner;
2121
+ }
2122
+ Size() {
2123
+ return { x: 150, y: 16 };
2124
+ }
2125
+ render(ctx, box, scale) {
2126
+ const label = this.owner.hiddenPortsShown() ? "\u25B4 less" : `\u25BE ${this.owner.hiddenPortCount()} more`;
2127
+ const midY = box.Position.y + box.Size.y / 2;
2128
+ const centerX = box.Position.x + box.Size.x / 2;
2129
+ ctx.font = `${11 * scale}px ${Theme.FontFamily}`;
2130
+ const half = ctx.measureText(label).width / 2 + 6 * scale;
2131
+ ctx.globalAlpha = this.hovered() ? 1 : 0.5;
2132
+ ctx.strokeStyle = Theme.Widget.FontColor;
2133
+ ctx.fillStyle = Theme.Widget.FontColor;
2134
+ ctx.lineWidth = Math.max(1, scale);
2135
+ ctx.beginPath();
2136
+ ctx.moveTo(box.Position.x, midY);
2137
+ ctx.lineTo(centerX - half, midY);
2138
+ ctx.moveTo(centerX + half, midY);
2139
+ ctx.lineTo(box.Position.x + box.Size.x, midY);
2140
+ ctx.stroke();
2141
+ ctx.textAlign = "center";
2142
+ ctx.textBaseline = "middle";
2143
+ ctx.fillText(label, centerX, midY);
2144
+ }
2145
+ onClick() {
2146
+ this.owner.setHiddenPortsShown(!this.owner.hiddenPortsShown());
2147
+ }
2148
+ };
2149
+
1872
2150
  // src/styles/stroke.ts
1873
2151
  function StrokeStyleWithFallback(input, fallback) {
1874
2152
  return {
@@ -1980,27 +2258,6 @@ var BoxStyle = class {
1980
2258
  }
1981
2259
  };
1982
2260
 
1983
- // src/styles/cursor.ts
1984
- var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
1985
- CursorStyle2["Default"] = "default";
1986
- CursorStyle2["Pointer"] = "pointer";
1987
- CursorStyle2["Grab"] = "grab";
1988
- CursorStyle2["Grabbing"] = "grabbing";
1989
- CursorStyle2["Wait"] = "wait";
1990
- CursorStyle2["Help"] = "help";
1991
- CursorStyle2["Crosshair"] = "crosshair";
1992
- CursorStyle2["Cell"] = "cell";
1993
- CursorStyle2["Text"] = "text";
1994
- CursorStyle2["Move"] = "move";
1995
- CursorStyle2["NotAllowed"] = "not-allowed";
1996
- CursorStyle2["ZoomIn"] = "zoom-in";
1997
- CursorStyle2["ZoomOut"] = "zoom-out";
1998
- CursorStyle2["None"] = "none";
1999
- CursorStyle2["ContextMenu"] = "context-menu";
2000
- CursorStyle2["EWResize"] = "ew-resize";
2001
- return CursorStyle2;
2002
- })(CursorStyle || {});
2003
-
2004
2261
  // src/styles/bevel.ts
2005
2262
  function drawBevel(ctx, box, scale, colors, sunken) {
2006
2263
  const width2 = Math.max(1, 2 * scale);
@@ -2092,176 +2349,6 @@ function drawFieldText(ctx, box, scale, look) {
2092
2349
  );
2093
2350
  }
2094
2351
 
2095
- // src/widgets/inplace.ts
2096
- function editInPlace(config) {
2097
- const canvas = config.canvas;
2098
- if (canvas === null) {
2099
- return null;
2100
- }
2101
- const rect = canvas.getBoundingClientRect();
2102
- const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
2103
- const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
2104
- const input = document.createElement("input");
2105
- input.type = "text";
2106
- input.value = config.value;
2107
- input.style.cssText = [
2108
- "position: fixed",
2109
- `left: ${rect.left + config.box.Position.x * sx}px`,
2110
- `top: ${rect.top + config.box.Position.y * sy}px`,
2111
- `width: ${config.box.Size.x * sx}px`,
2112
- `height: ${config.box.Size.y * sy}px`,
2113
- "box-sizing: border-box",
2114
- "margin: 0",
2115
- `padding: 0 ${Theme.Widget.Padding * config.scale}px`,
2116
- `font: ${Theme.Widget.FontSize * config.scale * sy}px ${Theme.FontFamily}`,
2117
- `color: ${Theme.Widget.FontColor}`,
2118
- `background: ${Theme.Widget.Hover.BackgroundColor}`,
2119
- `border: ${Theme.Widget.Border.Size * config.scale}px solid ${Theme.Widget.Border.Color}`,
2120
- `border-radius: ${Theme.Widget.Border.Radius * config.scale}px`,
2121
- "outline: none",
2122
- "z-index: 1000"
2123
- ].join(";");
2124
- let done = false;
2125
- const finish = (keep) => {
2126
- if (done) {
2127
- return;
2128
- }
2129
- done = true;
2130
- input.remove();
2131
- if (keep) {
2132
- config.onCommit(input.value);
2133
- }
2134
- requestRender();
2135
- };
2136
- input.addEventListener("keydown", (event) => {
2137
- event.stopPropagation();
2138
- if (event.key === "Enter") {
2139
- event.preventDefault();
2140
- finish(true);
2141
- } else if (event.key === "Escape") {
2142
- event.preventDefault();
2143
- finish(false);
2144
- }
2145
- });
2146
- input.addEventListener("blur", () => finish(true));
2147
- document.body.appendChild(input);
2148
- input.focus();
2149
- input.select();
2150
- return input;
2151
- }
2152
-
2153
- // src/widgets/widget.ts
2154
- var width = 150;
2155
- var height = 25;
2156
- var WidgetBase = class {
2157
- #hidden = false;
2158
- #disabled = false;
2159
- #hovered = false;
2160
- #pressed = false;
2161
- #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
2162
- #canvas = null;
2163
- #scale = 1;
2164
- Draw(ctx, box, scale, mousePosition) {
2165
- this.#box = { Position: { x: box.Position.x, y: box.Position.y }, Size: { x: box.Size.x, y: box.Size.y } };
2166
- this.#canvas = ctx.canvas ?? null;
2167
- this.#scale = scale;
2168
- this.#hovered = !this.#disabled && mousePosition !== void 0 && InBox(box, mousePosition);
2169
- ctx.save();
2170
- if (this.#disabled) {
2171
- ctx.globalAlpha *= 0.4;
2172
- }
2173
- this.render(ctx, box, scale);
2174
- ctx.restore();
2175
- }
2176
- PointerDown(pointer) {
2177
- if (this.#disabled) {
2178
- return;
2179
- }
2180
- this.#pressed = true;
2181
- this.onPointerDown(pointer);
2182
- requestRender();
2183
- }
2184
- PointerMove(pointer) {
2185
- if (!this.#pressed) {
2186
- return;
2187
- }
2188
- this.onPointerMove(pointer);
2189
- requestRender();
2190
- }
2191
- PointerUp(pointer) {
2192
- if (!this.#pressed) {
2193
- return;
2194
- }
2195
- this.#pressed = false;
2196
- this.onPointerUp(pointer);
2197
- if (pointer.inside) {
2198
- this.onClick(pointer);
2199
- }
2200
- requestRender();
2201
- }
2202
- Cursor() {
2203
- return "pointer" /* Pointer */;
2204
- }
2205
- Hidden() {
2206
- return this.#hidden;
2207
- }
2208
- setHidden(hidden) {
2209
- this.#hidden = hidden;
2210
- requestRender();
2211
- }
2212
- Disabled() {
2213
- return this.#disabled;
2214
- }
2215
- setDisabled(disabled) {
2216
- this.#disabled = disabled;
2217
- if (disabled) {
2218
- this.#pressed = false;
2219
- }
2220
- requestRender();
2221
- }
2222
- hovered() {
2223
- return this.#hovered;
2224
- }
2225
- pressed() {
2226
- return this.#pressed;
2227
- }
2228
- /**
2229
- * Where the widget was last drawn, in canvas pixels.
2230
- */
2231
- box() {
2232
- return this.#box;
2233
- }
2234
- canvas() {
2235
- return this.#canvas;
2236
- }
2237
- scale() {
2238
- return this.#scale;
2239
- }
2240
- onPointerDown(pointer) {
2241
- }
2242
- onPointerMove(pointer) {
2243
- }
2244
- onPointerUp(pointer) {
2245
- }
2246
- /**
2247
- * A press and release that both landed on the widget.
2248
- */
2249
- onClick(pointer) {
2250
- }
2251
- };
2252
- function startingWidgetValue(node, property, configured, fallback) {
2253
- if (configured !== void 0) {
2254
- return configured;
2255
- }
2256
- if (property !== void 0 && property !== null) {
2257
- const existing = node.getProperty(property);
2258
- if (existing !== void 0 && existing !== null) {
2259
- return existing;
2260
- }
2261
- }
2262
- return fallback;
2263
- }
2264
-
2265
2352
  // src/widgets/value.ts
2266
2353
  var ValueWidget = class extends WidgetBase {
2267
2354
  #node;
@@ -2396,16 +2483,10 @@ var NumberWidget = class extends ValueWidget {
2396
2483
  return;
2397
2484
  }
2398
2485
  const before = this.value();
2399
- editInPlace({
2400
- canvas: this.canvas(),
2401
- box: this.box(),
2402
- scale: this.scale(),
2403
- value: String(before),
2404
- onCommit: (text) => {
2405
- const parsed = Number.parseFloat(text);
2406
- if (Number.isFinite(parsed)) {
2407
- this.commit(this.#clean(parsed), before);
2408
- }
2486
+ this.editText(String(before), (text) => {
2487
+ const parsed = Number.parseFloat(text);
2488
+ if (Number.isFinite(parsed)) {
2489
+ this.commit(this.#clean(parsed), before);
2409
2490
  }
2410
2491
  });
2411
2492
  }
@@ -2613,13 +2694,7 @@ var StringWidget = class extends ValueWidget {
2613
2694
  }
2614
2695
  onClick() {
2615
2696
  const before = this.value();
2616
- editInPlace({
2617
- canvas: this.canvas(),
2618
- box: this.box(),
2619
- scale: this.scale(),
2620
- value: before,
2621
- onCommit: (text) => this.commit(text, before)
2622
- });
2697
+ this.editText(before, (text) => this.commit(text, before));
2623
2698
  }
2624
2699
  };
2625
2700
 
@@ -5530,7 +5605,8 @@ var FlowNode = class {
5530
5605
  size.y += this.#portRowHeight(port, tempMeasurement.y) + this.#elementSpacing;
5531
5606
  size.x = Math.max(size.x, tempMeasurement.x + doublePadding);
5532
5607
  }
5533
- for (const widget of this.#widgets) {
5608
+ const widgets = this.#hasHiddenPortsToggle() ? [this.#hiddenPortsToggle, ...this.#widgets] : this.#widgets;
5609
+ for (const widget of widgets) {
5534
5610
  if (widget.Hidden?.()) {
5535
5611
  continue;
5536
5612
  }
@@ -5556,6 +5632,31 @@ var FlowNode = class {
5556
5632
  this.#output.push(port);
5557
5633
  return port;
5558
5634
  }
5635
+ #hiddenPortsShown = false;
5636
+ #hiddenPortsToggle = new HiddenPortsToggle(this);
5637
+ #hiddenPortsTogglePosition = null;
5638
+ hiddenPortsShown() {
5639
+ return this.#hiddenPortsShown;
5640
+ }
5641
+ setHiddenPortsShown(shown) {
5642
+ if (shown === this.#hiddenPortsShown) {
5643
+ return;
5644
+ }
5645
+ this.#hiddenPortsShown = shown;
5646
+ this.invalidateLayout();
5647
+ }
5648
+ hiddenPortCount() {
5649
+ let count = 0;
5650
+ for (const port of [...this.#input, ...this.#output]) {
5651
+ if (port.tucked()) {
5652
+ count++;
5653
+ }
5654
+ }
5655
+ return count;
5656
+ }
5657
+ #hasHiddenPortsToggle() {
5658
+ return this.hiddenPortCount() > 0;
5659
+ }
5559
5660
  addWidget(widget) {
5560
5661
  this.#widgets.push(widget);
5561
5662
  requestRender();
@@ -5634,6 +5735,11 @@ var FlowNode = class {
5634
5735
  intersection.Port = this.#output[i];
5635
5736
  }
5636
5737
  }
5738
+ if (this.#hiddenPortsTogglePosition !== null && InBox(this.#hiddenPortsTogglePosition, position)) {
5739
+ intersection.Node = this;
5740
+ intersection.Widget = this.#hiddenPortsToggle;
5741
+ intersection.WidgetBox = this.#hiddenPortsTogglePosition;
5742
+ }
5637
5743
  for (let i = 0; i < this.#widgetPositions.Count(); i++) {
5638
5744
  const widget = this.#widgets[i];
5639
5745
  if (widget !== void 0 && !widget.Hidden?.() && InBox(this.#widgetPositions.At(i), position)) {
@@ -5903,6 +6009,16 @@ var FlowNode = class {
5903
6009
  this.#outputPortPositions.Push(port.render(ctx, position, camera, mousePosition, postProcess));
5904
6010
  startY += portHeight + scaledElementSpacing;
5905
6011
  }
6012
+ this.#hiddenPortsTogglePosition = null;
6013
+ if (this.#hasHiddenPortsToggle()) {
6014
+ const box = {
6015
+ Position: { x: nodeBounds.Position.x + scaledPadding, y: startY },
6016
+ Size: { x: nodeBounds.Size.x - scaledPadding * 2, y: this.#hiddenPortsToggle.Size().y * camera.zoom }
6017
+ };
6018
+ this.#hiddenPortsToggle.Draw(ctx, box, camera.zoom, mousePosition);
6019
+ this.#hiddenPortsTogglePosition = box;
6020
+ startY += box.Size.y + scaledElementSpacing;
6021
+ }
5906
6022
  for (let i = 0; i < this.#widgets.length; i++) {
5907
6023
  const widget = this.#widgets[i];
5908
6024
  if (widget.Hidden?.()) {
@@ -5957,6 +6073,7 @@ var FlowNode = class {
5957
6073
  return false;
5958
6074
  }
5959
6075
  this.#widgetPositions.Clear();
6076
+ this.#hiddenPortsTogglePosition = null;
5960
6077
  return true;
5961
6078
  }
5962
6079
  placePorts(bounds, zoom) {
@@ -7617,8 +7734,8 @@ var NodeFlowGraph = class {
7617
7734
  this.#clickEnd.bind(this),
7618
7735
  this.#openContextMenu.bind(this),
7619
7736
  this.#fileDrop.bind(this),
7620
- (amount, anchor) => {
7621
- this.zoom(amount, anchor);
7737
+ (factor, anchor) => {
7738
+ this.#zoomBy(factor, anchor);
7622
7739
  }
7623
7740
  );
7624
7741
  document.addEventListener(
@@ -7746,12 +7863,15 @@ var NodeFlowGraph = class {
7746
7863
  this.#mainNoteSubsystem.addNoteDragStopListener(callback);
7747
7864
  }
7748
7865
  zoom(amount, anchor) {
7866
+ this.#zoomBy(1 + amount * 0.05, anchor);
7867
+ }
7868
+ #zoomBy(factor, anchor) {
7749
7869
  const anchorPos = anchor ?? this.#mousePosition;
7750
7870
  let oldPos = void 0;
7751
7871
  if (anchorPos !== void 0) {
7752
7872
  oldPos = this.#sceenPositionToGraphPosition(anchorPos);
7753
7873
  }
7754
- this.#camera.zoom += amount * this.#camera.zoom * 0.05;
7874
+ this.#camera.zoom *= factor;
7755
7875
  if (!oldPos || anchorPos === void 0) {
7756
7876
  return;
7757
7877
  }
@@ -8016,10 +8136,12 @@ var NodeFlowGraph = class {
8016
8136
  this.#cursor = "default" /* Default */;
8017
8137
  TimeExecution("Render_Background", this.#renderBackground.bind(this));
8018
8138
  TimeExecution("Render_View_" + this.#currentView, () => {
8019
- let results = this.currentView().render(this.#ctx, this.#camera, this.#mousePosition);
8020
- if (results?.cursorStyle) {
8021
- this.#cursor = results?.cursorStyle;
8022
- }
8139
+ placeEditsDuring(this.#canvas, () => {
8140
+ let results = this.currentView().render(this.#ctx, this.#camera, this.#mousePosition);
8141
+ if (results?.cursorStyle) {
8142
+ this.#cursor = results?.cursorStyle;
8143
+ }
8144
+ });
8023
8145
  });
8024
8146
  TimeExecution("Render_Minimap", () => {
8025
8147
  this.#minimap.render(this.#ctx, this.#canvas, this.#camera, this.#mainNodeSubsystem.getNodes());