@elicdavis/node-flow 0.2.1 → 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.d.mts CHANGED
@@ -227,9 +227,11 @@ declare class Port {
227
227
  slots(): number;
228
228
  setHidden(hidden: boolean): void;
229
229
  /**
230
- * A port asked to hide still shows while anything is connected to it.
230
+ * A port asked to hide still shows while anything is connected to it,
231
+ * and while its node is showing its hidden ports.
231
232
  */
232
233
  hidden(): boolean;
234
+ tucked(): boolean;
233
235
  addConnectionRemovedListener(callback: ConnectionChangeCallback): void;
234
236
  clearConnection(connection: Connection): void;
235
237
  getDataType(): string;
@@ -644,6 +646,9 @@ declare class FlowNode {
644
646
  calculateBounds(ctx: CanvasRenderingContext2D, camera: Camera): Box;
645
647
  addInput(config: PortConfig): Port;
646
648
  addOutput(config: PortConfig): Port;
649
+ hiddenPortsShown(): boolean;
650
+ setHiddenPortsShown(shown: boolean): void;
651
+ hiddenPortCount(): number;
647
652
  addWidget(widget: Widget): void;
648
653
  insertWidget(index: number, widget: Widget): void;
649
654
  removeWidget(widget: Widget): void;
@@ -1089,7 +1094,7 @@ declare const defaults: {
1089
1094
  };
1090
1095
  type ThemeValues = typeof defaults;
1091
1096
  type DeepPartial<T> = {
1092
- [K in keyof T]?: T[K] extends object | null ? (null extends T[K] ? T[K] : DeepPartial<T[K]>) : T[K];
1097
+ [K in keyof T]?: T[K] extends BevelColors | null ? T[K] : T[K] extends object ? DeepPartial<T[K]> : T[K];
1093
1098
  };
1094
1099
  type ThemeConfig = DeepPartial<ThemeValues>;
1095
1100
  declare const DefaultTheme: Readonly<ThemeValues>;
package/dist/index.d.ts CHANGED
@@ -227,9 +227,11 @@ declare class Port {
227
227
  slots(): number;
228
228
  setHidden(hidden: boolean): void;
229
229
  /**
230
- * A port asked to hide still shows while anything is connected to it.
230
+ * A port asked to hide still shows while anything is connected to it,
231
+ * and while its node is showing its hidden ports.
231
232
  */
232
233
  hidden(): boolean;
234
+ tucked(): boolean;
233
235
  addConnectionRemovedListener(callback: ConnectionChangeCallback): void;
234
236
  clearConnection(connection: Connection): void;
235
237
  getDataType(): string;
@@ -644,6 +646,9 @@ declare class FlowNode {
644
646
  calculateBounds(ctx: CanvasRenderingContext2D, camera: Camera): Box;
645
647
  addInput(config: PortConfig): Port;
646
648
  addOutput(config: PortConfig): Port;
649
+ hiddenPortsShown(): boolean;
650
+ setHiddenPortsShown(shown: boolean): void;
651
+ hiddenPortCount(): number;
647
652
  addWidget(widget: Widget): void;
648
653
  insertWidget(index: number, widget: Widget): void;
649
654
  removeWidget(widget: Widget): void;
@@ -1089,7 +1094,7 @@ declare const defaults: {
1089
1094
  };
1090
1095
  type ThemeValues = typeof defaults;
1091
1096
  type DeepPartial<T> = {
1092
- [K in keyof T]?: T[K] extends object | null ? (null extends T[K] ? T[K] : DeepPartial<T[K]>) : T[K];
1097
+ [K in keyof T]?: T[K] extends BevelColors | null ? T[K] : T[K] extends object ? DeepPartial<T[K]> : T[K];
1093
1098
  };
1094
1099
  type ThemeConfig = DeepPartial<ThemeValues>;
1095
1100
  declare const DefaultTheme: Readonly<ThemeValues>;
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,117 +1942,6 @@ var Port = class {
1939
1942
  }
1940
1943
  };
1941
1944
 
1942
- // src/styles/stroke.ts
1943
- function StrokeStyleWithFallback(input, fallback) {
1944
- return {
1945
- color: input?.color === void 0 ? fallback?.color : input?.color,
1946
- size: input?.size === void 0 ? fallback?.size : input?.size
1947
- };
1948
- }
1949
- var StrokeStyle = class {
1950
- #size;
1951
- #color;
1952
- constructor(config) {
1953
- this.#size = config?.size === void 0 ? 0.5 : config.size;
1954
- this.#color = config?.color === void 0 ? "black" : config.color;
1955
- }
1956
- setupStyle(ctx, scale) {
1957
- ctx.strokeStyle = this.#color;
1958
- ctx.lineWidth = scale * this.#size;
1959
- }
1960
- setColor(newColor) {
1961
- this.#color = newColor;
1962
- }
1963
- setSize(newSize) {
1964
- this.#size = newSize;
1965
- }
1966
- size() {
1967
- return this.#size;
1968
- }
1969
- };
1970
-
1971
- // src/styles/box.ts
1972
- function BoxStyleWithFallback(input, fallback) {
1973
- return {
1974
- radius: input?.radius === void 0 ? fallback?.radius : input?.radius,
1975
- color: input?.color === void 0 ? fallback?.color : input?.color,
1976
- border: StrokeStyleWithFallback(input?.border, fallback?.border)
1977
- };
1978
- }
1979
- var BoxStyle = class {
1980
- #border;
1981
- #color;
1982
- #radius;
1983
- constructor(config) {
1984
- this.#color = config?.color === void 0 ? "#CCCCCC" : config.color;
1985
- this.#border = config?.border === void 0 ? null : new StrokeStyle(config.border);
1986
- this.#radius = config?.radius === void 0 ? 2 : config.radius;
1987
- }
1988
- #box(ctx, box, scale, radius) {
1989
- ctx.fillStyle = this.#color;
1990
- this.#border?.setupStyle(ctx, scale);
1991
- ctx.beginPath();
1992
- ctx.roundRect(
1993
- box.Position.x,
1994
- box.Position.y,
1995
- box.Size.x,
1996
- box.Size.y,
1997
- radius
1998
- );
1999
- ctx.fill();
2000
- }
2001
- #draw(ctx, box, scale, radius) {
2002
- this.#box(ctx, box, scale, radius);
2003
- ctx.stroke();
2004
- }
2005
- Outline(ctx, box, scale, radius) {
2006
- this.#border?.setupStyle(ctx, scale);
2007
- ctx.beginPath();
2008
- ctx.roundRect(
2009
- box.Position.x,
2010
- box.Position.y,
2011
- box.Size.x,
2012
- box.Size.y,
2013
- radius
2014
- );
2015
- ctx.stroke();
2016
- }
2017
- Draw(ctx, box, scale) {
2018
- this.#draw(ctx, box, scale, this.#radius * scale);
2019
- }
2020
- DrawRoundedTopOnly(ctx, box, scale) {
2021
- this.#draw(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0]);
2022
- }
2023
- DrawUnderline(ctx, box, scale) {
2024
- this.#box(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0]);
2025
- ctx.beginPath();
2026
- ctx.moveTo(box.Position.x, box.Position.y + box.Size.y);
2027
- ctx.lineTo(box.Position.x + box.Size.x, box.Position.y + box.Size.y);
2028
- ctx.stroke();
2029
- }
2030
- borderSize() {
2031
- if (this.#border === null) {
2032
- return 0;
2033
- }
2034
- return this.#border.size();
2035
- }
2036
- radius() {
2037
- return this.#radius;
2038
- }
2039
- setColor(color) {
2040
- this.#color = color;
2041
- }
2042
- setBorderColor(color) {
2043
- if (this.#border === null) {
2044
- this.#border = new StrokeStyle({
2045
- color
2046
- });
2047
- } else {
2048
- this.#border.setColor(color);
2049
- }
2050
- }
2051
- };
2052
-
2053
1945
  // src/styles/cursor.ts
2054
1946
  var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
2055
1947
  CursorStyle2["Default"] = "default";
@@ -2071,97 +1963,6 @@ var CursorStyle = /* @__PURE__ */ ((CursorStyle2) => {
2071
1963
  return CursorStyle2;
2072
1964
  })(CursorStyle || {});
2073
1965
 
2074
- // src/styles/bevel.ts
2075
- function drawBevel(ctx, box, scale, colors, sunken) {
2076
- const width2 = Math.max(1, 2 * scale);
2077
- const half = width2 / 2;
2078
- const left = box.Position.x + half;
2079
- const top = box.Position.y + half;
2080
- const right = box.Position.x + box.Size.x - half;
2081
- const bottom = box.Position.y + box.Size.y - half;
2082
- ctx.save();
2083
- ctx.lineWidth = width2;
2084
- ctx.lineCap = "square";
2085
- ctx.strokeStyle = sunken ? colors.Dark : colors.Light;
2086
- ctx.beginPath();
2087
- ctx.moveTo(left, bottom);
2088
- ctx.lineTo(left, top);
2089
- ctx.lineTo(right, top);
2090
- ctx.stroke();
2091
- ctx.strokeStyle = sunken ? colors.Light : colors.Dark;
2092
- ctx.beginPath();
2093
- ctx.moveTo(right, top);
2094
- ctx.lineTo(right, bottom);
2095
- ctx.lineTo(left, bottom);
2096
- ctx.stroke();
2097
- ctx.restore();
2098
- }
2099
-
2100
- // src/widgets/field.ts
2101
- function widgetFont(scale) {
2102
- return `${Theme.Widget.FontSize * scale}px ${Theme.FontFamily}`;
2103
- }
2104
- function fieldBackground(look) {
2105
- if (look.background !== void 0) {
2106
- return look.background;
2107
- }
2108
- if (look.input) {
2109
- return look.hovered ? Theme.Widget.Input.HoverBackgroundColor : Theme.Widget.Input.BackgroundColor;
2110
- }
2111
- if (look.pressed) {
2112
- return Theme.Widget.Button.Click.BackgroundColor;
2113
- }
2114
- return look.hovered ? Theme.Widget.Hover.BackgroundColor : Theme.Widget.BackgroundColor;
2115
- }
2116
- function drawFieldBox(ctx, box, scale, look) {
2117
- ctx.fillStyle = fieldBackground(look);
2118
- ctx.beginPath();
2119
- ctx.roundRect(box.Position.x, box.Position.y, box.Size.x, box.Size.y, Theme.Widget.Border.Radius * scale);
2120
- ctx.fill();
2121
- if (Theme.Widget.Border.Size > 0) {
2122
- ctx.strokeStyle = look.border ?? Theme.Widget.Border.Color;
2123
- ctx.lineWidth = Theme.Widget.Border.Size * scale;
2124
- ctx.stroke();
2125
- }
2126
- if (Theme.Widget.Bevel !== null) {
2127
- drawBevel(ctx, box, scale, Theme.Widget.Bevel, look.input === true || look.pressed === true);
2128
- }
2129
- }
2130
- function drawField(ctx, box, scale, look) {
2131
- drawFieldBox(ctx, box, scale, look);
2132
- drawFieldText(ctx, box, scale, look);
2133
- }
2134
- function drawFieldText(ctx, box, scale, look) {
2135
- const padding = Theme.Widget.Padding * scale;
2136
- const middle = box.Position.y + box.Size.y / 2;
2137
- const textColor = look.textColor ?? (look.input ? Theme.Widget.Input.FontColor : Theme.Widget.FontColor);
2138
- ctx.font = widgetFont(scale);
2139
- ctx.textBaseline = "middle";
2140
- if (look.label === void 0 || look.label === "") {
2141
- ctx.textAlign = "center";
2142
- ctx.fillStyle = textColor;
2143
- ctx.fillText(
2144
- fitString(ctx, look.value, box.Size.x - padding * 2),
2145
- box.Position.x + box.Size.x / 2,
2146
- middle
2147
- );
2148
- return;
2149
- }
2150
- const room = box.Size.x - padding * 2;
2151
- const label = fitString(ctx, look.label, room / 2);
2152
- const labelWidth = ctx.measureText(label).width;
2153
- ctx.textAlign = "left";
2154
- ctx.fillStyle = look.textColor ?? Theme.Widget.LabelColor;
2155
- ctx.fillText(label, box.Position.x + padding, middle);
2156
- ctx.textAlign = "right";
2157
- ctx.fillStyle = textColor;
2158
- ctx.fillText(
2159
- fitString(ctx, look.value, room - labelWidth - padding),
2160
- box.Position.x + box.Size.x - padding,
2161
- middle
2162
- );
2163
- }
2164
-
2165
1966
  // src/widgets/inplace.ts
2166
1967
  var open = /* @__PURE__ */ new Set();
2167
1968
  function placeEditsDuring(canvas, draw) {
@@ -2382,6 +2183,242 @@ function startingWidgetValue(node, property, configured, fallback) {
2382
2183
  return fallback;
2383
2184
  }
2384
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
+
2220
+ // src/styles/stroke.ts
2221
+ function StrokeStyleWithFallback(input, fallback) {
2222
+ return {
2223
+ color: input?.color === void 0 ? fallback?.color : input?.color,
2224
+ size: input?.size === void 0 ? fallback?.size : input?.size
2225
+ };
2226
+ }
2227
+ var StrokeStyle = class {
2228
+ #size;
2229
+ #color;
2230
+ constructor(config) {
2231
+ this.#size = config?.size === void 0 ? 0.5 : config.size;
2232
+ this.#color = config?.color === void 0 ? "black" : config.color;
2233
+ }
2234
+ setupStyle(ctx, scale) {
2235
+ ctx.strokeStyle = this.#color;
2236
+ ctx.lineWidth = scale * this.#size;
2237
+ }
2238
+ setColor(newColor) {
2239
+ this.#color = newColor;
2240
+ }
2241
+ setSize(newSize) {
2242
+ this.#size = newSize;
2243
+ }
2244
+ size() {
2245
+ return this.#size;
2246
+ }
2247
+ };
2248
+
2249
+ // src/styles/box.ts
2250
+ function BoxStyleWithFallback(input, fallback) {
2251
+ return {
2252
+ radius: input?.radius === void 0 ? fallback?.radius : input?.radius,
2253
+ color: input?.color === void 0 ? fallback?.color : input?.color,
2254
+ border: StrokeStyleWithFallback(input?.border, fallback?.border)
2255
+ };
2256
+ }
2257
+ var BoxStyle = class {
2258
+ #border;
2259
+ #color;
2260
+ #radius;
2261
+ constructor(config) {
2262
+ this.#color = config?.color === void 0 ? "#CCCCCC" : config.color;
2263
+ this.#border = config?.border === void 0 ? null : new StrokeStyle(config.border);
2264
+ this.#radius = config?.radius === void 0 ? 2 : config.radius;
2265
+ }
2266
+ #box(ctx, box, scale, radius) {
2267
+ ctx.fillStyle = this.#color;
2268
+ this.#border?.setupStyle(ctx, scale);
2269
+ ctx.beginPath();
2270
+ ctx.roundRect(
2271
+ box.Position.x,
2272
+ box.Position.y,
2273
+ box.Size.x,
2274
+ box.Size.y,
2275
+ radius
2276
+ );
2277
+ ctx.fill();
2278
+ }
2279
+ #draw(ctx, box, scale, radius) {
2280
+ this.#box(ctx, box, scale, radius);
2281
+ ctx.stroke();
2282
+ }
2283
+ Outline(ctx, box, scale, radius) {
2284
+ this.#border?.setupStyle(ctx, scale);
2285
+ ctx.beginPath();
2286
+ ctx.roundRect(
2287
+ box.Position.x,
2288
+ box.Position.y,
2289
+ box.Size.x,
2290
+ box.Size.y,
2291
+ radius
2292
+ );
2293
+ ctx.stroke();
2294
+ }
2295
+ Draw(ctx, box, scale) {
2296
+ this.#draw(ctx, box, scale, this.#radius * scale);
2297
+ }
2298
+ DrawRoundedTopOnly(ctx, box, scale) {
2299
+ this.#draw(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0]);
2300
+ }
2301
+ DrawUnderline(ctx, box, scale) {
2302
+ this.#box(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0]);
2303
+ ctx.beginPath();
2304
+ ctx.moveTo(box.Position.x, box.Position.y + box.Size.y);
2305
+ ctx.lineTo(box.Position.x + box.Size.x, box.Position.y + box.Size.y);
2306
+ ctx.stroke();
2307
+ }
2308
+ borderSize() {
2309
+ if (this.#border === null) {
2310
+ return 0;
2311
+ }
2312
+ return this.#border.size();
2313
+ }
2314
+ radius() {
2315
+ return this.#radius;
2316
+ }
2317
+ setColor(color) {
2318
+ this.#color = color;
2319
+ }
2320
+ setBorderColor(color) {
2321
+ if (this.#border === null) {
2322
+ this.#border = new StrokeStyle({
2323
+ color
2324
+ });
2325
+ } else {
2326
+ this.#border.setColor(color);
2327
+ }
2328
+ }
2329
+ };
2330
+
2331
+ // src/styles/bevel.ts
2332
+ function drawBevel(ctx, box, scale, colors, sunken) {
2333
+ const width2 = Math.max(1, 2 * scale);
2334
+ const half = width2 / 2;
2335
+ const left = box.Position.x + half;
2336
+ const top = box.Position.y + half;
2337
+ const right = box.Position.x + box.Size.x - half;
2338
+ const bottom = box.Position.y + box.Size.y - half;
2339
+ ctx.save();
2340
+ ctx.lineWidth = width2;
2341
+ ctx.lineCap = "square";
2342
+ ctx.strokeStyle = sunken ? colors.Dark : colors.Light;
2343
+ ctx.beginPath();
2344
+ ctx.moveTo(left, bottom);
2345
+ ctx.lineTo(left, top);
2346
+ ctx.lineTo(right, top);
2347
+ ctx.stroke();
2348
+ ctx.strokeStyle = sunken ? colors.Light : colors.Dark;
2349
+ ctx.beginPath();
2350
+ ctx.moveTo(right, top);
2351
+ ctx.lineTo(right, bottom);
2352
+ ctx.lineTo(left, bottom);
2353
+ ctx.stroke();
2354
+ ctx.restore();
2355
+ }
2356
+
2357
+ // src/widgets/field.ts
2358
+ function widgetFont(scale) {
2359
+ return `${Theme.Widget.FontSize * scale}px ${Theme.FontFamily}`;
2360
+ }
2361
+ function fieldBackground(look) {
2362
+ if (look.background !== void 0) {
2363
+ return look.background;
2364
+ }
2365
+ if (look.input) {
2366
+ return look.hovered ? Theme.Widget.Input.HoverBackgroundColor : Theme.Widget.Input.BackgroundColor;
2367
+ }
2368
+ if (look.pressed) {
2369
+ return Theme.Widget.Button.Click.BackgroundColor;
2370
+ }
2371
+ return look.hovered ? Theme.Widget.Hover.BackgroundColor : Theme.Widget.BackgroundColor;
2372
+ }
2373
+ function drawFieldBox(ctx, box, scale, look) {
2374
+ ctx.fillStyle = fieldBackground(look);
2375
+ ctx.beginPath();
2376
+ ctx.roundRect(box.Position.x, box.Position.y, box.Size.x, box.Size.y, Theme.Widget.Border.Radius * scale);
2377
+ ctx.fill();
2378
+ if (Theme.Widget.Border.Size > 0) {
2379
+ ctx.strokeStyle = look.border ?? Theme.Widget.Border.Color;
2380
+ ctx.lineWidth = Theme.Widget.Border.Size * scale;
2381
+ ctx.stroke();
2382
+ }
2383
+ if (Theme.Widget.Bevel !== null) {
2384
+ drawBevel(ctx, box, scale, Theme.Widget.Bevel, look.input === true || look.pressed === true);
2385
+ }
2386
+ }
2387
+ function drawField(ctx, box, scale, look) {
2388
+ drawFieldBox(ctx, box, scale, look);
2389
+ drawFieldText(ctx, box, scale, look);
2390
+ }
2391
+ function drawFieldText(ctx, box, scale, look) {
2392
+ const padding = Theme.Widget.Padding * scale;
2393
+ const middle = box.Position.y + box.Size.y / 2;
2394
+ const textColor = look.textColor ?? (look.input ? Theme.Widget.Input.FontColor : Theme.Widget.FontColor);
2395
+ ctx.font = widgetFont(scale);
2396
+ ctx.textBaseline = "middle";
2397
+ if (look.label === void 0 || look.label === "") {
2398
+ ctx.textAlign = "center";
2399
+ ctx.fillStyle = textColor;
2400
+ ctx.fillText(
2401
+ fitString(ctx, look.value, box.Size.x - padding * 2),
2402
+ box.Position.x + box.Size.x / 2,
2403
+ middle
2404
+ );
2405
+ return;
2406
+ }
2407
+ const room = box.Size.x - padding * 2;
2408
+ const label = fitString(ctx, look.label, room / 2);
2409
+ const labelWidth = ctx.measureText(label).width;
2410
+ ctx.textAlign = "left";
2411
+ ctx.fillStyle = look.textColor ?? Theme.Widget.LabelColor;
2412
+ ctx.fillText(label, box.Position.x + padding, middle);
2413
+ ctx.textAlign = "right";
2414
+ ctx.fillStyle = textColor;
2415
+ ctx.fillText(
2416
+ fitString(ctx, look.value, room - labelWidth - padding),
2417
+ box.Position.x + box.Size.x - padding,
2418
+ middle
2419
+ );
2420
+ }
2421
+
2385
2422
  // src/widgets/value.ts
2386
2423
  var ValueWidget = class extends WidgetBase {
2387
2424
  #node;
@@ -5638,7 +5675,8 @@ var FlowNode = class {
5638
5675
  size.y += this.#portRowHeight(port, tempMeasurement.y) + this.#elementSpacing;
5639
5676
  size.x = Math.max(size.x, tempMeasurement.x + doublePadding);
5640
5677
  }
5641
- for (const widget of this.#widgets) {
5678
+ const widgets = this.#hasHiddenPortsToggle() ? [this.#hiddenPortsToggle, ...this.#widgets] : this.#widgets;
5679
+ for (const widget of widgets) {
5642
5680
  if (widget.Hidden?.()) {
5643
5681
  continue;
5644
5682
  }
@@ -5664,6 +5702,31 @@ var FlowNode = class {
5664
5702
  this.#output.push(port);
5665
5703
  return port;
5666
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
+ }
5667
5730
  addWidget(widget) {
5668
5731
  this.#widgets.push(widget);
5669
5732
  requestRender();
@@ -5742,6 +5805,11 @@ var FlowNode = class {
5742
5805
  intersection.Port = this.#output[i];
5743
5806
  }
5744
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
+ }
5745
5813
  for (let i = 0; i < this.#widgetPositions.Count(); i++) {
5746
5814
  const widget = this.#widgets[i];
5747
5815
  if (widget !== void 0 && !widget.Hidden?.() && InBox(this.#widgetPositions.At(i), position)) {
@@ -6011,6 +6079,16 @@ var FlowNode = class {
6011
6079
  this.#outputPortPositions.Push(port.render(ctx, position, camera, mousePosition, postProcess));
6012
6080
  startY += portHeight + scaledElementSpacing;
6013
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
+ }
6014
6092
  for (let i = 0; i < this.#widgets.length; i++) {
6015
6093
  const widget = this.#widgets[i];
6016
6094
  if (widget.Hidden?.()) {
@@ -6065,6 +6143,7 @@ var FlowNode = class {
6065
6143
  return false;
6066
6144
  }
6067
6145
  this.#widgetPositions.Clear();
6146
+ this.#hiddenPortsTogglePosition = null;
6068
6147
  return true;
6069
6148
  }
6070
6149
  placePorts(bounds, zoom) {
@@ -7725,8 +7804,8 @@ var NodeFlowGraph = class {
7725
7804
  this.#clickEnd.bind(this),
7726
7805
  this.#openContextMenu.bind(this),
7727
7806
  this.#fileDrop.bind(this),
7728
- (amount, anchor) => {
7729
- this.zoom(amount, anchor);
7807
+ (factor, anchor) => {
7808
+ this.#zoomBy(factor, anchor);
7730
7809
  }
7731
7810
  );
7732
7811
  document.addEventListener(
@@ -7854,12 +7933,15 @@ var NodeFlowGraph = class {
7854
7933
  this.#mainNoteSubsystem.addNoteDragStopListener(callback);
7855
7934
  }
7856
7935
  zoom(amount, anchor) {
7936
+ this.#zoomBy(1 + amount * 0.05, anchor);
7937
+ }
7938
+ #zoomBy(factor, anchor) {
7857
7939
  const anchorPos = anchor ?? this.#mousePosition;
7858
7940
  let oldPos = void 0;
7859
7941
  if (anchorPos !== void 0) {
7860
7942
  oldPos = this.#sceenPositionToGraphPosition(anchorPos);
7861
7943
  }
7862
- this.#camera.zoom += amount * this.#camera.zoom * 0.05;
7944
+ this.#camera.zoom *= factor;
7863
7945
  if (!oldPos || anchorPos === void 0) {
7864
7946
  return;
7865
7947
  }