@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.js CHANGED
@@ -805,7 +805,7 @@ var ContextMenu = class _ContextMenu {
805
805
  // src/input.ts
806
806
  var LONG_PRESS_MS = 500;
807
807
  var LONG_PRESS_SLOP = 12;
808
- var PINCH_SENSITIVITY = 50;
808
+ var WHEEL_ZOOM_PER_PIXEL = 5e-4;
809
809
  var SUPPRESS_MOUSE_MS = 600;
810
810
  var MouseObserver = class {
811
811
  #clicked;
@@ -903,13 +903,12 @@ var MouseObserver = class {
903
903
  } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
904
904
  dy *= this.#ele.clientHeight;
905
905
  }
906
- const amount = -dy / 100;
907
- if (amount === 0) {
906
+ if (dy === 0) {
908
907
  return;
909
908
  }
910
909
  const anchor = this.#mousePos(event);
911
910
  this.#moveCallback(anchor);
912
- this.#zoomCallback(amount, anchor);
911
+ this.#zoomCallback(Math.exp(-dy * WHEEL_ZOOM_PER_PIXEL), anchor);
913
912
  }
914
913
  #move(event) {
915
914
  if (this.#fromTouch()) {
@@ -985,7 +984,7 @@ var MouseObserver = class {
985
984
  const anchor = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
986
985
  if (this.#pinchDist > 0) {
987
986
  this.#moveCallback(anchor);
988
- this.#zoomCallback((dist - this.#pinchDist) / PINCH_SENSITIVITY, anchor);
987
+ this.#zoomCallback(dist / this.#pinchDist, anchor);
989
988
  }
990
989
  this.#pinchDist = dist;
991
990
  return;
@@ -1835,9 +1834,13 @@ var Port = class {
1835
1834
  }
1836
1835
  }
1837
1836
  /**
1838
- * A port asked to hide still shows while anything is connected to it.
1837
+ * A port asked to hide still shows while anything is connected to it,
1838
+ * and while its node is showing its hidden ports.
1839
1839
  */
1840
1840
  hidden() {
1841
+ return this.tucked() && !(this.#node?.hiddenPortsShown() ?? false);
1842
+ }
1843
+ tucked() {
1841
1844
  return this.#hidden && this.#connections.length === 0;
1842
1845
  }
1843
1846
  addConnectionRemovedListener(callback) {
@@ -1939,6 +1942,281 @@ var Port = class {
1939
1942
  }
1940
1943
  };
1941
1944
 
1945
+ // src/styles/cursor.ts
1946
+ var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
1947
+ CursorStyle2["Default"] = "default";
1948
+ CursorStyle2["Pointer"] = "pointer";
1949
+ CursorStyle2["Grab"] = "grab";
1950
+ CursorStyle2["Grabbing"] = "grabbing";
1951
+ CursorStyle2["Wait"] = "wait";
1952
+ CursorStyle2["Help"] = "help";
1953
+ CursorStyle2["Crosshair"] = "crosshair";
1954
+ CursorStyle2["Cell"] = "cell";
1955
+ CursorStyle2["Text"] = "text";
1956
+ CursorStyle2["Move"] = "move";
1957
+ CursorStyle2["NotAllowed"] = "not-allowed";
1958
+ CursorStyle2["ZoomIn"] = "zoom-in";
1959
+ CursorStyle2["ZoomOut"] = "zoom-out";
1960
+ CursorStyle2["None"] = "none";
1961
+ CursorStyle2["ContextMenu"] = "context-menu";
1962
+ CursorStyle2["EWResize"] = "ew-resize";
1963
+ return CursorStyle2;
1964
+ })(CursorStyle || {});
1965
+
1966
+ // src/widgets/inplace.ts
1967
+ var open = /* @__PURE__ */ new Set();
1968
+ function placeEditsDuring(canvas, draw) {
1969
+ open.forEach((edit) => {
1970
+ if (edit.canvas === canvas) {
1971
+ edit.placed = false;
1972
+ }
1973
+ });
1974
+ draw();
1975
+ open.forEach((edit) => {
1976
+ if (edit.canvas === canvas && !edit.placed) {
1977
+ edit.input.style.opacity = "0";
1978
+ edit.input.style.pointerEvents = "none";
1979
+ }
1980
+ });
1981
+ }
1982
+ function editInPlace(config) {
1983
+ const canvas = config.canvas;
1984
+ if (canvas === null) {
1985
+ return null;
1986
+ }
1987
+ const input = document.createElement("input");
1988
+ input.type = "text";
1989
+ input.value = config.value;
1990
+ const edit = { canvas, input, placed: true };
1991
+ const place = (box, scale) => {
1992
+ const rect = canvas.getBoundingClientRect();
1993
+ const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
1994
+ const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
1995
+ const left = rect.left + box.Position.x * sx;
1996
+ const top = rect.top + box.Position.y * sy;
1997
+ const width2 = box.Size.x * sx;
1998
+ const height2 = box.Size.y * sy;
1999
+ const clip = [
2000
+ Math.max(0, rect.top - top),
2001
+ Math.max(0, left + width2 - (rect.left + rect.width)),
2002
+ Math.max(0, top + height2 - (rect.top + rect.height)),
2003
+ Math.max(0, rect.left - left)
2004
+ ];
2005
+ edit.placed = true;
2006
+ input.style.cssText = [
2007
+ "position: fixed",
2008
+ `left: ${left}px`,
2009
+ `top: ${top}px`,
2010
+ `width: ${width2}px`,
2011
+ `height: ${height2}px`,
2012
+ `clip-path: inset(${clip.map((c) => `${c}px`).join(" ")})`,
2013
+ "box-sizing: border-box",
2014
+ "margin: 0",
2015
+ `padding: 0 ${Theme.Widget.Padding * scale}px`,
2016
+ `font: ${Theme.Widget.FontSize * scale * sy}px ${Theme.FontFamily}`,
2017
+ `color: ${Theme.Widget.FontColor}`,
2018
+ `background: ${Theme.Widget.Hover.BackgroundColor}`,
2019
+ `border: ${Theme.Widget.Border.Size * scale}px solid ${Theme.Widget.Border.Color}`,
2020
+ `border-radius: ${Theme.Widget.Border.Radius * scale}px`,
2021
+ "outline: none",
2022
+ "z-index: 1000"
2023
+ ].join(";");
2024
+ };
2025
+ place(config.box, config.scale);
2026
+ let done = false;
2027
+ const finish = (keep) => {
2028
+ if (done) {
2029
+ return;
2030
+ }
2031
+ done = true;
2032
+ open.delete(edit);
2033
+ input.remove();
2034
+ if (keep) {
2035
+ config.onCommit(input.value);
2036
+ }
2037
+ requestRender();
2038
+ };
2039
+ input.addEventListener("keydown", (event) => {
2040
+ event.stopPropagation();
2041
+ if (event.key === "Enter") {
2042
+ event.preventDefault();
2043
+ finish(true);
2044
+ } else if (event.key === "Escape") {
2045
+ event.preventDefault();
2046
+ finish(false);
2047
+ }
2048
+ });
2049
+ input.addEventListener("blur", () => finish(true));
2050
+ open.add(edit);
2051
+ document.body.appendChild(input);
2052
+ input.focus();
2053
+ input.select();
2054
+ return { input, place };
2055
+ }
2056
+
2057
+ // src/widgets/widget.ts
2058
+ var width = 150;
2059
+ var height = 25;
2060
+ var WidgetBase = class {
2061
+ #hidden = false;
2062
+ #disabled = false;
2063
+ #hovered = false;
2064
+ #pressed = false;
2065
+ #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
2066
+ #canvas = null;
2067
+ #scale = 1;
2068
+ #editing = null;
2069
+ Draw(ctx, box, scale, mousePosition) {
2070
+ this.#box = { Position: { x: box.Position.x, y: box.Position.y }, Size: { x: box.Size.x, y: box.Size.y } };
2071
+ this.#canvas = ctx.canvas ?? null;
2072
+ this.#scale = scale;
2073
+ this.#hovered = !this.#disabled && mousePosition !== void 0 && InBox(box, mousePosition);
2074
+ if (this.#editing !== null) {
2075
+ if (this.#editing.input.isConnected) {
2076
+ this.#editing.place(this.#box, scale);
2077
+ } else {
2078
+ this.#editing = null;
2079
+ }
2080
+ }
2081
+ ctx.save();
2082
+ if (this.#disabled) {
2083
+ ctx.globalAlpha *= 0.4;
2084
+ }
2085
+ this.render(ctx, box, scale);
2086
+ ctx.restore();
2087
+ }
2088
+ PointerDown(pointer) {
2089
+ if (this.#disabled) {
2090
+ return;
2091
+ }
2092
+ this.#pressed = true;
2093
+ this.onPointerDown(pointer);
2094
+ requestRender();
2095
+ }
2096
+ PointerMove(pointer) {
2097
+ if (!this.#pressed) {
2098
+ return;
2099
+ }
2100
+ this.onPointerMove(pointer);
2101
+ requestRender();
2102
+ }
2103
+ PointerUp(pointer) {
2104
+ if (!this.#pressed) {
2105
+ return;
2106
+ }
2107
+ this.#pressed = false;
2108
+ this.onPointerUp(pointer);
2109
+ if (pointer.inside) {
2110
+ this.onClick(pointer);
2111
+ }
2112
+ requestRender();
2113
+ }
2114
+ Cursor() {
2115
+ return "pointer" /* Pointer */;
2116
+ }
2117
+ Hidden() {
2118
+ return this.#hidden;
2119
+ }
2120
+ setHidden(hidden) {
2121
+ this.#hidden = hidden;
2122
+ requestRender();
2123
+ }
2124
+ Disabled() {
2125
+ return this.#disabled;
2126
+ }
2127
+ setDisabled(disabled) {
2128
+ this.#disabled = disabled;
2129
+ if (disabled) {
2130
+ this.#pressed = false;
2131
+ }
2132
+ requestRender();
2133
+ }
2134
+ hovered() {
2135
+ return this.#hovered;
2136
+ }
2137
+ pressed() {
2138
+ return this.#pressed;
2139
+ }
2140
+ /**
2141
+ * Where the widget was last drawn, in canvas pixels.
2142
+ */
2143
+ box() {
2144
+ return this.#box;
2145
+ }
2146
+ canvas() {
2147
+ return this.#canvas;
2148
+ }
2149
+ scale() {
2150
+ return this.#scale;
2151
+ }
2152
+ editText(value, onCommit) {
2153
+ this.#editing = editInPlace({
2154
+ canvas: this.#canvas,
2155
+ box: this.#box,
2156
+ scale: this.#scale,
2157
+ value,
2158
+ onCommit
2159
+ });
2160
+ }
2161
+ onPointerDown(pointer) {
2162
+ }
2163
+ onPointerMove(pointer) {
2164
+ }
2165
+ onPointerUp(pointer) {
2166
+ }
2167
+ /**
2168
+ * A press and release that both landed on the widget.
2169
+ */
2170
+ onClick(pointer) {
2171
+ }
2172
+ };
2173
+ function startingWidgetValue(node, property, configured, fallback) {
2174
+ if (configured !== void 0) {
2175
+ return configured;
2176
+ }
2177
+ if (property !== void 0 && property !== null) {
2178
+ const existing = node.getProperty(property);
2179
+ if (existing !== void 0 && existing !== null) {
2180
+ return existing;
2181
+ }
2182
+ }
2183
+ return fallback;
2184
+ }
2185
+
2186
+ // src/widgets/hidden_ports.ts
2187
+ var HiddenPortsToggle = class extends WidgetBase {
2188
+ constructor(owner) {
2189
+ super();
2190
+ this.owner = owner;
2191
+ }
2192
+ Size() {
2193
+ return { x: 150, y: 16 };
2194
+ }
2195
+ render(ctx, box, scale) {
2196
+ const label = this.owner.hiddenPortsShown() ? "\u25B4 less" : `\u25BE ${this.owner.hiddenPortCount()} more`;
2197
+ const midY = box.Position.y + box.Size.y / 2;
2198
+ const centerX = box.Position.x + box.Size.x / 2;
2199
+ ctx.font = `${11 * scale}px ${Theme.FontFamily}`;
2200
+ const half = ctx.measureText(label).width / 2 + 6 * scale;
2201
+ ctx.globalAlpha = this.hovered() ? 1 : 0.5;
2202
+ ctx.strokeStyle = Theme.Widget.FontColor;
2203
+ ctx.fillStyle = Theme.Widget.FontColor;
2204
+ ctx.lineWidth = Math.max(1, scale);
2205
+ ctx.beginPath();
2206
+ ctx.moveTo(box.Position.x, midY);
2207
+ ctx.lineTo(centerX - half, midY);
2208
+ ctx.moveTo(centerX + half, midY);
2209
+ ctx.lineTo(box.Position.x + box.Size.x, midY);
2210
+ ctx.stroke();
2211
+ ctx.textAlign = "center";
2212
+ ctx.textBaseline = "middle";
2213
+ ctx.fillText(label, centerX, midY);
2214
+ }
2215
+ onClick() {
2216
+ this.owner.setHiddenPortsShown(!this.owner.hiddenPortsShown());
2217
+ }
2218
+ };
2219
+
1942
2220
  // src/styles/stroke.ts
1943
2221
  function StrokeStyleWithFallback(input, fallback) {
1944
2222
  return {
@@ -2050,27 +2328,6 @@ var BoxStyle = class {
2050
2328
  }
2051
2329
  };
2052
2330
 
2053
- // src/styles/cursor.ts
2054
- var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
2055
- CursorStyle2["Default"] = "default";
2056
- CursorStyle2["Pointer"] = "pointer";
2057
- CursorStyle2["Grab"] = "grab";
2058
- CursorStyle2["Grabbing"] = "grabbing";
2059
- CursorStyle2["Wait"] = "wait";
2060
- CursorStyle2["Help"] = "help";
2061
- CursorStyle2["Crosshair"] = "crosshair";
2062
- CursorStyle2["Cell"] = "cell";
2063
- CursorStyle2["Text"] = "text";
2064
- CursorStyle2["Move"] = "move";
2065
- CursorStyle2["NotAllowed"] = "not-allowed";
2066
- CursorStyle2["ZoomIn"] = "zoom-in";
2067
- CursorStyle2["ZoomOut"] = "zoom-out";
2068
- CursorStyle2["None"] = "none";
2069
- CursorStyle2["ContextMenu"] = "context-menu";
2070
- CursorStyle2["EWResize"] = "ew-resize";
2071
- return CursorStyle2;
2072
- })(CursorStyle || {});
2073
-
2074
2331
  // src/styles/bevel.ts
2075
2332
  function drawBevel(ctx, box, scale, colors, sunken) {
2076
2333
  const width2 = Math.max(1, 2 * scale);
@@ -2162,176 +2419,6 @@ function drawFieldText(ctx, box, scale, look) {
2162
2419
  );
2163
2420
  }
2164
2421
 
2165
- // src/widgets/inplace.ts
2166
- function editInPlace(config) {
2167
- const canvas = config.canvas;
2168
- if (canvas === null) {
2169
- return null;
2170
- }
2171
- const rect = canvas.getBoundingClientRect();
2172
- const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
2173
- const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
2174
- const input = document.createElement("input");
2175
- input.type = "text";
2176
- input.value = config.value;
2177
- input.style.cssText = [
2178
- "position: fixed",
2179
- `left: ${rect.left + config.box.Position.x * sx}px`,
2180
- `top: ${rect.top + config.box.Position.y * sy}px`,
2181
- `width: ${config.box.Size.x * sx}px`,
2182
- `height: ${config.box.Size.y * sy}px`,
2183
- "box-sizing: border-box",
2184
- "margin: 0",
2185
- `padding: 0 ${Theme.Widget.Padding * config.scale}px`,
2186
- `font: ${Theme.Widget.FontSize * config.scale * sy}px ${Theme.FontFamily}`,
2187
- `color: ${Theme.Widget.FontColor}`,
2188
- `background: ${Theme.Widget.Hover.BackgroundColor}`,
2189
- `border: ${Theme.Widget.Border.Size * config.scale}px solid ${Theme.Widget.Border.Color}`,
2190
- `border-radius: ${Theme.Widget.Border.Radius * config.scale}px`,
2191
- "outline: none",
2192
- "z-index: 1000"
2193
- ].join(";");
2194
- let done = false;
2195
- const finish = (keep) => {
2196
- if (done) {
2197
- return;
2198
- }
2199
- done = true;
2200
- input.remove();
2201
- if (keep) {
2202
- config.onCommit(input.value);
2203
- }
2204
- requestRender();
2205
- };
2206
- input.addEventListener("keydown", (event) => {
2207
- event.stopPropagation();
2208
- if (event.key === "Enter") {
2209
- event.preventDefault();
2210
- finish(true);
2211
- } else if (event.key === "Escape") {
2212
- event.preventDefault();
2213
- finish(false);
2214
- }
2215
- });
2216
- input.addEventListener("blur", () => finish(true));
2217
- document.body.appendChild(input);
2218
- input.focus();
2219
- input.select();
2220
- return input;
2221
- }
2222
-
2223
- // src/widgets/widget.ts
2224
- var width = 150;
2225
- var height = 25;
2226
- var WidgetBase = class {
2227
- #hidden = false;
2228
- #disabled = false;
2229
- #hovered = false;
2230
- #pressed = false;
2231
- #box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
2232
- #canvas = null;
2233
- #scale = 1;
2234
- Draw(ctx, box, scale, mousePosition) {
2235
- this.#box = { Position: { x: box.Position.x, y: box.Position.y }, Size: { x: box.Size.x, y: box.Size.y } };
2236
- this.#canvas = ctx.canvas ?? null;
2237
- this.#scale = scale;
2238
- this.#hovered = !this.#disabled && mousePosition !== void 0 && InBox(box, mousePosition);
2239
- ctx.save();
2240
- if (this.#disabled) {
2241
- ctx.globalAlpha *= 0.4;
2242
- }
2243
- this.render(ctx, box, scale);
2244
- ctx.restore();
2245
- }
2246
- PointerDown(pointer) {
2247
- if (this.#disabled) {
2248
- return;
2249
- }
2250
- this.#pressed = true;
2251
- this.onPointerDown(pointer);
2252
- requestRender();
2253
- }
2254
- PointerMove(pointer) {
2255
- if (!this.#pressed) {
2256
- return;
2257
- }
2258
- this.onPointerMove(pointer);
2259
- requestRender();
2260
- }
2261
- PointerUp(pointer) {
2262
- if (!this.#pressed) {
2263
- return;
2264
- }
2265
- this.#pressed = false;
2266
- this.onPointerUp(pointer);
2267
- if (pointer.inside) {
2268
- this.onClick(pointer);
2269
- }
2270
- requestRender();
2271
- }
2272
- Cursor() {
2273
- return "pointer" /* Pointer */;
2274
- }
2275
- Hidden() {
2276
- return this.#hidden;
2277
- }
2278
- setHidden(hidden) {
2279
- this.#hidden = hidden;
2280
- requestRender();
2281
- }
2282
- Disabled() {
2283
- return this.#disabled;
2284
- }
2285
- setDisabled(disabled) {
2286
- this.#disabled = disabled;
2287
- if (disabled) {
2288
- this.#pressed = false;
2289
- }
2290
- requestRender();
2291
- }
2292
- hovered() {
2293
- return this.#hovered;
2294
- }
2295
- pressed() {
2296
- return this.#pressed;
2297
- }
2298
- /**
2299
- * Where the widget was last drawn, in canvas pixels.
2300
- */
2301
- box() {
2302
- return this.#box;
2303
- }
2304
- canvas() {
2305
- return this.#canvas;
2306
- }
2307
- scale() {
2308
- return this.#scale;
2309
- }
2310
- onPointerDown(pointer) {
2311
- }
2312
- onPointerMove(pointer) {
2313
- }
2314
- onPointerUp(pointer) {
2315
- }
2316
- /**
2317
- * A press and release that both landed on the widget.
2318
- */
2319
- onClick(pointer) {
2320
- }
2321
- };
2322
- function startingWidgetValue(node, property, configured, fallback) {
2323
- if (configured !== void 0) {
2324
- return configured;
2325
- }
2326
- if (property !== void 0 && property !== null) {
2327
- const existing = node.getProperty(property);
2328
- if (existing !== void 0 && existing !== null) {
2329
- return existing;
2330
- }
2331
- }
2332
- return fallback;
2333
- }
2334
-
2335
2422
  // src/widgets/value.ts
2336
2423
  var ValueWidget = class extends WidgetBase {
2337
2424
  #node;
@@ -2466,16 +2553,10 @@ var NumberWidget = class extends ValueWidget {
2466
2553
  return;
2467
2554
  }
2468
2555
  const before = this.value();
2469
- editInPlace({
2470
- canvas: this.canvas(),
2471
- box: this.box(),
2472
- scale: this.scale(),
2473
- value: String(before),
2474
- onCommit: (text) => {
2475
- const parsed = Number.parseFloat(text);
2476
- if (Number.isFinite(parsed)) {
2477
- this.commit(this.#clean(parsed), before);
2478
- }
2556
+ this.editText(String(before), (text) => {
2557
+ const parsed = Number.parseFloat(text);
2558
+ if (Number.isFinite(parsed)) {
2559
+ this.commit(this.#clean(parsed), before);
2479
2560
  }
2480
2561
  });
2481
2562
  }
@@ -2683,13 +2764,7 @@ var StringWidget = class extends ValueWidget {
2683
2764
  }
2684
2765
  onClick() {
2685
2766
  const before = this.value();
2686
- editInPlace({
2687
- canvas: this.canvas(),
2688
- box: this.box(),
2689
- scale: this.scale(),
2690
- value: before,
2691
- onCommit: (text) => this.commit(text, before)
2692
- });
2767
+ this.editText(before, (text) => this.commit(text, before));
2693
2768
  }
2694
2769
  };
2695
2770
 
@@ -5600,7 +5675,8 @@ var FlowNode = class {
5600
5675
  size.y += this.#portRowHeight(port, tempMeasurement.y) + this.#elementSpacing;
5601
5676
  size.x = Math.max(size.x, tempMeasurement.x + doublePadding);
5602
5677
  }
5603
- for (const widget of this.#widgets) {
5678
+ const widgets = this.#hasHiddenPortsToggle() ? [this.#hiddenPortsToggle, ...this.#widgets] : this.#widgets;
5679
+ for (const widget of widgets) {
5604
5680
  if (widget.Hidden?.()) {
5605
5681
  continue;
5606
5682
  }
@@ -5626,6 +5702,31 @@ var FlowNode = class {
5626
5702
  this.#output.push(port);
5627
5703
  return port;
5628
5704
  }
5705
+ #hiddenPortsShown = false;
5706
+ #hiddenPortsToggle = new HiddenPortsToggle(this);
5707
+ #hiddenPortsTogglePosition = null;
5708
+ hiddenPortsShown() {
5709
+ return this.#hiddenPortsShown;
5710
+ }
5711
+ setHiddenPortsShown(shown) {
5712
+ if (shown === this.#hiddenPortsShown) {
5713
+ return;
5714
+ }
5715
+ this.#hiddenPortsShown = shown;
5716
+ this.invalidateLayout();
5717
+ }
5718
+ hiddenPortCount() {
5719
+ let count = 0;
5720
+ for (const port of [...this.#input, ...this.#output]) {
5721
+ if (port.tucked()) {
5722
+ count++;
5723
+ }
5724
+ }
5725
+ return count;
5726
+ }
5727
+ #hasHiddenPortsToggle() {
5728
+ return this.hiddenPortCount() > 0;
5729
+ }
5629
5730
  addWidget(widget) {
5630
5731
  this.#widgets.push(widget);
5631
5732
  requestRender();
@@ -5704,6 +5805,11 @@ var FlowNode = class {
5704
5805
  intersection.Port = this.#output[i];
5705
5806
  }
5706
5807
  }
5808
+ if (this.#hiddenPortsTogglePosition !== null && InBox(this.#hiddenPortsTogglePosition, position)) {
5809
+ intersection.Node = this;
5810
+ intersection.Widget = this.#hiddenPortsToggle;
5811
+ intersection.WidgetBox = this.#hiddenPortsTogglePosition;
5812
+ }
5707
5813
  for (let i = 0; i < this.#widgetPositions.Count(); i++) {
5708
5814
  const widget = this.#widgets[i];
5709
5815
  if (widget !== void 0 && !widget.Hidden?.() && InBox(this.#widgetPositions.At(i), position)) {
@@ -5973,6 +6079,16 @@ var FlowNode = class {
5973
6079
  this.#outputPortPositions.Push(port.render(ctx, position, camera, mousePosition, postProcess));
5974
6080
  startY += portHeight + scaledElementSpacing;
5975
6081
  }
6082
+ this.#hiddenPortsTogglePosition = null;
6083
+ if (this.#hasHiddenPortsToggle()) {
6084
+ const box = {
6085
+ Position: { x: nodeBounds.Position.x + scaledPadding, y: startY },
6086
+ Size: { x: nodeBounds.Size.x - scaledPadding * 2, y: this.#hiddenPortsToggle.Size().y * camera.zoom }
6087
+ };
6088
+ this.#hiddenPortsToggle.Draw(ctx, box, camera.zoom, mousePosition);
6089
+ this.#hiddenPortsTogglePosition = box;
6090
+ startY += box.Size.y + scaledElementSpacing;
6091
+ }
5976
6092
  for (let i = 0; i < this.#widgets.length; i++) {
5977
6093
  const widget = this.#widgets[i];
5978
6094
  if (widget.Hidden?.()) {
@@ -6027,6 +6143,7 @@ var FlowNode = class {
6027
6143
  return false;
6028
6144
  }
6029
6145
  this.#widgetPositions.Clear();
6146
+ this.#hiddenPortsTogglePosition = null;
6030
6147
  return true;
6031
6148
  }
6032
6149
  placePorts(bounds, zoom) {
@@ -7687,8 +7804,8 @@ var NodeFlowGraph = class {
7687
7804
  this.#clickEnd.bind(this),
7688
7805
  this.#openContextMenu.bind(this),
7689
7806
  this.#fileDrop.bind(this),
7690
- (amount, anchor) => {
7691
- this.zoom(amount, anchor);
7807
+ (factor, anchor) => {
7808
+ this.#zoomBy(factor, anchor);
7692
7809
  }
7693
7810
  );
7694
7811
  document.addEventListener(
@@ -7816,12 +7933,15 @@ var NodeFlowGraph = class {
7816
7933
  this.#mainNoteSubsystem.addNoteDragStopListener(callback);
7817
7934
  }
7818
7935
  zoom(amount, anchor) {
7936
+ this.#zoomBy(1 + amount * 0.05, anchor);
7937
+ }
7938
+ #zoomBy(factor, anchor) {
7819
7939
  const anchorPos = anchor ?? this.#mousePosition;
7820
7940
  let oldPos = void 0;
7821
7941
  if (anchorPos !== void 0) {
7822
7942
  oldPos = this.#sceenPositionToGraphPosition(anchorPos);
7823
7943
  }
7824
- this.#camera.zoom += amount * this.#camera.zoom * 0.05;
7944
+ this.#camera.zoom *= factor;
7825
7945
  if (!oldPos || anchorPos === void 0) {
7826
7946
  return;
7827
7947
  }
@@ -8086,10 +8206,12 @@ var NodeFlowGraph = class {
8086
8206
  this.#cursor = "default" /* Default */;
8087
8207
  TimeExecution("Render_Background", this.#renderBackground.bind(this));
8088
8208
  TimeExecution("Render_View_" + this.#currentView, () => {
8089
- let results = this.currentView().render(this.#ctx, this.#camera, this.#mousePosition);
8090
- if (results?.cursorStyle) {
8091
- this.#cursor = results?.cursorStyle;
8092
- }
8209
+ placeEditsDuring(this.#canvas, () => {
8210
+ let results = this.currentView().render(this.#ctx, this.#camera, this.#mousePosition);
8211
+ if (results?.cursorStyle) {
8212
+ this.#cursor = results?.cursorStyle;
8213
+ }
8214
+ });
8093
8215
  });
8094
8216
  TimeExecution("Render_Minimap", () => {
8095
8217
  this.#minimap.render(this.#ctx, this.#canvas, this.#camera, this.#mainNodeSubsystem.getNodes());