@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.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.
Files changed (154) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +21 -21
  3. package/README.md +206 -139
  4. package/dist/auto.d.ts +6 -6
  5. package/dist/auto.js +2 -2
  6. package/dist/doctor/cli.mjs +62 -25
  7. package/dist/doctor/cli.mjs.map +1 -1
  8. package/dist/index.d.ts +6 -6
  9. package/dist/index.js +2 -2
  10. package/dist/mp.d.ts +3 -3
  11. package/dist/race.d.ts +1 -1
  12. package/dist/rooms.d.ts +3 -3
  13. package/dist/shared/bridge.js +9 -3
  14. package/dist/shared/bridge.js.map +1 -1
  15. package/dist/shared/client-version.gen.js +1 -1
  16. package/dist/shared/daily.d.ts +10 -4
  17. package/dist/shared/daily.d.ts.map +1 -1
  18. package/dist/shared/daily.js +12 -2
  19. package/dist/shared/daily.js.map +1 -1
  20. package/dist/shared/globals.js +8 -3
  21. package/dist/shared/globals.js.map +1 -1
  22. package/dist/shared/index.d.ts +314 -35
  23. package/dist/shared/index.d.ts.map +1 -1
  24. package/dist/shared/input.js +7 -2
  25. package/dist/shared/input.js.map +1 -1
  26. package/dist/shared/latest-state.js +1 -1
  27. package/dist/shared/mp.js +10 -2
  28. package/dist/shared/mp.js.map +1 -1
  29. package/dist/shared/multiplayer-types.d.ts +15 -2
  30. package/dist/shared/multiplayer-types.d.ts.map +1 -1
  31. package/dist/shared/race-types.d.ts +1 -1
  32. package/dist/shared/race.js +1 -1
  33. package/dist/shared/room-types.d.ts +28 -3
  34. package/dist/shared/room-types.d.ts.map +1 -1
  35. package/dist/shared/rooms.d.ts.map +1 -1
  36. package/dist/shared/rooms.js +10 -2
  37. package/dist/shared/rooms.js.map +1 -1
  38. package/dist/shared/runtime-opener.js +1 -1
  39. package/dist/shared/social.d.ts +23 -8
  40. package/dist/shared/social.d.ts.map +1 -1
  41. package/dist/shared/social.js +6 -1
  42. package/dist/shared/social.js.map +1 -1
  43. package/dist/shared/src.js +1588 -388
  44. package/dist/shared/src.js.map +1 -1
  45. package/dist/shared/wire-types.d.ts +6 -2
  46. package/dist/shared/wire-types.d.ts.map +1 -1
  47. package/dist/social.d.ts +5 -5
  48. package/dist/social.js +1 -1
  49. package/package.json +89 -89
  50. package/src/auto.ts +39 -39
  51. package/src/core/connection.ts +394 -299
  52. package/src/core/errors.ts +147 -147
  53. package/src/core/game-data.ts +40 -38
  54. package/src/core/hub-economy.ts +216 -179
  55. package/src/core/hub-progress.ts +202 -205
  56. package/src/core/hub.ts +133 -131
  57. package/src/core/menu-control.ts +61 -61
  58. package/src/core/offline/events.ts +50 -50
  59. package/src/core/offline/favorites.ts +21 -21
  60. package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
  61. package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
  62. package/src/core/offline/hub-kinds/game-fields.ts +2 -0
  63. package/src/core/offline/hub-kinds/index.ts +35 -0
  64. package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
  65. package/src/core/offline/hub-kinds/progress-kinds.ts +148 -0
  66. package/src/core/offline/kind-adapters.ts +48 -0
  67. package/src/core/offline/local-inventory.ts +64 -0
  68. package/src/core/offline/local-store.ts +259 -261
  69. package/src/core/offline/offline-api.ts +65 -0
  70. package/src/core/offline/outbox-entry.ts +59 -0
  71. package/src/core/offline/outbox-kinds.ts +78 -0
  72. package/src/core/offline/outbox-merge.ts +39 -0
  73. package/src/core/offline/outbox-replayer.ts +141 -0
  74. package/src/core/offline/outbox-store.ts +85 -0
  75. package/src/core/offline/outbox.ts +232 -0
  76. package/src/core/offline/queue.ts +126 -171
  77. package/src/core/offline/replay-lease.ts +87 -0
  78. package/src/core/offline/replay-policy.ts +52 -0
  79. package/src/core/page.ts +74 -74
  80. package/src/core/rest/endpoint-types.ts +22 -22
  81. package/src/core/rest/rest-client.ts +163 -152
  82. package/src/core/rest/routes.gen.ts +108 -87
  83. package/src/core/storage/json-guards.ts +80 -74
  84. package/src/core/storage/key-value.ts +66 -66
  85. package/src/core/ticket.ts +43 -43
  86. package/src/core/types.ts +134 -134
  87. package/src/core/version/client-version.gen.ts +1 -1
  88. package/src/core/version/hub-version.ts +62 -62
  89. package/src/core/version/semver.ts +97 -97
  90. package/src/daily/daily-system.ts +168 -161
  91. package/src/daily/page-daily.ts +36 -36
  92. package/src/daily/types.ts +68 -67
  93. package/src/daily.ts +10 -10
  94. package/src/doctor/checks.ts +86 -76
  95. package/src/doctor/cli.ts +98 -106
  96. package/src/doctor/hub-version-fetch.ts +43 -0
  97. package/src/doctor/tsconfig.json +12 -12
  98. package/src/globals.ts +115 -110
  99. package/src/index.ts +55 -50
  100. package/src/input/device-state.ts +146 -146
  101. package/src/input/edges.ts +55 -55
  102. package/src/input/gamepad.ts +61 -61
  103. package/src/input/input-events.ts +32 -32
  104. package/src/input/input-registry.ts +171 -164
  105. package/src/input/input-system.ts +393 -393
  106. package/src/input/legacy-controls.ts +64 -64
  107. package/src/input/nav-state.ts +191 -191
  108. package/src/input/navigator.ts +171 -171
  109. package/src/input/on-screen-controls.ts +101 -101
  110. package/src/input/page-control.ts +43 -43
  111. package/src/input/page-input.ts +193 -193
  112. package/src/input/player-remaps.ts +77 -77
  113. package/src/input/source-values.ts +116 -116
  114. package/src/input/spatial.ts +47 -47
  115. package/src/input/types.ts +331 -331
  116. package/src/input/zero-config.ts +177 -177
  117. package/src/input.ts +22 -22
  118. package/src/mp.ts +10 -10
  119. package/src/overlay/controls-panel.ts +163 -163
  120. package/src/overlay/dom.ts +46 -46
  121. package/src/overlay/layout-clusters.ts +162 -162
  122. package/src/overlay/layout-geometry.ts +136 -136
  123. package/src/overlay/layout.ts +203 -203
  124. package/src/overlay/listener-scope.ts +23 -23
  125. package/src/overlay/on-screen-keyboard.ts +137 -137
  126. package/src/overlay/overlay.ts +208 -208
  127. package/src/overlay/styles.ts +9 -9
  128. package/src/overlay/virtual-controls.ts +262 -262
  129. package/src/race.ts +10 -10
  130. package/src/realtime/bridge.ts +126 -121
  131. package/src/realtime/cross-bundle.ts +29 -29
  132. package/src/realtime/fallback-notice.ts +48 -48
  133. package/src/realtime/hub-link.ts +65 -65
  134. package/src/realtime/latest-state.ts +33 -33
  135. package/src/realtime/multiplayer-forwarder.ts +40 -28
  136. package/src/realtime/multiplayer-types.ts +66 -52
  137. package/src/realtime/page-runtime.ts +60 -60
  138. package/src/realtime/race-forwarder.ts +59 -59
  139. package/src/realtime/race-types.ts +132 -132
  140. package/src/realtime/room-types.ts +151 -125
  141. package/src/realtime/rooms-forwarder.ts +91 -79
  142. package/src/realtime/runtime-api.ts +60 -49
  143. package/src/realtime/runtime-opener.ts +61 -61
  144. package/src/realtime/social-forwarder.ts +77 -72
  145. package/src/realtime/social-types.ts +86 -78
  146. package/src/realtime/types.ts +53 -53
  147. package/src/realtime/wire-types.ts +79 -72
  148. package/src/rooms.ts +10 -10
  149. package/src/social.ts +18 -18
  150. package/src/types.ts +13 -13
  151. package/src/uistack/page-stack.ts +45 -45
  152. package/src/uistack/play-suspension.ts +82 -82
  153. package/src/uistack/types.ts +76 -76
  154. package/src/uistack/ui-stack.ts +144 -144
@@ -1,177 +1,177 @@
1
- /**
2
- * The zero-config fallback for games that never call `hub.input.define()`:
3
- * on the catalog page a gamepad moves a highlight across the game tiles; in
4
- * a game it presses the keys `game.controls` maps its buttons to. Runs each
5
- * frame only while the menu is closed and no game has taken over input.
6
- */
7
- import type { RepeatingEdges } from './edges';
8
- import { anyPadDirections, isAnyPadButtonPressed, isPadButtonPressed, PAD_BUTTON, padDirections } from './gamepad';
9
- import type { Direction } from './gamepad';
10
- import type { LegacyControls } from './legacy-controls';
11
- import { controlMapFor } from './legacy-controls';
12
- import { findNearestInDirection, rectOf } from './spatial';
13
- import { isTypingTarget } from './device-state';
14
-
15
- /** Synthetic keys stay pressed past half deflection. */
16
- const SYNTH_DEADZONE = 0.5;
17
- const FOCUS_CLASS = 'gp-focus';
18
- const FOCUS_STYLE_ID = 'gp-focus-style';
19
- const FOCUS_CSS = '.gp-focus{outline:3px solid #ff5f3b !important;outline-offset:2px;border-radius:4px;}';
20
-
21
- /** `KeyboardEvent.code` → its `key` and legacy `keyCode`. */
22
- const KEY_FOR_CODE = new Map<string, readonly [string, number]>([
23
- ['ArrowUp', ['ArrowUp', 38]], ['ArrowDown', ['ArrowDown', 40]], ['ArrowLeft', ['ArrowLeft', 37]], ['ArrowRight', ['ArrowRight', 39]],
24
- ['Space', [' ', 32]], ['Enter', ['Enter', 13]], ['Escape', ['Escape', 27]], ['Tab', ['Tab', 9]], ['Backspace', ['Backspace', 8]],
25
- ['ShiftLeft', ['Shift', 16]], ['ControlLeft', ['Control', 17]], ['AltLeft', ['Alt', 18]],
26
- ]);
27
- for (let code = 65; code <= 90; code += 1) {
28
- const letter = String.fromCharCode(code);
29
- KEY_FOR_CODE.set(`Key${letter}`, [letter.toLowerCase(), code]);
30
- }
31
- for (let digit = 0; digit <= 9; digit += 1) {
32
- KEY_FOR_CODE.set(`Digit${String(digit)}`, [String(digit), 48 + digit]);
33
- }
34
-
35
- /** What the fallback reads from the input system each frame. */
36
- export interface ZeroConfigFrame {
37
- readonly gamepads: readonly Gamepad[];
38
- readonly slug: string | null;
39
- readonly controls: LegacyControls;
40
- readonly navEdges: RepeatingEdges;
41
- }
42
-
43
- /** Dispatch a synthetic key event (with the legacy `keyCode`/`which` old games read) on the focused element. */
44
- function dispatchKey(type: 'keydown' | 'keyup', code: string): void {
45
- const [key, keyCode] = KEY_FOR_CODE.get(code) ?? [code, 0];
46
- const active = document.activeElement;
47
- const target: EventTarget = active !== null && active !== document.body ? active : document.body;
48
- const event = new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true });
49
- Object.defineProperty(event, 'keyCode', { get: () => keyCode });
50
- Object.defineProperty(event, 'which', { get: () => keyCode });
51
- target.dispatchEvent(event);
52
- }
53
-
54
- /** Add the highlight style to the page once. */
55
- function ensureFocusStyle(): void {
56
- if (document.getElementById(FOCUS_STYLE_ID) !== null) {
57
- return;
58
- }
59
- const style = document.createElement('style');
60
- style.id = FOCUS_STYLE_ID;
61
- style.textContent = FOCUS_CSS;
62
- document.head.appendChild(style);
63
- }
64
-
65
- /** The keys each pad holds down, by pad index. */
66
- const heldCodes = new Map<number, Set<string>>();
67
-
68
- function releaseAllKeys(): void {
69
- for (const [padIndex, codes] of heldCodes) {
70
- for (const code of codes) {
71
- dispatchKey('keyup', code);
72
- }
73
- heldCodes.set(padIndex, new Set());
74
- }
75
- }
76
-
77
- /** In a game: press and release the mapped keys to match each pad. */
78
- function synthesizeKeys(frame: ZeroConfigFrame): void {
79
- if (isTypingTarget(document.activeElement)) {
80
- releaseAllKeys();
81
- return;
82
- }
83
- frame.gamepads.forEach((pad, padIndex) => {
84
- const map = controlMapFor(frame.controls, padIndex);
85
- const directions = padDirections(pad, SYNTH_DEADZONE);
86
- const wanted = new Set<string>();
87
- for (const direction of ['up', 'down', 'left', 'right'] as const) {
88
- if (directions[direction]) {
89
- wanted.add(map[direction]);
90
- }
91
- }
92
- for (const [buttonIndex, code] of map.buttons) {
93
- if (isPadButtonPressed(pad, buttonIndex)) {
94
- wanted.add(code);
95
- }
96
- }
97
- const held = heldCodes.get(padIndex) ?? new Set<string>();
98
- wanted.forEach((code) => {
99
- if (!held.has(code)) {
100
- dispatchKey('keydown', code);
101
- }
102
- });
103
- held.forEach((code) => {
104
- if (!wanted.has(code)) {
105
- dispatchKey('keyup', code);
106
- }
107
- });
108
- heldCodes.set(padIndex, wanted);
109
- });
110
- }
111
-
112
- /** The visible game tiles on the catalog page. */
113
- function visibleTiles(): HTMLElement[] {
114
- return [...document.querySelectorAll<HTMLElement>('.grid .card')].filter((card) => card.offsetParent !== null);
115
- }
116
-
117
- function highlightTile(tiles: readonly HTMLElement[], index: number): void {
118
- tiles.forEach((tile) => {
119
- tile.classList.remove(FOCUS_CLASS);
120
- });
121
- const tile = tiles[index];
122
- tile?.classList.add(FOCUS_CLASS);
123
- tile?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
124
- }
125
-
126
- /** On the catalog page: move the highlight across the tiles; A or Start opens one. */
127
- function navigateCatalog(frame: ZeroConfigFrame): void {
128
- const held = anyPadDirections(frame.gamepads, SYNTH_DEADZONE);
129
- const isAccepting = isAnyPadButtonPressed(frame.gamepads, PAD_BUTTON.A) || isAnyPadButtonPressed(frame.gamepads, PAD_BUTTON.START);
130
- const fired: Record<Direction, boolean> = {
131
- up: frame.navEdges.fires('lu', held.up),
132
- down: frame.navEdges.fires('ld', held.down),
133
- left: frame.navEdges.fires('ll', held.left),
134
- right: frame.navEdges.fires('lr', held.right),
135
- };
136
- const shouldAccept = frame.navEdges.fires('la', isAccepting) && isAccepting;
137
- const tiles = visibleTiles();
138
- if (tiles.length === 0) {
139
- return;
140
- }
141
- const focused = tiles.findIndex((tile) => tile.classList.contains(FOCUS_CLASS));
142
- const focusedTile = tiles[focused];
143
- if (focusedTile === undefined) {
144
- highlightTile(tiles, 0);
145
- return;
146
- }
147
- if (shouldAccept) {
148
- const href = focusedTile.getAttribute('href');
149
- if (href === null) {
150
- focusedTile.click();
151
- } else {
152
- location.href = href;
153
- }
154
- return;
155
- }
156
- const direction = (['up', 'down', 'left', 'right'] as const).find((candidate) => fired[candidate]);
157
- if (direction === undefined) {
158
- return;
159
- }
160
- const next = findNearestInDirection(tiles.map(rectOf), focused, direction);
161
- if (next >= 0) {
162
- highlightTile(tiles, next);
163
- }
164
- }
165
-
166
- /** One frame of the zero-config fallback. */
167
- export function runZeroConfigFrame(frame: ZeroConfigFrame): void {
168
- if (frame.gamepads.length === 0) {
169
- return;
170
- }
171
- ensureFocusStyle();
172
- if (frame.slug === null) {
173
- navigateCatalog(frame);
174
- } else {
175
- synthesizeKeys(frame);
176
- }
177
- }
1
+ /**
2
+ * The zero-config fallback for games that never call `hub.input.define()`:
3
+ * on the catalog page a gamepad moves a highlight across the game tiles; in
4
+ * a game it presses the keys `game.controls` maps its buttons to. Runs each
5
+ * frame only while the menu is closed and no game has taken over input.
6
+ */
7
+ import type { RepeatingEdges } from './edges';
8
+ import { anyPadDirections, isAnyPadButtonPressed, isPadButtonPressed, PAD_BUTTON, padDirections } from './gamepad';
9
+ import type { Direction } from './gamepad';
10
+ import type { LegacyControls } from './legacy-controls';
11
+ import { controlMapFor } from './legacy-controls';
12
+ import { findNearestInDirection, rectOf } from './spatial';
13
+ import { isTypingTarget } from './device-state';
14
+
15
+ /** Synthetic keys stay pressed past half deflection. */
16
+ const SYNTH_DEADZONE = 0.5;
17
+ const FOCUS_CLASS = 'gp-focus';
18
+ const FOCUS_STYLE_ID = 'gp-focus-style';
19
+ const FOCUS_CSS = '.gp-focus{outline:3px solid #ff5f3b !important;outline-offset:2px;border-radius:4px;}';
20
+
21
+ /** `KeyboardEvent.code` → its `key` and legacy `keyCode`. */
22
+ const KEY_FOR_CODE = new Map<string, readonly [string, number]>([
23
+ ['ArrowUp', ['ArrowUp', 38]], ['ArrowDown', ['ArrowDown', 40]], ['ArrowLeft', ['ArrowLeft', 37]], ['ArrowRight', ['ArrowRight', 39]],
24
+ ['Space', [' ', 32]], ['Enter', ['Enter', 13]], ['Escape', ['Escape', 27]], ['Tab', ['Tab', 9]], ['Backspace', ['Backspace', 8]],
25
+ ['ShiftLeft', ['Shift', 16]], ['ControlLeft', ['Control', 17]], ['AltLeft', ['Alt', 18]],
26
+ ]);
27
+ for (let code = 65; code <= 90; code += 1) {
28
+ const letter = String.fromCharCode(code);
29
+ KEY_FOR_CODE.set(`Key${letter}`, [letter.toLowerCase(), code]);
30
+ }
31
+ for (let digit = 0; digit <= 9; digit += 1) {
32
+ KEY_FOR_CODE.set(`Digit${String(digit)}`, [String(digit), 48 + digit]);
33
+ }
34
+
35
+ /** What the fallback reads from the input system each frame. */
36
+ export interface ZeroConfigFrame {
37
+ readonly gamepads: readonly Gamepad[];
38
+ readonly slug: string | null;
39
+ readonly controls: LegacyControls;
40
+ readonly navEdges: RepeatingEdges;
41
+ }
42
+
43
+ /** Dispatch a synthetic key event (with the legacy `keyCode`/`which` old games read) on the focused element. */
44
+ function dispatchKey(type: 'keydown' | 'keyup', code: string): void {
45
+ const [key, keyCode] = KEY_FOR_CODE.get(code) ?? [code, 0];
46
+ const active = document.activeElement;
47
+ const target: EventTarget = active !== null && active !== document.body ? active : document.body;
48
+ const event = new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true });
49
+ Object.defineProperty(event, 'keyCode', { get: () => keyCode });
50
+ Object.defineProperty(event, 'which', { get: () => keyCode });
51
+ target.dispatchEvent(event);
52
+ }
53
+
54
+ /** Add the highlight style to the page once. */
55
+ function ensureFocusStyle(): void {
56
+ if (document.getElementById(FOCUS_STYLE_ID) !== null) {
57
+ return;
58
+ }
59
+ const style = document.createElement('style');
60
+ style.id = FOCUS_STYLE_ID;
61
+ style.textContent = FOCUS_CSS;
62
+ document.head.appendChild(style);
63
+ }
64
+
65
+ /** The keys each pad holds down, by pad index. */
66
+ const heldCodes = new Map<number, Set<string>>();
67
+
68
+ function releaseAllKeys(): void {
69
+ for (const [padIndex, codes] of heldCodes) {
70
+ for (const code of codes) {
71
+ dispatchKey('keyup', code);
72
+ }
73
+ heldCodes.set(padIndex, new Set());
74
+ }
75
+ }
76
+
77
+ /** In a game: press and release the mapped keys to match each pad. */
78
+ function synthesizeKeys(frame: ZeroConfigFrame): void {
79
+ if (isTypingTarget(document.activeElement)) {
80
+ releaseAllKeys();
81
+ return;
82
+ }
83
+ frame.gamepads.forEach((pad, padIndex) => {
84
+ const map = controlMapFor(frame.controls, padIndex);
85
+ const directions = padDirections(pad, SYNTH_DEADZONE);
86
+ const wanted = new Set<string>();
87
+ for (const direction of ['up', 'down', 'left', 'right'] as const) {
88
+ if (directions[direction]) {
89
+ wanted.add(map[direction]);
90
+ }
91
+ }
92
+ for (const [buttonIndex, code] of map.buttons) {
93
+ if (isPadButtonPressed(pad, buttonIndex)) {
94
+ wanted.add(code);
95
+ }
96
+ }
97
+ const held = heldCodes.get(padIndex) ?? new Set<string>();
98
+ wanted.forEach((code) => {
99
+ if (!held.has(code)) {
100
+ dispatchKey('keydown', code);
101
+ }
102
+ });
103
+ held.forEach((code) => {
104
+ if (!wanted.has(code)) {
105
+ dispatchKey('keyup', code);
106
+ }
107
+ });
108
+ heldCodes.set(padIndex, wanted);
109
+ });
110
+ }
111
+
112
+ /** The visible game tiles on the catalog page. */
113
+ function visibleTiles(): HTMLElement[] {
114
+ return [...document.querySelectorAll<HTMLElement>('.grid .card')].filter((card) => card.offsetParent !== null);
115
+ }
116
+
117
+ function highlightTile(tiles: readonly HTMLElement[], index: number): void {
118
+ tiles.forEach((tile) => {
119
+ tile.classList.remove(FOCUS_CLASS);
120
+ });
121
+ const tile = tiles[index];
122
+ tile?.classList.add(FOCUS_CLASS);
123
+ tile?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
124
+ }
125
+
126
+ /** On the catalog page: move the highlight across the tiles; A or Start opens one. */
127
+ function navigateCatalog(frame: ZeroConfigFrame): void {
128
+ const held = anyPadDirections(frame.gamepads, SYNTH_DEADZONE);
129
+ const isAccepting = isAnyPadButtonPressed(frame.gamepads, PAD_BUTTON.A) || isAnyPadButtonPressed(frame.gamepads, PAD_BUTTON.START);
130
+ const fired: Record<Direction, boolean> = {
131
+ up: frame.navEdges.fires('lu', held.up),
132
+ down: frame.navEdges.fires('ld', held.down),
133
+ left: frame.navEdges.fires('ll', held.left),
134
+ right: frame.navEdges.fires('lr', held.right),
135
+ };
136
+ const shouldAccept = frame.navEdges.fires('la', isAccepting) && isAccepting;
137
+ const tiles = visibleTiles();
138
+ if (tiles.length === 0) {
139
+ return;
140
+ }
141
+ const focused = tiles.findIndex((tile) => tile.classList.contains(FOCUS_CLASS));
142
+ const focusedTile = tiles[focused];
143
+ if (focusedTile === undefined) {
144
+ highlightTile(tiles, 0);
145
+ return;
146
+ }
147
+ if (shouldAccept) {
148
+ const href = focusedTile.getAttribute('href');
149
+ if (href === null) {
150
+ focusedTile.click();
151
+ } else {
152
+ location.href = href;
153
+ }
154
+ return;
155
+ }
156
+ const direction = (['up', 'down', 'left', 'right'] as const).find((candidate) => fired[candidate]);
157
+ if (direction === undefined) {
158
+ return;
159
+ }
160
+ const next = findNearestInDirection(tiles.map(rectOf), focused, direction);
161
+ if (next >= 0) {
162
+ highlightTile(tiles, next);
163
+ }
164
+ }
165
+
166
+ /** One frame of the zero-config fallback. */
167
+ export function runZeroConfigFrame(frame: ZeroConfigFrame): void {
168
+ if (frame.gamepads.length === 0) {
169
+ return;
170
+ }
171
+ ensureFocusStyle();
172
+ if (frame.slug === null) {
173
+ navigateCatalog(frame);
174
+ } else {
175
+ synthesizeKeys(frame);
176
+ }
177
+ }
package/src/input.ts CHANGED
@@ -1,22 +1,22 @@
1
- /**
2
- * `@diffenderfer-games/hub/input`: the input system (`hub.input`): the
3
- * page-wide {@link getInput}, the {@link InputSystem} class, the on-screen
4
- * control layout, and the overlay stack behind `hub.overlay`.
5
- *
6
- * @packageDocumentation
7
- */
8
- export { InputSystem } from './input/input-system';
9
- export type { InputSystemOptions } from './input/input-system';
10
- export { getInput, INPUT_SINGLETON_API, isInputSystem, parseInputOverrides } from './input/page-input';
11
- export type { InputOptions } from './input/page-input';
12
- export { isPageInputControl } from './input/page-control';
13
- export type { PageInputControl } from './input/page-control';
14
- export type * from './input/types';
15
- export { Overlay } from './overlay/overlay';
16
- export { layoutControls } from './overlay/layout';
17
- export type { LayoutOptions } from './overlay/layout';
18
- export { boxesTouch, controlSize } from './overlay/layout-geometry';
19
- export type { Insets, LayoutBox, LayoutItem } from './overlay/layout-geometry';
20
- export { getUiStack, isUiStack } from './uistack/page-stack';
21
- export { UI_STACK_VERSION } from './uistack/ui-stack';
22
- export type * from './uistack/types';
1
+ /**
2
+ * `@diffenderfer-games/hub/input`: the input system (`hub.input`): the
3
+ * page-wide {@link getInput}, the {@link InputSystem} class, the on-screen
4
+ * control layout, and the overlay stack behind `hub.overlay`.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export { InputSystem } from './input/input-system';
9
+ export type { InputSystemOptions } from './input/input-system';
10
+ export { getInput, INPUT_SINGLETON_API, isInputSystem, parseInputOverrides } from './input/page-input';
11
+ export type { InputOptions } from './input/page-input';
12
+ export { isPageInputControl } from './input/page-control';
13
+ export type { PageInputControl } from './input/page-control';
14
+ export type * from './input/types';
15
+ export { Overlay } from './overlay/overlay';
16
+ export { layoutControls } from './overlay/layout';
17
+ export type { LayoutOptions } from './overlay/layout';
18
+ export { boxesTouch, controlSize } from './overlay/layout-geometry';
19
+ export type { Insets, LayoutBox, LayoutItem } from './overlay/layout-geometry';
20
+ export { getUiStack, isUiStack } from './uistack/page-stack';
21
+ export { UI_STACK_VERSION } from './uistack/ui-stack';
22
+ export type * from './uistack/types';
package/src/mp.ts CHANGED
@@ -1,10 +1,10 @@
1
- /**
2
- * `@diffenderfer-games/hub/mp`: the multiplayer forwarder (`hub.mp`):
3
- * invites, launches, the leave guard and own-server tickets.
4
- *
5
- * @packageDocumentation
6
- */
7
- export { multiplayerForwarder as mp } from './realtime/multiplayer-forwarder';
8
- export type * from './realtime/multiplayer-types';
9
- export type { InviteCreated } from './realtime/wire-types';
10
- export type { Invite, JoinInfo, Launch, LaunchTicket, Party } from '@diffenderfer-games/hub-contract';
1
+ /**
2
+ * `@diffenderfer-games/hub/mp`: the multiplayer forwarder (`hub.mp`):
3
+ * invites, launches, the leave guard and own-server tickets.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ export { multiplayerForwarder as mp } from './realtime/multiplayer-forwarder';
8
+ export type * from './realtime/multiplayer-types';
9
+ export type { InviteCreated, InviteLink } from './realtime/wire-types';
10
+ export type { Invite, JoinInfo, Launch, LaunchTicket, Party } from '@diffenderfer-games/hub-contract';