@elicdavis/node-flow 0.1.15 → 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.
Files changed (43) hide show
  1. package/.mocharc.json +5 -0
  2. package/dist/index.d.mts +139 -4
  3. package/dist/index.d.ts +139 -4
  4. package/dist/index.js +3754 -3352
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +3741 -3352
  7. package/dist/index.mjs.map +1 -1
  8. package/index.html +0 -30
  9. package/package.json +4 -2
  10. package/src/graph.ts +234 -41
  11. package/src/index.ts +82 -40
  12. package/src/input.ts +22 -14
  13. package/src/markdown/entry.ts +26 -3
  14. package/src/markdown/lexicalParser.ts +22 -32
  15. package/src/markdown/syntaxParser.ts +93 -68
  16. package/src/minimap.ts +178 -0
  17. package/src/node.ts +114 -0
  18. package/src/nodes/factory.ts +9 -5
  19. package/src/nodes/publisher.ts +39 -4
  20. package/src/nodes/subsystem.ts +218 -8
  21. package/src/notes/subsystem.ts +3 -0
  22. package/src/organize.ts +276 -181
  23. package/src/port.ts +143 -7
  24. package/src/render_scheduler.ts +49 -0
  25. package/src/styles/text.ts +8 -0
  26. package/src/theme.ts +8 -0
  27. package/src/types/text.ts +8 -4
  28. package/src/utils/string.ts +7 -1
  29. package/src/widgets/color.ts +5 -6
  30. package/src/widgets/factory.ts +1 -1
  31. package/src/widgets/image.ts +2 -0
  32. package/src/widgets/number.ts +2 -2
  33. package/src/widgets/slider.ts +2 -2
  34. package/src/widgets/string.ts +2 -2
  35. package/src/widgets/toggle.ts +2 -2
  36. package/src/widgets/widget.ts +22 -0
  37. package/test/markdown.test.ts +165 -0
  38. package/test/minimap.test.ts +103 -0
  39. package/test/node_menu_filter.test.ts +119 -0
  40. package/test/organize.test.ts +156 -0
  41. package/test/port.test.ts +125 -0
  42. package/test/render_scheduler.test.ts +119 -0
  43. package/tsconfig.json +6 -2
package/index.html CHANGED
@@ -312,40 +312,10 @@ node.addOutput({ name: "sum", type: "float32" })
312
312
  ],
313
313
  });
314
314
 
315
- const manyPortsNode = new FlowNode({
316
- position: {
317
- x: 1200,
318
- y: 450
319
- },
320
- title: "Many Ports",
321
- subTitle: "Zoom stress test",
322
- info: "Node with lots of ports — zoom in/out to check height stability",
323
- inputs: [
324
- { name: "in 0", type: "float32" },
325
- { name: "in 1", type: "float32" },
326
- { name: "in 2", type: "float32" },
327
- { name: "in 3", type: "float32" },
328
- { name: "in 4", type: "float32" },
329
- { name: "in 5", type: "float32" },
330
- { name: "in 6", type: "float32" },
331
- { name: "in 7", type: "float32" },
332
- { name: "in 8", type: "float32" },
333
- { name: "in 9", type: "float32" },
334
- ],
335
- outputs: [
336
- { name: "out 0", type: "float32" },
337
- { name: "out 1", type: "float32" },
338
- { name: "out 2", type: "float32" },
339
- { name: "out 3", type: "float32" },
340
- { name: "out 4", type: "float32" },
341
- ],
342
- });
343
-
344
315
  graph.addNode(sumNode)
345
316
  graph.addNode(aNode)
346
317
  graph.addNode(bNode)
347
318
  graph.addNode(arrNode)
348
- graph.addNode(manyPortsNode)
349
319
 
350
320
  graph.connectNodes(aNode, 0, sumNode, 0)
351
321
  graph.connectNodes(bNode, 0, sumNode, 1)
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@elicdavis/node-flow",
3
3
  "description": "build node graphs",
4
4
  "author": "Eli C Davis",
5
- "version": "0.1.15",
5
+ "version": "0.1.17",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "nodes",
@@ -15,7 +15,9 @@
15
15
  "scripts": {
16
16
  "build": "ts-node ./esbuild.ts",
17
17
  "watch": "npm run build -- --watch",
18
- "package": "tsup"
18
+ "package": "tsup",
19
+ "test": "mocha",
20
+ "typecheck": "tsc --noEmit"
19
21
  },
20
22
  "homepage": "https://github.com/EliCDavis/node-flow",
21
23
  "repository": {
package/src/graph.ts CHANGED
@@ -10,7 +10,7 @@ import { Clamp01 } from "./utils/math";
10
10
  import { GraphSubsystem, RenderResults } from './graphSubsystem';
11
11
  import { FlowNote } from "./notes/note";
12
12
  import { NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback, NoteRemovedCallback, NoteSubsystem, NoteSubsystemConfig } from "./notes/subsystem";
13
- import { ConnectionRendererConfiguration, NodeAddedCallback, NodeRemovedCallback, NodeSubsystem } from "./nodes/subsystem";
13
+ import { ConnectionRendererConfiguration, DanglingConnection, InternalConnection, NodeAddedCallback, NodeRemovedCallback, NodeSubsystem } from "./nodes/subsystem";
14
14
  import { Connection } from './connection';
15
15
  import { Publisher } from './nodes/publisher';
16
16
  import { VectorPool } from './types/pool';
@@ -18,6 +18,8 @@ import { Camera, CameraOrientation } from './camera';
18
18
  export { CameraOrientation };
19
19
  import { PassSubsystem } from './pass/subsystem';
20
20
  import { QuickMenu } from './quickMenu';
21
+ import { onRenderRequested, renderContinuously, requestRender } from './render_scheduler';
22
+ import { Minimap, MinimapConfig } from './minimap';
21
23
 
22
24
  export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
23
25
 
@@ -49,6 +51,10 @@ function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
49
51
 
50
52
  const contextMenuGroup = "graph-context-menu";
51
53
 
54
+ export type CopyCallback = (nodes: Array<FlowNode>, connections: Array<InternalConnection>) => void;
55
+
56
+ export type PasteCallback = (position: Vector2) => void;
57
+
52
58
  export interface FlowNodeGraphConfiguration {
53
59
  backgroundRenderer?: GraphRenderer;
54
60
  backgroundColor?: string;
@@ -56,6 +62,7 @@ export interface FlowNodeGraphConfiguration {
56
62
  contextMenu?: ContextMenuConfig
57
63
  nodes?: NodeFactoryConfig
58
64
  board?: NoteSubsystemConfig
65
+ minimap?: MinimapConfig
59
66
  }
60
67
 
61
68
  interface OpenContextMenu {
@@ -221,7 +228,15 @@ export class NodeFlowGraph {
221
228
  this.#backgroundRenderer = BuildBackgroundRenderer(backgroundColor);
222
229
  }
223
230
 
224
- window.requestAnimationFrame(this.#render.bind(this));
231
+ this.#minimap = new Minimap(config?.minimap);
232
+
233
+ this.#mainNodeSubsystem.setConnectionReleasedHandler((dangling) => {
234
+ this.#openScopedMenu(dangling);
235
+ });
236
+
237
+ this.#watchCanvasSize();
238
+ this.#stopRendering = onRenderRequested(this.#render.bind(this));
239
+ requestRender();
225
240
 
226
241
  new MouseObserver(this.#canvas,
227
242
  this.#mouseDragEvent.bind(this),
@@ -240,45 +255,115 @@ export class NodeFlowGraph {
240
255
  document.addEventListener(
241
256
  "keydown",
242
257
  (e) => {
243
- if (document.activeElement) {
244
- if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
245
- }
258
+ this.#keyDown(e);
259
+ requestRender();
260
+ }
261
+ );
262
+ }
246
263
 
247
- if (this.#openQuickMenu) {
248
- if (e.code === "Escape") {
249
- this.#openQuickMenu = null;
250
- return;
251
- }
264
+ #copyCallbacks: Array<CopyCallback> = [];
252
265
 
253
- if (e.code === "Enter") {
254
- this.#openQuickMenu.Menu.execute();
255
- this.#openQuickMenu = null;
256
- return;
257
- }
266
+ #pasteCallbacks: Array<PasteCallback> = [];
258
267
 
259
- this.#openQuickMenu.Menu.keyboardEvent(e);
260
- return
261
- }
268
+ public addCopyListener(callback: CopyCallback): void {
269
+ this.#copyCallbacks.push(callback);
270
+ }
271
+
272
+ public addPasteListener(callback: PasteCallback): void {
273
+ this.#pasteCallbacks.push(callback);
274
+ }
275
+
276
+ #raiseCopy(): void {
277
+ const nodes = this.#mainNodeSubsystem.getSelectedNodes();
278
+ if (nodes.length === 0) {
279
+ return;
280
+ }
281
+
282
+ const connections = this.#mainNodeSubsystem.connectionsWithin(nodes);
283
+ this.#copyCallbacks.forEach((callback) => callback(nodes, connections));
284
+ }
285
+
286
+ #raisePaste(): void {
287
+ const position = this.#mousePosition === undefined
288
+ ? { x: 0, y: 0 }
289
+ : this.#sceenPositionToGraphPosition(this.#mousePosition);
290
+
291
+ this.#pasteCallbacks.forEach((callback) => callback(position));
292
+ }
293
+
294
+ #deleteKey(e: KeyboardEvent): boolean {
295
+ return e.code === "Delete" || e.code === "Backspace" || e.key === "Delete" || e.key === "Backspace";
296
+ }
297
+
298
+ #keyDown(e: KeyboardEvent): void {
299
+ if (document.activeElement) {
300
+ if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
301
+ }
302
+
303
+ if (this.#deleteKey(e)) {
304
+ if (this.#mainNodeSubsystem.getSelectedNodes().length > 0) {
305
+ e.preventDefault();
306
+ this.#mainNodeSubsystem.removeSelectedNodes();
307
+ return;
308
+ }
309
+ }
310
+
311
+ if (e.ctrlKey || e.metaKey) {
312
+ const letter = e.code.startsWith("Key")
313
+ ? e.code.substring(3).toLowerCase()
314
+ : (e.key ?? "").toLowerCase();
262
315
 
263
- const spacePressed = e.key == " " || e.code == "Space";
264
- if (!spacePressed) {
316
+ switch (letter) {
317
+ case "c":
318
+ this.#raiseCopy();
265
319
  return;
266
- }
267
320
 
268
- if (!this.#mousePosition) {
321
+ case "v":
322
+ this.#raisePaste();
323
+ return;
324
+
325
+ // duplicate
326
+ case "d":
327
+ e.preventDefault();
328
+ this.#raiseCopy();
329
+ this.#raisePaste();
269
330
  return;
270
- }
271
- const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
272
-
273
- let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
274
- this.#openQuickMenu = {
275
- Menu: new QuickMenu({
276
- subMenus: items,
277
- }),
278
- Position: contextMenuPosition
279
- }
280
331
  }
281
- );
332
+ }
333
+
334
+ if (this.#openQuickMenu) {
335
+ if (e.code === "Escape") {
336
+ this.#openQuickMenu = null;
337
+ return;
338
+ }
339
+
340
+ if (e.code === "Enter") {
341
+ this.#openQuickMenu.Menu.execute();
342
+ this.#openQuickMenu = null;
343
+ return;
344
+ }
345
+
346
+ this.#openQuickMenu.Menu.keyboardEvent(e);
347
+ return
348
+ }
349
+
350
+ const spacePressed = e.key == " " || e.code == "Space";
351
+ if (!spacePressed) {
352
+ return;
353
+ }
354
+
355
+ if (!this.#mousePosition) {
356
+ return;
357
+ }
358
+ const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
359
+
360
+ let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
361
+ this.#openQuickMenu = {
362
+ Menu: new QuickMenu({
363
+ subMenus: items,
364
+ }),
365
+ Position: contextMenuPosition
366
+ }
282
367
  }
283
368
 
284
369
  #fileDrop(file: File): void {
@@ -362,9 +447,33 @@ export class NodeFlowGraph {
362
447
  this.#contextMenuEntryHovering = null;
363
448
 
364
449
  this.#mousePosition = mousePosition;
450
+
451
+ if (this.#minimap.contains(mousePosition)) {
452
+ this.#lookAtFromMinimap(mousePosition);
453
+ this.#draggingMinimap = true;
454
+ return;
455
+ }
456
+
365
457
  this.currentView().clickStart(mousePosition, this.#camera, ctrlKey);
366
458
  }
367
459
 
460
+ #draggingMinimap: boolean = false;
461
+
462
+ #lookAtFromMinimap(mousePosition: Vector2): void {
463
+ const target: Vector2 = { x: 0, y: 0 };
464
+ this.#minimap.graphPositionOf(mousePosition, target);
465
+
466
+ // Centre the viewport on it rather than putting it in the corner.
467
+ this.#camera.position.x = -(target.x * this.#camera.zoom) + (this.#canvas.width / 2);
468
+ this.#camera.position.y = -(target.y * this.#camera.zoom) + (this.#canvas.height / 2);
469
+ requestRender();
470
+ }
471
+
472
+ /** The minimap, so it can be turned on and sized by the host. */
473
+ public minimap(): Minimap {
474
+ return this.#minimap;
475
+ }
476
+
368
477
  currentView(): GraphView {
369
478
  return this.#views[this.#currentView];
370
479
  }
@@ -389,6 +498,14 @@ export class NodeFlowGraph {
389
498
  return this.#mainNodeSubsystem.getSelectedNodes();
390
499
  }
391
500
 
501
+ public unselectAllNodes(): void {
502
+ this.#mainNodeSubsystem.unselectAllNodes();
503
+ }
504
+
505
+ public removeSelectedNodes(): Array<FlowNode> {
506
+ return this.#mainNodeSubsystem.removeSelectedNodes();
507
+ }
508
+
392
509
  /**
393
510
  * Returns a copy of the current camera orientation (screen-space pan + zoom).
394
511
  */
@@ -533,11 +650,42 @@ export class NodeFlowGraph {
533
650
  };
534
651
  }
535
652
 
653
+ #openScopedMenu(dangling: DanglingConnection): void {
654
+ if (this.#mousePosition === undefined) {
655
+ return;
656
+ }
657
+
658
+ const position = this.#sceenPositionToGraphPosition(this.#mousePosition);
659
+ const menu = this.#mainNodeSubsystem.nodeFactory().openMenu(
660
+ this.#mainNodeSubsystem,
661
+ position,
662
+ {
663
+ needs: dangling.needs,
664
+ types: dangling.types,
665
+ onCreated: dangling.connectTo,
666
+ },
667
+ );
668
+
669
+ this.#openedContextMenu = {
670
+ Menu: new ContextMenu(menu),
671
+ Position: position,
672
+ };
673
+ requestRender();
674
+ }
675
+
536
676
  #clickEnd(): void {
677
+ this.#draggingMinimap = false;
537
678
  this.currentView().clickEnd();
538
679
  }
539
680
 
540
681
  #mouseDragEvent(delta: Vector2): void {
682
+ if (this.#draggingMinimap) {
683
+ if (this.#mousePosition !== undefined) {
684
+ this.#lookAtFromMinimap(this.#mousePosition);
685
+ }
686
+ return;
687
+ }
688
+
541
689
  let draggingSomething = this.currentView().mouseDragEvent(delta, this.#camera.zoom);
542
690
  if (!draggingSomething) {
543
691
  this.#camera.position.x += delta.x;
@@ -549,14 +697,57 @@ export class NodeFlowGraph {
549
697
 
550
698
  #cursor: CursorStyle;
551
699
 
552
- #render(): void {
553
- if (this.#canvas.parentNode !== null) {
554
- // Stupid as any because typescript doesn't think it exists
555
- var rect = (this.#canvas.parentNode as any).getBoundingClientRect();
556
- this.#canvas.width = rect.width;
557
- this.#canvas.height = rect.height;
700
+ #minimap: Minimap;
701
+
702
+ #stopRendering: () => void;
703
+
704
+ #sizeObserver: ResizeObserver | undefined;
705
+
706
+ #matchCanvasToParent(): void {
707
+ const parent = this.#canvas.parentNode as HTMLElement | null;
708
+ if (parent === null) {
709
+ return;
710
+ }
711
+
712
+ const rect = parent.getBoundingClientRect();
713
+ if (this.#canvas.width === rect.width && this.#canvas.height === rect.height) {
714
+ return;
558
715
  }
559
716
 
717
+ this.#canvas.width = rect.width;
718
+ this.#canvas.height = rect.height;
719
+ }
720
+
721
+ #watchCanvasSize(): void {
722
+ this.#matchCanvasToParent();
723
+
724
+ const parent = this.#canvas.parentNode as HTMLElement | null;
725
+ if (parent === null || typeof ResizeObserver === "undefined") {
726
+ return;
727
+ }
728
+
729
+ this.#sizeObserver = new ResizeObserver(() => {
730
+ this.#matchCanvasToParent();
731
+ requestRender();
732
+ });
733
+ this.#sizeObserver.observe(parent);
734
+ }
735
+
736
+ public requestRender(): void {
737
+ requestRender();
738
+ }
739
+
740
+ public renderContinuously(): () => void {
741
+ return renderContinuously();
742
+ }
743
+
744
+ public dispose(): void {
745
+ this.#stopRendering();
746
+ this.#sizeObserver?.disconnect();
747
+ }
748
+
749
+ #render(): void {
750
+ this.#matchCanvasToParent();
560
751
 
561
752
  this.#cursor = CursorStyle.Default;
562
753
 
@@ -569,6 +760,10 @@ export class NodeFlowGraph {
569
760
  }
570
761
  });
571
762
 
763
+ TimeExecution("Render_Minimap", () => {
764
+ this.#minimap.render(this.#ctx, this.#canvas, this.#camera, this.#mainNodeSubsystem.getNodes());
765
+ });
766
+
572
767
  TimeExecution("Render_Context", this.#renderContextMenu.bind(this));
573
768
 
574
769
  TimeExecution("Render_QuickMenu", this.#renderQuickMenu.bind(this))
@@ -579,8 +774,6 @@ export class NodeFlowGraph {
579
774
  this.#canvas.style.cursor = this.#cursor;
580
775
  }
581
776
  this.#lastFrameCursor = this.#cursor;
582
-
583
- window.requestAnimationFrame(this.#render.bind(this));
584
777
  }
585
778
 
586
779
  #renderBackground(): void {
package/src/index.ts CHANGED
@@ -1,40 +1,82 @@
1
- export * from './graph';
2
- export * from './node';
3
-
4
- import { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState } from './contextMenu';
5
- export { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState };
6
-
7
- import { Theme } from './theme';
8
- export { Theme };
9
-
10
- import { Publisher, PublisherConfig } from './nodes/publisher';
11
- export { Publisher, PublisherConfig };
12
-
13
- import { FlowNote, FlowNoteConfig, NoteContentChangeCallback, NoteWidthChangeCallback } from './notes/note';
14
- export { FlowNote, FlowNoteConfig, NoteContentChangeCallback, NoteWidthChangeCallback };
15
-
16
- import {
17
- NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback,
18
- NoteRemovedCallback, NoteSubsystemConfig
19
- } from './notes/subsystem';
20
- export {
21
- NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback,
22
- NoteRemovedCallback, NoteSubsystemConfig
23
- };
24
-
25
- // Widgets
26
- import { NumberWidget } from './widgets/number';
27
- import { ColorWidget } from './widgets/color';
28
- import { StringWidget } from './widgets/string';
29
- import { TextWidget } from './widgets/text';
30
- import { ButtonWidget } from './widgets/button';
31
- import { ToggleWidget } from './widgets/toggle';
32
- import { SliderWidget } from './widgets/slider';
33
- import { ImageWidget } from './widgets/image';
34
- export {
35
- NumberWidget, ColorWidget, StringWidget, TextWidget,
36
- ButtonWidget, ToggleWidget, SliderWidget, ImageWidget
37
- };
38
-
39
- import { GlobalWidgetFactory } from './widgets/factory';
40
- export { GlobalWidgetFactory };
1
+ export * from './graph';
2
+ export * from './node';
3
+
4
+ import { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState } from './contextMenu';
5
+ export { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState };
6
+
7
+ import { Theme } from './theme';
8
+ export { Theme };
9
+
10
+ import { Publisher, PublisherConfig } from './nodes/publisher';
11
+ export { Publisher, PublisherConfig };
12
+
13
+ import { FlowNote, FlowNoteConfig, NoteContentChangeCallback, NoteWidthChangeCallback } from './notes/note';
14
+ export { FlowNote, FlowNoteConfig, NoteContentChangeCallback, NoteWidthChangeCallback };
15
+
16
+ import {
17
+ NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback,
18
+ NoteRemovedCallback, NoteSubsystemConfig
19
+ } from './notes/subsystem';
20
+ export {
21
+ NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback,
22
+ NoteRemovedCallback, NoteSubsystemConfig
23
+ };
24
+
25
+ // Ports
26
+ import { Port, PortConfig, PortStyle, PortType, portsCompatible } from './port';
27
+ export { Port, PortConfig, PortStyle, PortType, portsCompatible };
28
+
29
+ // Rendering
30
+ import { renderContinuously, requestRender } from './render_scheduler';
31
+ export { renderContinuously, requestRender };
32
+
33
+ import { Minimap, MinimapConfig } from './minimap';
34
+ export { Minimap, MinimapConfig };
35
+
36
+ import { DanglingConnection, InternalConnection } from './nodes/subsystem';
37
+ export { DanglingConnection, InternalConnection };
38
+
39
+ import { NodeMenuFilter } from './nodes/publisher';
40
+ export { NodeMenuFilter };
41
+
42
+ import { portConfigTypes, typeSetsCompatible } from './port';
43
+ export { portConfigTypes, typeSetsCompatible };
44
+
45
+ // Connections
46
+ import {
47
+ Connection, ConnectionRenderer, ConnectionRendererParams, DefaultConnectionRenderer
48
+ } from './connection';
49
+ export { Connection, ConnectionRenderer, ConnectionRendererParams, DefaultConnectionRenderer };
50
+
51
+ // Node factory
52
+ import { NodeCreatedCallback, NodeFactory, NodeFactoryConfig } from './nodes/factory';
53
+ export { NodeCreatedCallback, NodeFactory, NodeFactoryConfig };
54
+
55
+ // Widgets
56
+ import { Widget } from './widgets/widget';
57
+ import { NumberWidget, NumberWidgetConfig } from './widgets/number';
58
+ import { ColorWidget, ColorWidgetConfig } from './widgets/color';
59
+ import { StringWidget, StringWidgetConfig } from './widgets/string';
60
+ import { TextWidget, TextWidgetConfig } from './widgets/text';
61
+ import { ButtonWidget, ButtonWidgetConfig } from './widgets/button';
62
+ import { ToggleWidget, ToggleWidgetConfig, ToggleStyleConfig } from './widgets/toggle';
63
+ import { SliderWidget, SliderWidgetConfig } from './widgets/slider';
64
+ import { ImageWidget, ImageWidgetConfig } from './widgets/image';
65
+ export {
66
+ Widget,
67
+ NumberWidget, ColorWidget, StringWidget, TextWidget,
68
+ ButtonWidget, ToggleWidget, SliderWidget, ImageWidget
69
+ };
70
+ export type {
71
+ NumberWidgetConfig, ColorWidgetConfig, StringWidgetConfig, TextWidgetConfig,
72
+ ButtonWidgetConfig, ToggleWidgetConfig, ToggleStyleConfig, SliderWidgetConfig,
73
+ ImageWidgetConfig
74
+ };
75
+
76
+ import { GlobalWidgetFactory, WidgetBuilder, WidgetFactory } from './widgets/factory';
77
+ export { GlobalWidgetFactory, WidgetFactory };
78
+ export type { WidgetBuilder };
79
+
80
+ import { Box } from './types/box';
81
+ import { Vector2 } from './types/vector2';
82
+ export type { Box, Vector2 };
package/src/input.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { requestRender } from "./render_scheduler";
1
2
  import { CopyVector2, Distance, SubVector2, Vector2, Zero } from "./types/vector2";
2
3
 
3
4
  const LONG_PRESS_MS = 500;
@@ -49,19 +50,25 @@ export class MouseObserver {
49
50
 
50
51
  ele.style.touchAction = "none";
51
52
 
52
- ele.addEventListener("mousedown", this.#down.bind(this), false);
53
- ele.addEventListener("touchstart", this.#touchStart.bind(this), { passive: false });
53
+ const redraws = <T extends Event>(handler: (event: T) => void) =>
54
+ (event: T): void => {
55
+ handler(event);
56
+ requestRender();
57
+ };
54
58
 
55
- document.addEventListener("mouseup", this.#up.bind(this), false);
56
- document.addEventListener("touchend", this.#touchEnd.bind(this), false);
57
- document.addEventListener("touchcancel", this.#touchEnd.bind(this), false);
59
+ ele.addEventListener("mousedown", redraws(this.#down.bind(this)), false);
60
+ ele.addEventListener("touchstart", redraws(this.#touchStart.bind(this)), { passive: false });
58
61
 
59
- ele.addEventListener("mousemove", this.#move.bind(this), false);
60
- ele.addEventListener("touchmove", this.#touchMove.bind(this), { passive: false });
62
+ document.addEventListener("mouseup", redraws(this.#up.bind(this)), false);
63
+ document.addEventListener("touchend", redraws(this.#touchEnd.bind(this)), false);
64
+ document.addEventListener("touchcancel", redraws(this.#touchEnd.bind(this)), false);
61
65
 
62
- ele.addEventListener("wheel", this.#wheel.bind(this), { passive: false });
66
+ ele.addEventListener("mousemove", redraws(this.#move.bind(this)), false);
67
+ ele.addEventListener("touchmove", redraws(this.#touchMove.bind(this)), { passive: false });
63
68
 
64
- ele.addEventListener("drop", (ev) => {
69
+ ele.addEventListener("wheel", redraws(this.#wheel.bind(this)), { passive: false });
70
+
71
+ ele.addEventListener("drop", redraws((ev: DragEvent) => {
65
72
  ev.preventDefault();
66
73
  if (ev.dataTransfer?.items) {
67
74
  [...ev.dataTransfer.items].forEach((item, i) => {
@@ -73,21 +80,21 @@ export class MouseObserver {
73
80
  }
74
81
  });
75
82
  }
76
- });
83
+ }));
77
84
 
78
- ele.addEventListener("dragover", (ev) => {
85
+ ele.addEventListener("dragover", redraws((ev: DragEvent) => {
79
86
  ev.preventDefault();
80
87
  this.#moveCallback(this.#mousePos(ev));
81
- });
88
+ }));
82
89
 
83
- ele.addEventListener("contextmenu", (evt) => {
90
+ ele.addEventListener("contextmenu", redraws((evt: MouseEvent) => {
84
91
  evt.preventDefault();
85
92
  // Touch uses long-press; ignore synthetic browser contextmenu after touch.
86
93
  if (Date.now() - this.#lastTouchAt < SUPPRESS_MOUSE_MS) {
87
94
  return;
88
95
  }
89
96
  this.#contextMenu(this.#mousePos(evt));
90
- }, false);
97
+ }), false);
91
98
  }
92
99
 
93
100
  #mousePos(event: MouseEvent | DragEvent | WheelEvent): Vector2 {
@@ -198,6 +205,7 @@ export class MouseObserver {
198
205
  this.#clicked = false;
199
206
  this.#clickStop();
200
207
  this.#contextMenu({ x, y });
208
+ requestRender();
201
209
  }, LONG_PRESS_MS);
202
210
 
203
211
  this.#clicked = true;