@flighthq/gui 0.4.1-edge.28.7f8e221

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 (96) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +17 -0
  3. package/dist/buttonController.d.ts +7 -0
  4. package/dist/buttonController.d.ts.map +1 -0
  5. package/dist/buttonController.js +83 -0
  6. package/dist/buttonController.js.map +1 -0
  7. package/dist/comboBoxController.d.ts +7 -0
  8. package/dist/comboBoxController.d.ts.map +1 -0
  9. package/dist/comboBoxController.js +46 -0
  10. package/dist/comboBoxController.js.map +1 -0
  11. package/dist/contract.d.ts +16 -0
  12. package/dist/contract.d.ts.map +1 -0
  13. package/dist/contract.js +16 -0
  14. package/dist/contract.js.map +1 -0
  15. package/dist/guiController.d.ts +24 -0
  16. package/dist/guiController.d.ts.map +1 -0
  17. package/dist/guiController.js +101 -0
  18. package/dist/guiController.js.map +1 -0
  19. package/dist/guiTestHelper.d.ts +5 -0
  20. package/dist/guiTestHelper.d.ts.map +1 -0
  21. package/dist/guiTestHelper.js +57 -0
  22. package/dist/guiTestHelper.js.map +1 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/listController.d.ts +8 -0
  28. package/dist/listController.d.ts.map +1 -0
  29. package/dist/listController.js +82 -0
  30. package/dist/listController.js.map +1 -0
  31. package/dist/progressBarController.d.ts +6 -0
  32. package/dist/progressBarController.d.ts.map +1 -0
  33. package/dist/progressBarController.js +42 -0
  34. package/dist/progressBarController.js.map +1 -0
  35. package/dist/radioGroupController.d.ts +7 -0
  36. package/dist/radioGroupController.d.ts.map +1 -0
  37. package/dist/radioGroupController.js +50 -0
  38. package/dist/radioGroupController.js.map +1 -0
  39. package/dist/scrollBarController.d.ts +7 -0
  40. package/dist/scrollBarController.d.ts.map +1 -0
  41. package/dist/scrollBarController.js +124 -0
  42. package/dist/scrollBarController.js.map +1 -0
  43. package/dist/scrollViewController.d.ts +8 -0
  44. package/dist/scrollViewController.d.ts.map +1 -0
  45. package/dist/scrollViewController.js +99 -0
  46. package/dist/scrollViewController.js.map +1 -0
  47. package/dist/sliderController.d.ts +7 -0
  48. package/dist/sliderController.d.ts.map +1 -0
  49. package/dist/sliderController.js +105 -0
  50. package/dist/sliderController.js.map +1 -0
  51. package/dist/splitPaneController.d.ts +7 -0
  52. package/dist/splitPaneController.d.ts.map +1 -0
  53. package/dist/splitPaneController.js +87 -0
  54. package/dist/splitPaneController.js.map +1 -0
  55. package/dist/tabBarController.d.ts +7 -0
  56. package/dist/tabBarController.d.ts.map +1 -0
  57. package/dist/tabBarController.js +43 -0
  58. package/dist/tabBarController.js.map +1 -0
  59. package/dist/textInputController.d.ts +9 -0
  60. package/dist/textInputController.d.ts.map +1 -0
  61. package/dist/textInputController.js +132 -0
  62. package/dist/textInputController.js.map +1 -0
  63. package/dist/toggleController.d.ts +7 -0
  64. package/dist/toggleController.d.ts.map +1 -0
  65. package/dist/toggleController.js +57 -0
  66. package/dist/toggleController.js.map +1 -0
  67. package/dist/tooltipController.d.ts +6 -0
  68. package/dist/tooltipController.d.ts.map +1 -0
  69. package/dist/tooltipController.js +62 -0
  70. package/dist/tooltipController.js.map +1 -0
  71. package/dist/treeViewController.d.ts +10 -0
  72. package/dist/treeViewController.d.ts.map +1 -0
  73. package/dist/treeViewController.js +128 -0
  74. package/dist/treeViewController.js.map +1 -0
  75. package/dist/windowController.d.ts +7 -0
  76. package/dist/windowController.d.ts.map +1 -0
  77. package/dist/windowController.js +119 -0
  78. package/dist/windowController.js.map +1 -0
  79. package/package.json +53 -0
  80. package/src/buttonController.test.ts +90 -0
  81. package/src/comboBoxController.test.ts +71 -0
  82. package/src/guiController.test.ts +162 -0
  83. package/src/guiTreeShaking.test.ts +35 -0
  84. package/src/listController.test.ts +96 -0
  85. package/src/progressBarController.test.ts +49 -0
  86. package/src/radioGroupController.test.ts +61 -0
  87. package/src/scrollBarController.test.ts +86 -0
  88. package/src/scrollViewController.test.ts +80 -0
  89. package/src/sliderController.test.ts +74 -0
  90. package/src/splitPaneController.test.ts +85 -0
  91. package/src/tabBarController.test.ts +50 -0
  92. package/src/textInputController.test.ts +93 -0
  93. package/src/toggleController.test.ts +72 -0
  94. package/src/tooltipController.test.ts +56 -0
  95. package/src/treeViewController.test.ts +105 -0
  96. package/src/windowController.test.ts +75 -0
@@ -0,0 +1,119 @@
1
+ import { getNodeHeight, getNodeWidth } from '@flighthq/node/contract';
2
+ import { createSignal, emitSignal } from '@flighthq/signals/contract';
3
+ import { getButtonControllerSignals } from './buttonController';
4
+ import { connectGuiInteraction, connectGuiSignal, createGuiController, createGuiControllerRuntime, disposeGuiController, getGuiControllerRuntime, setGuiVisualProperty, } from './guiController';
5
+ export function createWindowController(options) {
6
+ const width = getNodeWidth(options.frame);
7
+ const height = getNodeHeight(options.frame);
8
+ const runtime = createGuiControllerRuntime({
9
+ closeButton: options.closeButton ?? null,
10
+ content: options.content ?? null,
11
+ dragPointer: -1,
12
+ dragStartFrameX: 0,
13
+ dragStartFrameY: 0,
14
+ dragStartWorldX: 0,
15
+ dragStartWorldY: 0,
16
+ draggable: options.draggable ?? true,
17
+ frame: options.frame,
18
+ frameBaseHeight: height,
19
+ frameBaseScaleX: options.frame.scaleX,
20
+ frameBaseScaleY: options.frame.scaleY,
21
+ frameBaseWidth: width,
22
+ minimumHeight: Math.max(0, options.minimumHeight ?? 0),
23
+ minimumWidth: Math.max(0, options.minimumWidth ?? 0),
24
+ resizeHandle: options.resizeHandle ?? null,
25
+ resizePointer: -1,
26
+ resizeStartHeight: 0,
27
+ resizeStartWidth: 0,
28
+ resizeStartWorldX: 0,
29
+ resizeStartWorldY: 0,
30
+ resizable: options.resizable ?? true,
31
+ signals: { onClose: createSignal(), onMove: createSignal(), onResize: createSignal() },
32
+ titleBar: options.titleBar ?? null,
33
+ }, options.transition);
34
+ const controller = createGuiController(runtime);
35
+ if (runtime.closeButton !== null) {
36
+ connectGuiSignal(runtime, getButtonControllerSignals(runtime.closeButton).onClick, () => emitSignal(runtime.signals.onClose));
37
+ }
38
+ if (runtime.titleBar !== null) {
39
+ connectGuiInteraction(runtime, runtime.titleBar, 'onPointerDown', (data) => {
40
+ if (!runtime.draggable || runtime.frame === null)
41
+ return;
42
+ runtime.dragPointer = data.pointerId;
43
+ runtime.dragStartWorldX = data.worldX;
44
+ runtime.dragStartWorldY = data.worldY;
45
+ runtime.dragStartFrameX = runtime.frame.x;
46
+ runtime.dragStartFrameY = runtime.frame.y;
47
+ });
48
+ connectGuiInteraction(runtime, runtime.titleBar, 'onPointerMove', (data) => {
49
+ if (data.pointerId !== runtime.dragPointer)
50
+ return;
51
+ setWindowControllerPosition(controller, runtime.dragStartFrameX + data.worldX - runtime.dragStartWorldX, runtime.dragStartFrameY + data.worldY - runtime.dragStartWorldY);
52
+ });
53
+ const endDrag = (data) => {
54
+ if (data.pointerId === runtime.dragPointer)
55
+ runtime.dragPointer = -1;
56
+ };
57
+ connectGuiInteraction(runtime, runtime.titleBar, 'onPointerUp', endDrag);
58
+ connectGuiInteraction(runtime, runtime.titleBar, 'onPointerCancel', endDrag);
59
+ connectGuiInteraction(runtime, runtime.titleBar, 'onReleaseOutside', endDrag);
60
+ }
61
+ if (runtime.resizeHandle !== null) {
62
+ connectGuiInteraction(runtime, runtime.resizeHandle, 'onPointerDown', (data) => {
63
+ if (!runtime.resizable || runtime.frame === null)
64
+ return;
65
+ runtime.resizePointer = data.pointerId;
66
+ runtime.resizeStartWorldX = data.worldX;
67
+ runtime.resizeStartWorldY = data.worldY;
68
+ runtime.resizeStartWidth = getNodeWidth(runtime.frame);
69
+ runtime.resizeStartHeight = getNodeHeight(runtime.frame);
70
+ });
71
+ connectGuiInteraction(runtime, runtime.resizeHandle, 'onPointerMove', (data) => {
72
+ if (data.pointerId !== runtime.resizePointer)
73
+ return;
74
+ setWindowControllerSize(controller, runtime.resizeStartWidth + data.worldX - runtime.resizeStartWorldX, runtime.resizeStartHeight + data.worldY - runtime.resizeStartWorldY);
75
+ });
76
+ const endResize = (data) => {
77
+ if (data.pointerId === runtime.resizePointer)
78
+ runtime.resizePointer = -1;
79
+ };
80
+ connectGuiInteraction(runtime, runtime.resizeHandle, 'onPointerUp', endResize);
81
+ connectGuiInteraction(runtime, runtime.resizeHandle, 'onPointerCancel', endResize);
82
+ connectGuiInteraction(runtime, runtime.resizeHandle, 'onReleaseOutside', endResize);
83
+ }
84
+ return controller;
85
+ }
86
+ export function disposeWindowController(controller) {
87
+ const runtime = getGuiControllerRuntime(controller);
88
+ disposeGuiController(controller, () => {
89
+ runtime.closeButton = null;
90
+ runtime.content = null;
91
+ runtime.frame = null;
92
+ runtime.resizeHandle = null;
93
+ runtime.titleBar = null;
94
+ });
95
+ }
96
+ export function getWindowControllerSignals(controller) {
97
+ return getGuiControllerRuntime(controller).signals;
98
+ }
99
+ export function setWindowControllerPosition(controller, x, y) {
100
+ const runtime = getGuiControllerRuntime(controller);
101
+ if (runtime.frame === null || runtime.disposed || (runtime.frame.x === x && runtime.frame.y === y))
102
+ return;
103
+ setGuiVisualProperty(runtime, runtime.frame, 'x', x);
104
+ setGuiVisualProperty(runtime, runtime.frame, 'y', y);
105
+ emitSignal(runtime.signals.onMove, x, y);
106
+ }
107
+ export function setWindowControllerSize(controller, width, height) {
108
+ const runtime = getGuiControllerRuntime(controller);
109
+ if (runtime.frame === null || runtime.disposed)
110
+ return;
111
+ const nextWidth = Math.max(runtime.minimumWidth, width);
112
+ const nextHeight = Math.max(runtime.minimumHeight, height);
113
+ if (runtime.frameBaseWidth > 0)
114
+ setGuiVisualProperty(runtime, runtime.frame, 'scaleX', (runtime.frameBaseScaleX * nextWidth) / runtime.frameBaseWidth);
115
+ if (runtime.frameBaseHeight > 0)
116
+ setGuiVisualProperty(runtime, runtime.frame, 'scaleY', (runtime.frameBaseScaleY * nextHeight) / runtime.frameBaseHeight);
117
+ emitSignal(runtime.signals.onResize, nextWidth, nextHeight);
118
+ }
119
+ //# sourceMappingURL=windowController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windowController.js","sourceRoot":"","sources":["../src/windowController.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@flighthq/gui",
3
+ "version": "0.4.1-edge.28.7f8e221",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/flighthq/flight.git",
9
+ "directory": "packages/gui"
10
+ },
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./contract": {
20
+ "types": "./dist/contract.d.ts",
21
+ "default": "./dist/contract.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src/**/*.test.ts",
27
+ "!dist/**/*.test.js",
28
+ "!dist/**/*.test.d.ts",
29
+ "!dist/**/*.test.js.map",
30
+ "!dist/**/*.test.d.ts.map"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc -b",
34
+ "clean": "tsc -b --clean",
35
+ "test": "vitest run --config vitest.config.ts",
36
+ "test:watch": "vitest --watch --config vitest.config.ts",
37
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
38
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
+ },
40
+ "dependencies": {
41
+ "@flighthq/interaction": "0.4.1-edge.28.7f8e221",
42
+ "@flighthq/node": "0.4.1-edge.28.7f8e221",
43
+ "@flighthq/signals": "0.4.1-edge.28.7f8e221",
44
+ "@flighthq/text": "0.4.1-edge.28.7f8e221",
45
+ "@flighthq/textinput": "0.4.1-edge.28.7f8e221",
46
+ "@flighthq/types": "0.4.1-edge.28.7f8e221"
47
+ },
48
+ "devDependencies": {
49
+ "typescript": "^5.3.0"
50
+ },
51
+ "description": "Controller-only behavior for caller-authored 2D interface visuals",
52
+ "sideEffects": false
53
+ }
@@ -0,0 +1,90 @@
1
+ import { connectSignal } from '@flighthq/signals/contract';
2
+
3
+ import {
4
+ createButtonController,
5
+ disposeButtonController,
6
+ getButtonControllerSignals,
7
+ isButtonControllerDisabled,
8
+ setButtonControllerDisabled,
9
+ } from './buttonController';
10
+ import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
11
+
12
+ describe('createButtonController', () => {
13
+ it('drives optional states and emits the complete pointer lifecycle', () => {
14
+ const up = createGuiTestNode();
15
+ const over = createGuiTestNode();
16
+ const down = createGuiTestNode();
17
+ const controller = createButtonController({ downState: down, overState: over, upState: up });
18
+ const events: string[] = [];
19
+ const signals = getButtonControllerSignals(controller);
20
+ connectSignal(signals.onPress, () => events.push('press'));
21
+ connectSignal(signals.onRelease, () => events.push('release'));
22
+ connectSignal(signals.onClick, () => events.push('click'));
23
+
24
+ emitGuiPointer(up, 'onPointerOver');
25
+ expect([up.visible, over.visible, down.visible]).toEqual([false, true, false]);
26
+ emitGuiPointer(up, 'onPointerDown');
27
+ expect([up.visible, over.visible, down.visible]).toEqual([false, false, true]);
28
+ emitGuiPointer(up, 'onPointerUp');
29
+ emitGuiPointer(up, 'onClick');
30
+ expect(events).toEqual(['press', 'release', 'click']);
31
+ });
32
+
33
+ it('delegates property assignment only when a transition is supplied', () => {
34
+ const up = createGuiTestNode();
35
+ const over = createGuiTestNode();
36
+ const applies: Array<() => void> = [];
37
+ createButtonController({
38
+ overState: over,
39
+ transition: { run: (request) => applies.push(() => request.apply()) },
40
+ upState: up,
41
+ });
42
+ for (const apply of applies.splice(0)) apply();
43
+ expect([up.visible, over.visible]).toEqual([true, false]);
44
+
45
+ emitGuiPointer(up, 'onPointerOver');
46
+ expect([up.visible, over.visible]).toEqual([true, false]);
47
+ for (const apply of applies.splice(0)) apply();
48
+ expect([up.visible, over.visible]).toEqual([false, true]);
49
+ });
50
+ });
51
+
52
+ describe('disposeButtonController', () => {
53
+ it('detaches listeners idempotently', () => {
54
+ const up = createGuiTestNode();
55
+ const controller = createButtonController({ upState: up });
56
+ let clicks = 0;
57
+ connectSignal(getButtonControllerSignals(controller).onClick, () => clicks++);
58
+ disposeButtonController(controller);
59
+ disposeButtonController(controller);
60
+ emitGuiPointer(up, 'onClick');
61
+ expect(clicks).toBe(0);
62
+ });
63
+ });
64
+
65
+ describe('getButtonControllerSignals', () => {
66
+ it('returns a stable signal group', () => {
67
+ const controller = createButtonController({ upState: createGuiTestNode() });
68
+ expect(getButtonControllerSignals(controller)).toBe(getButtonControllerSignals(controller));
69
+ });
70
+ });
71
+
72
+ describe('isButtonControllerDisabled', () => {
73
+ it('reads the configured state', () => {
74
+ expect(isButtonControllerDisabled(createButtonController({ disabled: true, upState: createGuiTestNode() }))).toBe(
75
+ true,
76
+ );
77
+ });
78
+ });
79
+
80
+ describe('setButtonControllerDisabled', () => {
81
+ it('blocks click emission', () => {
82
+ const up = createGuiTestNode();
83
+ const controller = createButtonController({ upState: up });
84
+ let clicks = 0;
85
+ connectSignal(getButtonControllerSignals(controller).onClick, () => clicks++);
86
+ setButtonControllerDisabled(controller, true);
87
+ emitGuiPointer(up, 'onClick');
88
+ expect(clicks).toBe(0);
89
+ });
90
+ });
@@ -0,0 +1,71 @@
1
+ import { connectSignal } from '@flighthq/signals/contract';
2
+
3
+ import { createButtonController } from './buttonController';
4
+ import {
5
+ createComboBoxController,
6
+ disposeComboBoxController,
7
+ getComboBoxControllerSignals,
8
+ isComboBoxControllerOpen,
9
+ setComboBoxControllerOpen,
10
+ } from './comboBoxController';
11
+ import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
12
+ import { createListController } from './listController';
13
+
14
+ function parts() {
15
+ const buttonNode = createGuiTestNode();
16
+ const viewport = createGuiTestNode();
17
+ const item = createGuiTestNode();
18
+ return {
19
+ button: createButtonController({ upState: buttonNode }),
20
+ buttonNode,
21
+ item,
22
+ list: createListController({ content: createGuiTestNode(), items: [item], viewport }),
23
+ viewport,
24
+ };
25
+ }
26
+
27
+ describe('createComboBoxController', () => {
28
+ it('opens from its button and closes on list selection', () => {
29
+ const p = parts();
30
+ const controller = createComboBoxController(p);
31
+ emitGuiPointer(p.buttonNode, 'onClick');
32
+ expect(isComboBoxControllerOpen(controller)).toBe(true);
33
+ emitGuiPointer(p.item, 'onClick');
34
+ expect(isComboBoxControllerOpen(controller)).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe('disposeComboBoxController', () => {
39
+ it('detaches composed controller signals', () => {
40
+ const p = parts();
41
+ const controller = createComboBoxController(p);
42
+ disposeComboBoxController(controller);
43
+ emitGuiPointer(p.buttonNode, 'onClick');
44
+ expect(isComboBoxControllerOpen(controller)).toBe(false);
45
+ });
46
+ });
47
+
48
+ describe('getComboBoxControllerSignals', () => {
49
+ it('emits open-state transitions', () => {
50
+ const controller = createComboBoxController(parts());
51
+ const values: boolean[] = [];
52
+ connectSignal(getComboBoxControllerSignals(controller).onOpenChange, (open) => values.push(open));
53
+ setComboBoxControllerOpen(controller, true);
54
+ expect(values).toEqual([true]);
55
+ });
56
+ });
57
+
58
+ describe('isComboBoxControllerOpen', () => {
59
+ it('reads initial open state', () => {
60
+ expect(isComboBoxControllerOpen(createComboBoxController({ ...parts(), open: true }))).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe('setComboBoxControllerOpen', () => {
65
+ it('updates list visibility', () => {
66
+ const p = parts();
67
+ const controller = createComboBoxController(p);
68
+ setComboBoxControllerOpen(controller, true);
69
+ expect(p.viewport.visible).toBe(true);
70
+ });
71
+ });
@@ -0,0 +1,162 @@
1
+ import { getNodeHitArea, isNodeHitTestEnabled } from '@flighthq/interaction/contract';
2
+ import { createSignal, emitSignal } from '@flighthq/signals/contract';
3
+ import type { Entity } from '@flighthq/types/contract';
4
+
5
+ import {
6
+ clampGuiValue,
7
+ configureGuiHitArea,
8
+ connectGuiInteraction,
9
+ connectGuiSignal,
10
+ createGuiController,
11
+ createGuiControllerRuntime,
12
+ disposeGuiController,
13
+ getGuiControllerRuntime,
14
+ getGuiLength,
15
+ getGuiPosition,
16
+ setGuiPosition,
17
+ setGuiScale,
18
+ setGuiVisible,
19
+ setGuiVisualProperty,
20
+ snapGuiValue,
21
+ } from './guiController';
22
+ import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
23
+
24
+ describe('clampGuiValue', () => {
25
+ it('clamps finite values and normalizes reversed bounds', () => {
26
+ expect(clampGuiValue(8, 0, 5)).toBe(5);
27
+ expect(clampGuiValue(2, 5, 0)).toBe(2);
28
+ expect(clampGuiValue(NaN, 2, 5)).toBe(2);
29
+ });
30
+ });
31
+
32
+ describe('configureGuiHitArea', () => {
33
+ it('restores a caller hit area on disposal', () => {
34
+ const target = createGuiTestNode();
35
+ const area = createGuiTestNode();
36
+ const runtime = createGuiControllerRuntime({});
37
+ const controller = createGuiController<Entity, typeof runtime>(runtime);
38
+ configureGuiHitArea(runtime, target, area);
39
+ expect(getNodeHitArea(target)).toBe(area);
40
+ disposeGuiController(controller, () => {});
41
+ expect(getNodeHitArea(target)).toBeNull();
42
+ });
43
+ });
44
+
45
+ describe('connectGuiInteraction', () => {
46
+ it('connects input and restores hit-test state', () => {
47
+ const target = createGuiTestNode();
48
+ const runtime = createGuiControllerRuntime({});
49
+ const controller = createGuiController<Entity, typeof runtime>(runtime);
50
+ let calls = 0;
51
+ connectGuiInteraction(runtime, target, 'onClick', () => calls++);
52
+ emitGuiPointer(target, 'onClick');
53
+ expect(calls).toBe(1);
54
+ disposeGuiController(controller, () => {});
55
+ expect(isNodeHitTestEnabled(target)).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('connectGuiSignal', () => {
60
+ it('disconnects a generic signal on disposal', () => {
61
+ const signal = createSignal<() => void>();
62
+ const runtime = createGuiControllerRuntime({});
63
+ const controller = createGuiController<Entity, typeof runtime>(runtime);
64
+ let calls = 0;
65
+ connectGuiSignal(runtime, signal, () => calls++);
66
+ disposeGuiController(controller, () => {});
67
+ emitSignal(signal);
68
+ expect(calls).toBe(0);
69
+ });
70
+ });
71
+
72
+ describe('createGuiController', () => {
73
+ it('stores the supplied runtime', () => {
74
+ const runtime = createGuiControllerRuntime({ value: 4 });
75
+ const controller = createGuiController<Entity, typeof runtime>(runtime);
76
+ expect(getGuiControllerRuntime<{ value: number }>(controller).value).toBe(4);
77
+ });
78
+ });
79
+
80
+ describe('createGuiControllerRuntime', () => {
81
+ it('starts undisposed with no transition', () => {
82
+ const runtime = createGuiControllerRuntime({});
83
+ expect([runtime.disposed, runtime.transition]).toEqual([false, null]);
84
+ });
85
+ });
86
+
87
+ describe('disposeGuiController', () => {
88
+ it('runs clear once', () => {
89
+ const runtime = createGuiControllerRuntime({});
90
+ const controller = createGuiController<Entity, typeof runtime>(runtime);
91
+ let clears = 0;
92
+ disposeGuiController(controller, () => clears++);
93
+ disposeGuiController(controller, () => clears++);
94
+ expect(clears).toBe(1);
95
+ });
96
+ });
97
+
98
+ describe('getGuiControllerRuntime', () => {
99
+ it('returns the exact runtime identity', () => {
100
+ const runtime = createGuiControllerRuntime({});
101
+ expect(getGuiControllerRuntime(createGuiController<Entity, typeof runtime>(runtime))).toBe(runtime);
102
+ });
103
+ });
104
+
105
+ describe('getGuiLength', () => {
106
+ it('reads the requested bounded axis', () => {
107
+ const node = createGuiTestNode(30, 40);
108
+ expect([getGuiLength(node, 'horizontal'), getGuiLength(node, 'vertical')]).toEqual([30, 40]);
109
+ });
110
+ });
111
+
112
+ describe('getGuiPosition', () => {
113
+ it('reads the requested transform axis', () => {
114
+ const node = createGuiTestNode();
115
+ node.x = 3;
116
+ node.y = 4;
117
+ expect([getGuiPosition(node, 'horizontal'), getGuiPosition(node, 'vertical')]).toEqual([3, 4]);
118
+ });
119
+ });
120
+
121
+ describe('setGuiPosition', () => {
122
+ it('sets one axis', () => {
123
+ const runtime = createGuiControllerRuntime({});
124
+ const node = createGuiTestNode();
125
+ setGuiPosition(runtime, node, 'vertical', 5);
126
+ expect(node.y).toBe(5);
127
+ });
128
+ });
129
+
130
+ describe('setGuiScale', () => {
131
+ it('sets one scale axis', () => {
132
+ const runtime = createGuiControllerRuntime({});
133
+ const node = createGuiTestNode();
134
+ setGuiScale(runtime, node, 'horizontal', 0.5);
135
+ expect(node.scaleX).toBe(0.5);
136
+ });
137
+ });
138
+
139
+ describe('setGuiVisible', () => {
140
+ it('sets visibility directly by default', () => {
141
+ const runtime = createGuiControllerRuntime({});
142
+ const node = createGuiTestNode();
143
+ setGuiVisible(runtime, node, false);
144
+ expect(node.visible).toBe(false);
145
+ });
146
+ });
147
+
148
+ describe('setGuiVisualProperty', () => {
149
+ it('offers intermediate writes to an opt-in transition', () => {
150
+ const node = createGuiTestNode();
151
+ const runtime = createGuiControllerRuntime({}, { run: (request) => request.apply(0.5) });
152
+ setGuiVisualProperty(runtime, node, 'alpha', 0);
153
+ expect(node.alpha).toBe(0.5);
154
+ });
155
+ });
156
+
157
+ describe('snapGuiValue', () => {
158
+ it('snaps around the minimum', () => {
159
+ expect(snapGuiValue(3.1, 1, 2)).toBe(3);
160
+ expect(snapGuiValue(3.1, 1, null)).toBe(3.1);
161
+ });
162
+ });
@@ -0,0 +1,35 @@
1
+ // @vitest-environment node
2
+
3
+ import { build } from 'esbuild';
4
+
5
+ describe('GUI controller tree shaking', () => {
6
+ it('does not pull the catalog or tween runtime into a Button-only bundle', async () => {
7
+ const result = await build({
8
+ bundle: true,
9
+ format: 'esm',
10
+ logLevel: 'silent',
11
+ minify: true,
12
+ packages: 'external',
13
+ stdin: {
14
+ contents: `export { createButtonController } from './contract.ts';`,
15
+ resolveDir: fileUrlDirectory(import.meta.url),
16
+ sourcefile: 'tree-shake-button.ts',
17
+ },
18
+ treeShaking: true,
19
+ write: false,
20
+ });
21
+ const output = result.outputFiles[0].text;
22
+ expect(output).toContain('createButtonController');
23
+ expect(output).not.toContain('setInterval');
24
+ expect(output).not.toContain('onExpandChange');
25
+ expect(output).not.toContain('createTextInputManager');
26
+ expect(output).not.toContain('@flighthq/tween');
27
+ });
28
+ });
29
+
30
+ function fileUrlDirectory(url: string): string {
31
+ const directory = new URL('.', url);
32
+ const pathname = decodeURIComponent(directory.pathname);
33
+ if (directory.hostname !== '') return `//${directory.hostname}${pathname}`;
34
+ return /^\/[A-Za-z]:\//.test(pathname) ? pathname.slice(1) : pathname;
35
+ }
@@ -0,0 +1,96 @@
1
+ import { connectSignal } from '@flighthq/signals/contract';
2
+
3
+ import { createGuiTestNode, emitGuiKeyboard, emitGuiPointer } from './guiTestHelper';
4
+ import {
5
+ createListController,
6
+ disposeListController,
7
+ getListControllerSelectedIndex,
8
+ getListControllerSignals,
9
+ setListControllerSelectedIndex,
10
+ setListControllerVisible,
11
+ } from './listController';
12
+
13
+ describe('createListController', () => {
14
+ it('selects and activates item visuals', () => {
15
+ const items = [createGuiTestNode(), createGuiTestNode()];
16
+ const controller = createListController({ content: createGuiTestNode(), items, viewport: createGuiTestNode() });
17
+ const activations: number[] = [];
18
+ connectSignal(getListControllerSignals(controller).onActivate, (index) => activations.push(index));
19
+ emitGuiPointer(items[1], 'onClick');
20
+ emitGuiPointer(items[1], 'onDoubleClick');
21
+ expect(getListControllerSelectedIndex(controller)).toBe(1);
22
+ expect(activations).toEqual([1]);
23
+ });
24
+
25
+ it('supports hierarchical-independent flat keyboard navigation', () => {
26
+ const viewport = createGuiTestNode();
27
+ const controller = createListController({
28
+ content: createGuiTestNode(),
29
+ items: [createGuiTestNode(), createGuiTestNode()],
30
+ viewport,
31
+ });
32
+ emitGuiKeyboard(viewport, 'onKeyDown', 'ArrowDown');
33
+ expect(getListControllerSelectedIndex(controller)).toBe(0);
34
+ });
35
+ });
36
+
37
+ describe('disposeListController', () => {
38
+ it('detaches item input', () => {
39
+ const item = createGuiTestNode();
40
+ const controller = createListController({
41
+ content: createGuiTestNode(),
42
+ items: [item],
43
+ viewport: createGuiTestNode(),
44
+ });
45
+ disposeListController(controller);
46
+ emitGuiPointer(item, 'onClick');
47
+ expect(getListControllerSelectedIndex(controller)).toBe(-1);
48
+ });
49
+ });
50
+
51
+ describe('getListControllerSelectedIndex', () => {
52
+ it('normalizes invalid initial values', () => {
53
+ expect(
54
+ getListControllerSelectedIndex(
55
+ createListController({
56
+ content: createGuiTestNode(),
57
+ items: [],
58
+ selectedIndex: 2,
59
+ viewport: createGuiTestNode(),
60
+ }),
61
+ ),
62
+ ).toBe(-1);
63
+ });
64
+ });
65
+
66
+ describe('getListControllerSignals', () => {
67
+ it('returns stable signals', () => {
68
+ const controller = createListController({ content: createGuiTestNode(), items: [], viewport: createGuiTestNode() });
69
+ expect(getListControllerSignals(controller)).toBe(getListControllerSignals(controller));
70
+ });
71
+ });
72
+
73
+ describe('setListControllerSelectedIndex', () => {
74
+ it('emits only real changes', () => {
75
+ const controller = createListController({
76
+ content: createGuiTestNode(),
77
+ items: [createGuiTestNode()],
78
+ viewport: createGuiTestNode(),
79
+ });
80
+ const values: number[] = [];
81
+ connectSignal(getListControllerSignals(controller).onSelect, (index) => values.push(index));
82
+ setListControllerSelectedIndex(controller, 0);
83
+ setListControllerSelectedIndex(controller, 0);
84
+ expect(values).toEqual([0]);
85
+ });
86
+ });
87
+
88
+ describe('setListControllerVisible', () => {
89
+ it('changes both list-owned references without creating visuals', () => {
90
+ const content = createGuiTestNode();
91
+ const viewport = createGuiTestNode();
92
+ const controller = createListController({ content, items: [], viewport });
93
+ setListControllerVisible(controller, false);
94
+ expect([content.visible, viewport.visible]).toEqual([false, false]);
95
+ });
96
+ });