@limrun/ui 0.17.0-rc.1 → 0.17.0-rc.3

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 (80) hide show
  1. package/README.md +18 -3
  2. package/dist/{GLTFLoader-DW6wpvyR.mjs → GLTFLoader-B4nnixLv.mjs} +1 -1
  3. package/dist/{GLTFLoader-BKnsBFsj.js → GLTFLoader-GATLvS0U.js} +1 -1
  4. package/dist/components/duo-fold-motion.d.ts +1 -1
  5. package/dist/components/duo-view.d.ts +5 -1
  6. package/dist/{duo-frame-DjfA4gG-.js → duo-frame-CfBILL32.js} +2 -2
  7. package/dist/{duo-frame-BzyYljix.mjs → duo-frame-F3hwc09B.mjs} +406 -406
  8. package/dist/duo-view-BsdKpp8d.mjs +662 -0
  9. package/dist/duo-view-Cy8kBdg9.js +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.css +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/lite.cjs +1 -1
  14. package/dist/lite.js +1 -1
  15. package/dist/{remote-control-CQjSEVDF.mjs → remote-control-BAmNHDdv.mjs} +109 -108
  16. package/dist/{remote-control-DVRXfCR2.js → remote-control-F3h8U5pg.js} +1 -1
  17. package/package.json +5 -1
  18. package/dist/demo.d.ts +0 -1
  19. package/dist/duo-view-COZg8ieG.js +0 -1
  20. package/dist/duo-view-o1yxIzxu.mjs +0 -654
  21. package/index.html +0 -199
  22. package/src/assets/Apple_logo_white.svg +0 -4
  23. package/src/assets/android_boot.gif +0 -0
  24. package/src/assets/android_boot.webp +0 -0
  25. package/src/assets/iphone16pro_black.webp +0 -0
  26. package/src/assets/iphone16pro_black_bg.webp +0 -0
  27. package/src/assets/iphone16pro_black_landscape.webp +0 -0
  28. package/src/assets/iphone16pro_black_landscape_bg.webp +0 -0
  29. package/src/assets/pixel9_black.webp +0 -0
  30. package/src/assets/pixel9_black_landscape.webp +0 -0
  31. package/src/assets/pixel_tablet_landscape.webp +0 -0
  32. package/src/assets/pixel_tablet_portrait.webp +0 -0
  33. package/src/components/device-install/index.ts +0 -1
  34. package/src/components/duo-button-hinge.test.tsx +0 -138
  35. package/src/components/duo-button-hinge.ts +0 -75
  36. package/src/components/duo-controls.tsx +0 -142
  37. package/src/components/duo-flat-frame.tsx +0 -145
  38. package/src/components/duo-flat-geometry.test.ts +0 -63
  39. package/src/components/duo-flat-geometry.ts +0 -97
  40. package/src/components/duo-fold-motion.test.tsx +0 -245
  41. package/src/components/duo-fold-motion.tsx +0 -192
  42. package/src/components/duo-fold-renderer.test.ts +0 -29
  43. package/src/components/duo-fold-renderer.ts +0 -397
  44. package/src/components/duo-fold-timing.test.ts +0 -44
  45. package/src/components/duo-fold-timing.ts +0 -9
  46. package/src/components/duo-frame.tsx +0 -579
  47. package/src/components/duo-hardware.test.ts +0 -61
  48. package/src/components/duo-hardware.ts +0 -61
  49. package/src/components/duo-model.test.ts +0 -243
  50. package/src/components/duo-model.ts +0 -607
  51. package/src/components/duo-screen-handoff.ts +0 -52
  52. package/src/components/duo-view.test.tsx +0 -104
  53. package/src/components/duo-view.tsx +0 -315
  54. package/src/components/inspect-overlay.css +0 -223
  55. package/src/components/inspect-overlay.tsx +0 -437
  56. package/src/components/remote-control-framed.tsx +0 -14
  57. package/src/components/remote-control.css +0 -441
  58. package/src/components/remote-control.test.ts +0 -34
  59. package/src/components/remote-control.tsx +0 -3989
  60. package/src/core/ax-fetcher.test.ts +0 -418
  61. package/src/core/ax-fetcher.ts +0 -387
  62. package/src/core/ax-tree.test.ts +0 -491
  63. package/src/core/ax-tree.ts +0 -416
  64. package/src/core/camera.test.ts +0 -155
  65. package/src/core/camera.ts +0 -133
  66. package/src/core/constants.ts +0 -344
  67. package/src/core/duo.test.ts +0 -142
  68. package/src/core/duo.ts +0 -156
  69. package/src/core/webrtc-messages.ts +0 -148
  70. package/src/demo.tsx +0 -283
  71. package/src/frame-assets.ts +0 -22
  72. package/src/hooks/index.ts +0 -1
  73. package/src/index.ts +0 -29
  74. package/src/lite.test.ts +0 -30
  75. package/src/lite.ts +0 -30
  76. package/src/vite-env.d.ts +0 -1
  77. package/tsconfig.json +0 -26
  78. package/tsconfig.node.json +0 -26
  79. package/vite.config.ts +0 -35
  80. package/vitest.config.ts +0 -24
@@ -1,344 +0,0 @@
1
- export const CONTROL_MSG_TYPE = {
2
- INJECT_KEYCODE: 0,
3
- INJECT_TEXT: 1,
4
- INJECT_TOUCH_EVENT: 2,
5
- // iOS-specific: send two touch points in one message (Option/Alt+drag pinch)
6
- INJECT_TWO_FINGER_TOUCH_EVENT: 18,
7
- INJECT_SCROLL_EVENT: 3,
8
- BACK_OR_SCREEN_ON: 4,
9
- EXPAND_NOTIFICATION_PANEL: 5,
10
- EXPAND_SETTINGS_PANEL: 6,
11
- COLLAPSE_PANELS: 7,
12
- GET_CLIPBOARD: 8,
13
- SET_CLIPBOARD: 9,
14
- SET_DISPLAY_POWER: 10,
15
- ROTATE_DEVICE: 11,
16
- } as const;
17
-
18
- export const AMOTION_EVENT = {
19
- ACTION_DOWN: 0,
20
- ACTION_UP: 1,
21
- ACTION_MOVE: 2,
22
- ACTION_CANCEL: 3,
23
- ACTION_POINTER_DOWN: 5,
24
- ACTION_POINTER_UP: 6,
25
- BUTTON_PRIMARY: 1,
26
- BUTTON_SECONDARY: 2,
27
- BUTTON_TERTIARY: 4,
28
- } as const;
29
-
30
- export const ANDROID_KEYS = {
31
- // Actions
32
- ACTION_DOWN: 0,
33
- ACTION_UP: 1,
34
-
35
- // Meta state flags (from android.view.KeyEvent)
36
- META_NONE: 0,
37
- META_SHIFT_ON: 1,
38
- META_ALT_ON: 2,
39
- META_CTRL_ON: 4096, // META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON
40
- META_META_ON: 65536, // META_META_LEFT_ON | META_META_RIGHT_ON
41
-
42
- // Keycodes (from android.view.KeyEvent)
43
- KEYCODE_UNKNOWN: 0,
44
- KEYCODE_SOFT_LEFT: 1,
45
- KEYCODE_SOFT_RIGHT: 2,
46
- KEYCODE_HOME: 3,
47
- KEYCODE_BACK: 4,
48
- KEYCODE_CALL: 5,
49
- KEYCODE_ENDCALL: 6,
50
- KEYCODE_0: 7,
51
- KEYCODE_1: 8,
52
- KEYCODE_2: 9,
53
- KEYCODE_3: 10,
54
- KEYCODE_4: 11,
55
- KEYCODE_5: 12,
56
- KEYCODE_6: 13,
57
- KEYCODE_7: 14,
58
- KEYCODE_8: 15,
59
- KEYCODE_9: 16,
60
- KEYCODE_STAR: 17,
61
- KEYCODE_POUND: 18,
62
- DPAD_UP: 19, // KEYCODE_DPAD_UP
63
- DPAD_DOWN: 20, // KEYCODE_DPAD_DOWN
64
- DPAD_LEFT: 21, // KEYCODE_DPAD_LEFT
65
- DPAD_RIGHT: 22, // KEYCODE_DPAD_RIGHT
66
- KEYCODE_DPAD_CENTER: 23,
67
- KEYCODE_VOLUME_UP: 24,
68
- KEYCODE_VOLUME_DOWN: 25,
69
- KEYCODE_POWER: 26,
70
- KEYCODE_CAMERA: 27,
71
- KEYCODE_CLEAR: 28,
72
- KEYCODE_A: 29,
73
- KEYCODE_B: 30,
74
- KEYCODE_C: 31,
75
- KEYCODE_D: 32,
76
- KEYCODE_E: 33,
77
- KEYCODE_F: 34,
78
- KEYCODE_G: 35,
79
- KEYCODE_H: 36,
80
- KEYCODE_I: 37,
81
- KEYCODE_J: 38,
82
- KEYCODE_K: 39,
83
- KEYCODE_L: 40,
84
- KEYCODE_M: 41,
85
- KEYCODE_N: 42,
86
- KEYCODE_O: 43,
87
- KEYCODE_P: 44,
88
- KEYCODE_Q: 45,
89
- KEYCODE_R: 46,
90
- KEYCODE_S: 47,
91
- KEYCODE_T: 48,
92
- KEYCODE_U: 49,
93
- KEYCODE_V: 50,
94
- KEYCODE_W: 51,
95
- KEYCODE_X: 52,
96
- KEYCODE_Y: 53,
97
- KEYCODE_Z: 54,
98
- KEYCODE_COMMA: 55,
99
- KEYCODE_PERIOD: 56,
100
- KEYCODE_ALT_LEFT: 57,
101
- KEYCODE_ALT_RIGHT: 58,
102
- KEYCODE_SHIFT_LEFT: 59,
103
- KEYCODE_SHIFT_RIGHT: 60,
104
- KEYCODE_TAB: 61,
105
- KEYCODE_SPACE: 62,
106
- KEYCODE_SYM: 63,
107
- KEYCODE_EXPLORER: 64,
108
- KEYCODE_ENVELOPE: 65,
109
- ENTER: 66, // KEYCODE_ENTER
110
- DEL: 67, // KEYCODE_DEL (Backspace)
111
- KEYCODE_GRAVE: 68, // `
112
- KEYCODE_MINUS: 69, // -
113
- KEYCODE_EQUALS: 70, // =
114
- KEYCODE_LEFT_BRACKET: 71, // [
115
- KEYCODE_RIGHT_BRACKET: 72, // ]
116
- KEYCODE_BACKSLASH: 73, // \
117
- KEYCODE_SEMICOLON: 74, // ;
118
- KEYCODE_APOSTROPHE: 75, // '
119
- KEYCODE_SLASH: 76, // /
120
- KEYCODE_AT: 77, // @
121
- KEYCODE_NUM: 78,
122
- KEYCODE_HEADSETHOOK: 79,
123
- KEYCODE_FOCUS: 80,
124
- KEYCODE_PLUS: 81,
125
- MENU: 82, // KEYCODE_MENU
126
- KEYCODE_NOTIFICATION: 83,
127
- KEYCODE_SEARCH: 84,
128
- KEYCODE_MEDIA_PLAY_PAUSE: 85,
129
- KEYCODE_MEDIA_STOP: 86,
130
- KEYCODE_MEDIA_NEXT: 87,
131
- KEYCODE_MEDIA_PREVIOUS: 88,
132
- KEYCODE_MEDIA_REWIND: 89,
133
- KEYCODE_MEDIA_FAST_FORWARD: 90,
134
- KEYCODE_MUTE: 91,
135
- KEYCODE_PAGE_UP: 92,
136
- KEYCODE_PAGE_DOWN: 93,
137
- KEYCODE_PICTSYMBOLS: 94,
138
- KEYCODE_SWITCH_CHARSET: 95,
139
- KEYCODE_BUTTON_A: 96,
140
- KEYCODE_BUTTON_B: 97,
141
- KEYCODE_BUTTON_C: 98,
142
- KEYCODE_BUTTON_X: 99,
143
- KEYCODE_BUTTON_Y: 100,
144
- KEYCODE_BUTTON_Z: 101,
145
- KEYCODE_BUTTON_L1: 102,
146
- KEYCODE_BUTTON_R1: 103,
147
- KEYCODE_BUTTON_L2: 104,
148
- KEYCODE_BUTTON_R2: 105,
149
- KEYCODE_BUTTON_THUMBL: 106,
150
- KEYCODE_BUTTON_THUMBR: 107,
151
- KEYCODE_BUTTON_START: 108,
152
- KEYCODE_BUTTON_SELECT: 109,
153
- KEYCODE_BUTTON_MODE: 110,
154
- KEYCODE_ESCAPE: 111,
155
- FORWARD_DEL: 112, // KEYCODE_FORWARD_DEL (Delete key)
156
- KEYCODE_CTRL_LEFT: 113,
157
- KEYCODE_CTRL_RIGHT: 114,
158
- KEYCODE_CAPS_LOCK: 115,
159
- KEYCODE_SCROLL_LOCK: 116,
160
- KEYCODE_META_LEFT: 117,
161
- KEYCODE_META_RIGHT: 118,
162
- KEYCODE_FUNCTION: 119,
163
- KEYCODE_SYSRQ: 120,
164
- KEYCODE_BREAK: 121,
165
- KEYCODE_MOVE_HOME: 122,
166
- KEYCODE_MOVE_END: 123,
167
- KEYCODE_INSERT: 124,
168
- KEYCODE_FORWARD: 125,
169
- KEYCODE_MEDIA_PLAY: 126,
170
- KEYCODE_MEDIA_PAUSE: 127,
171
- KEYCODE_MEDIA_CLOSE: 128,
172
- KEYCODE_MEDIA_EJECT: 129,
173
- KEYCODE_MEDIA_RECORD: 130,
174
- KEYCODE_F1: 131,
175
- KEYCODE_F2: 132,
176
- KEYCODE_F3: 133,
177
- KEYCODE_F4: 134,
178
- KEYCODE_F5: 135,
179
- KEYCODE_F6: 136,
180
- KEYCODE_F7: 137,
181
- KEYCODE_F8: 138,
182
- KEYCODE_F9: 139,
183
- KEYCODE_F10: 140,
184
- KEYCODE_F11: 141,
185
- KEYCODE_F12: 142,
186
- KEYCODE_NUM_LOCK: 143,
187
- KEYCODE_NUMPAD_0: 144,
188
- KEYCODE_NUMPAD_1: 145,
189
- KEYCODE_NUMPAD_2: 146,
190
- KEYCODE_NUMPAD_3: 147,
191
- KEYCODE_NUMPAD_4: 148,
192
- KEYCODE_NUMPAD_5: 149,
193
- KEYCODE_NUMPAD_6: 150,
194
- KEYCODE_NUMPAD_7: 151,
195
- KEYCODE_NUMPAD_8: 152,
196
- KEYCODE_NUMPAD_9: 153,
197
- KEYCODE_NUMPAD_DIVIDE: 154,
198
- KEYCODE_NUMPAD_MULTIPLY: 155,
199
- KEYCODE_NUMPAD_SUBTRACT: 156,
200
- KEYCODE_NUMPAD_ADD: 157,
201
- KEYCODE_NUMPAD_DOT: 158,
202
- KEYCODE_NUMPAD_COMMA: 159,
203
- KEYCODE_NUMPAD_ENTER: 160,
204
- KEYCODE_NUMPAD_EQUALS: 161,
205
- KEYCODE_NUMPAD_LEFT_PAREN: 162,
206
- KEYCODE_NUMPAD_RIGHT_PAREN: 163,
207
- KEYCODE_VOLUME_MUTE: 164,
208
- KEYCODE_INFO: 165,
209
- KEYCODE_CHANNEL_UP: 166,
210
- KEYCODE_CHANNEL_DOWN: 167,
211
- KEYCODE_ZOOM_IN: 168,
212
- KEYCODE_ZOOM_OUT: 169,
213
- KEYCODE_TV: 170,
214
- KEYCODE_WINDOW: 171,
215
- KEYCODE_GUIDE: 172,
216
- KEYCODE_DVR: 173,
217
- KEYCODE_BOOKMARK: 174,
218
- KEYCODE_CAPTIONS: 175,
219
- KEYCODE_SETTINGS: 176,
220
- KEYCODE_TV_POWER: 177,
221
- KEYCODE_TV_INPUT: 178,
222
- KEYCODE_STB_POWER: 179,
223
- KEYCODE_STB_INPUT: 180,
224
- KEYCODE_AVR_POWER: 181,
225
- KEYCODE_AVR_INPUT: 182,
226
- KEYCODE_PROG_RED: 183,
227
- KEYCODE_PROG_GREEN: 184,
228
- KEYCODE_PROG_YELLOW: 185,
229
- KEYCODE_PROG_BLUE: 186,
230
- KEYCODE_APP_SWITCH: 187,
231
- KEYCODE_LANGUAGE_SWITCH: 204,
232
- KEYCODE_ASSIST: 219,
233
- KEYCODE_BRIGHTNESS_DOWN: 220,
234
- KEYCODE_BRIGHTNESS_UP: 221,
235
- KEYCODE_SLEEP: 223,
236
- KEYCODE_WAKEUP: 224,
237
- KEYCODE_SOFT_SLEEP: 276,
238
- } as const;
239
-
240
- // Map based on event.code for layout independence
241
- export const codeMap: { [code: string]: number } = {
242
- KeyA: ANDROID_KEYS.KEYCODE_A,
243
- KeyB: ANDROID_KEYS.KEYCODE_B,
244
- KeyC: ANDROID_KEYS.KEYCODE_C,
245
- KeyD: ANDROID_KEYS.KEYCODE_D,
246
- KeyE: ANDROID_KEYS.KEYCODE_E,
247
- KeyF: ANDROID_KEYS.KEYCODE_F,
248
- KeyG: ANDROID_KEYS.KEYCODE_G,
249
- KeyH: ANDROID_KEYS.KEYCODE_H,
250
- KeyI: ANDROID_KEYS.KEYCODE_I,
251
- KeyJ: ANDROID_KEYS.KEYCODE_J,
252
- KeyK: ANDROID_KEYS.KEYCODE_K,
253
- KeyL: ANDROID_KEYS.KEYCODE_L,
254
- KeyM: ANDROID_KEYS.KEYCODE_M,
255
- KeyN: ANDROID_KEYS.KEYCODE_N,
256
- KeyO: ANDROID_KEYS.KEYCODE_O,
257
- KeyP: ANDROID_KEYS.KEYCODE_P,
258
- KeyQ: ANDROID_KEYS.KEYCODE_Q,
259
- KeyR: ANDROID_KEYS.KEYCODE_R,
260
- KeyS: ANDROID_KEYS.KEYCODE_S,
261
- KeyT: ANDROID_KEYS.KEYCODE_T,
262
- KeyU: ANDROID_KEYS.KEYCODE_U,
263
- KeyV: ANDROID_KEYS.KEYCODE_V,
264
- KeyW: ANDROID_KEYS.KEYCODE_W,
265
- KeyX: ANDROID_KEYS.KEYCODE_X,
266
- KeyY: ANDROID_KEYS.KEYCODE_Y,
267
- KeyZ: ANDROID_KEYS.KEYCODE_Z,
268
- Digit0: ANDROID_KEYS.KEYCODE_0,
269
- Digit1: ANDROID_KEYS.KEYCODE_1,
270
- Digit2: ANDROID_KEYS.KEYCODE_2,
271
- Digit3: ANDROID_KEYS.KEYCODE_3,
272
- Digit4: ANDROID_KEYS.KEYCODE_4,
273
- Digit5: ANDROID_KEYS.KEYCODE_5,
274
- Digit6: ANDROID_KEYS.KEYCODE_6,
275
- Digit7: ANDROID_KEYS.KEYCODE_7,
276
- Digit8: ANDROID_KEYS.KEYCODE_8,
277
- Digit9: ANDROID_KEYS.KEYCODE_9,
278
- Backquote: ANDROID_KEYS.KEYCODE_GRAVE,
279
- Minus: ANDROID_KEYS.KEYCODE_MINUS,
280
- Equal: ANDROID_KEYS.KEYCODE_EQUALS,
281
- BracketLeft: ANDROID_KEYS.KEYCODE_LEFT_BRACKET,
282
- BracketRight: ANDROID_KEYS.KEYCODE_RIGHT_BRACKET,
283
- Backslash: ANDROID_KEYS.KEYCODE_BACKSLASH,
284
- Semicolon: ANDROID_KEYS.KEYCODE_SEMICOLON,
285
- Quote: ANDROID_KEYS.KEYCODE_APOSTROPHE,
286
- Comma: ANDROID_KEYS.KEYCODE_COMMA,
287
- Period: ANDROID_KEYS.KEYCODE_PERIOD,
288
- Slash: ANDROID_KEYS.KEYCODE_SLASH,
289
- Space: ANDROID_KEYS.KEYCODE_SPACE,
290
- Tab: ANDROID_KEYS.KEYCODE_TAB,
291
- Escape: ANDROID_KEYS.KEYCODE_ESCAPE,
292
- ArrowUp: ANDROID_KEYS.DPAD_UP,
293
- ArrowDown: ANDROID_KEYS.DPAD_DOWN,
294
- ArrowLeft: ANDROID_KEYS.DPAD_LEFT,
295
- ArrowRight: ANDROID_KEYS.DPAD_RIGHT,
296
- Enter: ANDROID_KEYS.ENTER,
297
- Backspace: ANDROID_KEYS.DEL,
298
- Delete: ANDROID_KEYS.FORWARD_DEL,
299
- Home: ANDROID_KEYS.KEYCODE_MOVE_HOME,
300
- End: ANDROID_KEYS.KEYCODE_MOVE_END,
301
- PageUp: ANDROID_KEYS.KEYCODE_PAGE_UP,
302
- PageDown: ANDROID_KEYS.KEYCODE_PAGE_DOWN,
303
- Insert: ANDROID_KEYS.KEYCODE_INSERT,
304
- F1: ANDROID_KEYS.KEYCODE_F1,
305
- F2: ANDROID_KEYS.KEYCODE_F2,
306
- F3: ANDROID_KEYS.KEYCODE_F3,
307
- F4: ANDROID_KEYS.KEYCODE_F4,
308
- F5: ANDROID_KEYS.KEYCODE_F5,
309
- F6: ANDROID_KEYS.KEYCODE_F6,
310
- F7: ANDROID_KEYS.KEYCODE_F7,
311
- F8: ANDROID_KEYS.KEYCODE_F8,
312
- F9: ANDROID_KEYS.KEYCODE_F9,
313
- F10: ANDROID_KEYS.KEYCODE_F10,
314
- F11: ANDROID_KEYS.KEYCODE_F11,
315
- F12: ANDROID_KEYS.KEYCODE_F12,
316
- ShiftLeft: ANDROID_KEYS.KEYCODE_SHIFT_LEFT,
317
- ShiftRight: ANDROID_KEYS.KEYCODE_SHIFT_RIGHT,
318
- ControlLeft: ANDROID_KEYS.KEYCODE_CTRL_LEFT,
319
- ControlRight: ANDROID_KEYS.KEYCODE_CTRL_RIGHT,
320
- AltLeft: ANDROID_KEYS.KEYCODE_ALT_LEFT,
321
- AltRight: ANDROID_KEYS.KEYCODE_ALT_RIGHT,
322
- MetaLeft: ANDROID_KEYS.KEYCODE_META_LEFT,
323
- MetaRight: ANDROID_KEYS.KEYCODE_META_RIGHT, // Windows/Command key
324
- ContextMenu: ANDROID_KEYS.MENU, // Often the Menu key
325
- // Numpad mappings
326
- Numpad0: ANDROID_KEYS.KEYCODE_NUMPAD_0,
327
- Numpad1: ANDROID_KEYS.KEYCODE_NUMPAD_1,
328
- Numpad2: ANDROID_KEYS.KEYCODE_NUMPAD_2,
329
- Numpad3: ANDROID_KEYS.KEYCODE_NUMPAD_3,
330
- Numpad4: ANDROID_KEYS.KEYCODE_NUMPAD_4,
331
- Numpad5: ANDROID_KEYS.KEYCODE_NUMPAD_5,
332
- Numpad6: ANDROID_KEYS.KEYCODE_NUMPAD_6,
333
- Numpad7: ANDROID_KEYS.KEYCODE_NUMPAD_7,
334
- Numpad8: ANDROID_KEYS.KEYCODE_NUMPAD_8,
335
- Numpad9: ANDROID_KEYS.KEYCODE_NUMPAD_9,
336
- NumpadDivide: ANDROID_KEYS.KEYCODE_NUMPAD_DIVIDE,
337
- NumpadMultiply: ANDROID_KEYS.KEYCODE_NUMPAD_MULTIPLY,
338
- NumpadSubtract: ANDROID_KEYS.KEYCODE_NUMPAD_SUBTRACT,
339
- NumpadAdd: ANDROID_KEYS.KEYCODE_NUMPAD_ADD,
340
- NumpadDecimal: ANDROID_KEYS.KEYCODE_NUMPAD_DOT,
341
- NumpadComma: ANDROID_KEYS.KEYCODE_NUMPAD_COMMA, // Some numpads have comma
342
- NumpadEnter: ANDROID_KEYS.KEYCODE_NUMPAD_ENTER,
343
- NumpadEqual: ANDROID_KEYS.KEYCODE_NUMPAD_EQUALS,
344
- };
@@ -1,142 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import {
3
- createDisplayTouchMessage,
4
- flatDisplayTouch,
5
- duoPointerMode,
6
- DuoButtonSender,
7
- HingeSender,
8
- panelTouch,
9
- validHingeAngle,
10
- } from './duo';
11
-
12
- describe('native Duo input', () => {
13
- it.each([
14
- [0, 0.2, 0.3],
15
- [1, 0.3, 0.8],
16
- [2, 0.8, 0.7],
17
- [3, 0.7, 0.2],
18
- ])('maps 2D screen coordinates back to the native panel at rotation %s', (turns, x, y) => {
19
- const point = flatDisplayTouch(0.2, 0.3, turns!);
20
- expect(point.x).toBeCloseTo(x!);
21
- expect(point.y).toBeCloseTo(y!);
22
- });
23
- it('keeps a captured drag inside the native panel after it leaves the screen', () => {
24
- expect(flatDisplayTouch(-1, 2, 1)).toEqual({ x: 1, y: 1 });
25
- });
26
- it('rejects non-finite and out-of-range hinge values', () => {
27
- for (const angle of [NaN, Infinity, -1, 181]) expect(validHingeAngle(angle)).toBe(false);
28
- for (const angle of [0, 45.5, 90, 180]) expect(validHingeAngle(angle)).toBe(true);
29
- });
30
- it('encodes panel identity and top-left coordinates without video rotation', () => {
31
- const point = panelTouch(0.3, 0.6);
32
- const data = new DataView(createDisplayTouchMessage(0, 3, point.x, point.y));
33
- expect(data.byteLength).toBe(12);
34
- expect(data.getUint8(0)).toBe(19);
35
- expect(data.getUint8(2)).toBe(3);
36
- expect(data.getFloat32(4, true)).toBeCloseTo(0.3);
37
- expect(data.getFloat32(8, true)).toBeCloseTo(0.4);
38
- expect(() => createDisplayTouchMessage(0, 3, NaN, 0)).toThrow();
39
- });
40
- it('sends the final slider angle while coalescing intermediate positions', async () => {
41
- let finish!: () => void;
42
- const send = vi
43
- .fn()
44
- .mockImplementationOnce(
45
- () =>
46
- new Promise<void>((resolve) => {
47
- finish = resolve;
48
- }),
49
- )
50
- .mockResolvedValue(undefined);
51
- const sender = new HingeSender(send, vi.fn());
52
- sender.set(10);
53
- sender.set(20);
54
- sender.set(90);
55
- sender.set(180);
56
- expect(send.mock.calls).toEqual([[10]]);
57
- finish();
58
- await vi.waitFor(() => expect(send.mock.calls).toEqual([[10], [180]]));
59
- });
60
- it('drops pending angles after disconnect and reports failures', async () => {
61
- const failed = vi.fn();
62
- const sender = new HingeSender(async () => {
63
- throw new Error('offline');
64
- }, failed);
65
- sender.set(120);
66
- await vi.waitFor(() => expect(failed).toHaveBeenCalledOnce());
67
- sender.stop();
68
- sender.set(0);
69
- expect(failed).toHaveBeenCalledOnce();
70
- });
71
- });
72
-
73
- describe('Duo position lock', () => {
74
- it.each([false, true])('keeps touch active while locked (Alt=%s)', (alt) => {
75
- expect(duoPointerMode(true, true, alt)).toBe('touch');
76
- expect(duoPointerMode(true, false, alt)).toBe('ignore');
77
- });
78
- it('allows deliberate orbit only after unlocking', () => {
79
- expect(duoPointerMode(false, false, false)).toBe('orbit');
80
- expect(duoPointerMode(false, true, false)).toBe('touch');
81
- expect(duoPointerMode(false, true, true)).toBe('orbit');
82
- });
83
- });
84
-
85
- describe('Duo hardware input', () => {
86
- it('orders a quick release after the acknowledged down event', async () => {
87
- let ack!: () => void;
88
- const send = vi
89
- .fn()
90
- .mockImplementationOnce(
91
- () =>
92
- new Promise<void>((resolve) => {
93
- ack = resolve;
94
- }),
95
- )
96
- .mockResolvedValue(undefined);
97
- const sender = new DuoButtonSender(send, vi.fn());
98
- sender.press('side');
99
- sender.release();
100
- sender.release();
101
- await vi.waitFor(() => expect(send.mock.calls).toEqual([['side', true]]));
102
- ack();
103
- await vi.waitFor(() =>
104
- expect(send.mock.calls).toEqual([
105
- ['side', true],
106
- ['side', false],
107
- ]),
108
- );
109
- });
110
- it('still releases when a down event fails or times out', async () => {
111
- const send = vi.fn().mockRejectedValueOnce(new Error('timeout')).mockResolvedValue(undefined);
112
- const failed = vi.fn();
113
- const sender = new DuoButtonSender(send, failed);
114
- sender.press('volumeDown');
115
- sender.press('volumeUp');
116
- sender.release();
117
- await vi.waitFor(() =>
118
- expect(send.mock.calls).toEqual([
119
- ['volumeDown', true],
120
- ['volumeDown', false],
121
- ]),
122
- );
123
- expect(failed).toHaveBeenCalledOnce();
124
- });
125
- it('releases a button if the browser loses the pointer release', async () => {
126
- vi.useFakeTimers();
127
- try {
128
- const send = vi.fn().mockResolvedValue(undefined);
129
- const sender = new DuoButtonSender(send, vi.fn());
130
- sender.press('volumeUp');
131
- await vi.advanceTimersByTimeAsync(10000);
132
- expect(send.mock.calls).toEqual([
133
- ['volumeUp', true],
134
- ['volumeUp', false],
135
- ]);
136
- sender.release();
137
- expect(send).toHaveBeenCalledTimes(2);
138
- } finally {
139
- vi.useRealTimers();
140
- }
141
- });
142
- });
package/src/core/duo.ts DELETED
@@ -1,156 +0,0 @@
1
- /** Native iPhone Duo display metadata. Pixel geometry comes from CoreSimulator. */
2
- export interface DuoDisplay {
3
- id: 'outer' | 'inner';
4
- screenId: number;
5
- trackId: string;
6
- width: number;
7
- height: number;
8
- scale: number;
9
- orientation: number;
10
- }
11
-
12
- export type DuoOrientation = 'portrait' | 'pud' | 'landscape-left' | 'landscape-right';
13
-
14
- export interface DuoState {
15
- orientation: DuoOrientation;
16
- angleDegrees: number;
17
- minAngleDegrees: number;
18
- maxAngleDegrees: number;
19
- displays: DuoDisplay[];
20
- }
21
-
22
- export const DUO_DIMENSIONS = { width: 164.6, height: 117.8, depth: 5.2 } as const;
23
-
24
- /** Invert the CSS display rotation before sending native panel coordinates. */
25
- export function flatDisplayTouch(x: number, y: number, turns: number): { x: number; y: number } {
26
- x = Math.max(0, Math.min(1, x));
27
- y = Math.max(0, Math.min(1, y));
28
- if (turns === 1) return { x: y, y: 1 - x };
29
- if (turns === 2) return { x: 1 - x, y: 1 - y };
30
- if (turns === 3) return { x: 1 - y, y: x };
31
- return { x, y };
32
- }
33
-
34
- export function validHingeAngle(angle: number): boolean {
35
- return Number.isFinite(angle) && angle >= 0 && angle <= 180;
36
- }
37
-
38
- /** Mesh UVs use the native portrait panel; HID has a top-left origin. */
39
- export function panelTouch(u: number, v: number): { x: number; y: number } {
40
- return { x: Math.max(0, Math.min(1, u)), y: Math.max(0, Math.min(1, 1 - v)) };
41
- }
42
-
43
- /** Locking the view never suppresses screen input, including accidental Alt presses. */
44
- export function duoPointerMode(
45
- locked: boolean,
46
- screenHit: boolean,
47
- altKey: boolean,
48
- ): 'touch' | 'orbit' | 'ignore' {
49
- if (screenHit && (locked || !altKey)) return 'touch';
50
- return locked ? 'ignore' : 'orbit';
51
- }
52
-
53
- export function createDisplayTouchMessage(
54
- action: number,
55
- screenId: number,
56
- x: number,
57
- y: number,
58
- ): ArrayBuffer {
59
- if (
60
- !Number.isInteger(action) ||
61
- action < 0 ||
62
- action > 3 ||
63
- !Number.isInteger(screenId) ||
64
- screenId < 1 ||
65
- screenId > 255 ||
66
- !Number.isFinite(x) ||
67
- !Number.isFinite(y) ||
68
- x < 0 ||
69
- x > 1 ||
70
- y < 0 ||
71
- y > 1
72
- ) {
73
- throw new RangeError('Invalid display touch coordinates');
74
- }
75
- const message = new ArrayBuffer(12);
76
- const view = new DataView(message);
77
- view.setUint8(0, 19);
78
- view.setUint8(1, action);
79
- view.setUint8(2, screenId);
80
- view.setFloat32(4, x, true);
81
- view.setFloat32(8, y, true);
82
- return message;
83
- }
84
-
85
- /** One request in flight; intermediate slider positions are replaced by the newest position. */
86
- export class HingeSender {
87
- private latest: number | undefined;
88
- private running = false;
89
- private stopped = false;
90
- constructor(
91
- private send: (angle: number) => Promise<void>,
92
- private failed: (error: unknown) => void,
93
- ) {}
94
-
95
- set(angle: number): void {
96
- if (this.stopped || !validHingeAngle(angle)) return;
97
- this.latest = angle;
98
- void this.flush();
99
- }
100
-
101
- stop(): void {
102
- this.stopped = true;
103
- this.latest = undefined;
104
- }
105
-
106
- private async flush(): Promise<void> {
107
- if (this.running || this.stopped) return;
108
- this.running = true;
109
- try {
110
- while (this.latest !== undefined && !this.stopped) {
111
- const angle = this.latest;
112
- this.latest = undefined;
113
- await this.send(angle);
114
- }
115
- } catch (error) {
116
- this.latest = undefined;
117
- if (!this.stopped) this.failed(error);
118
- } finally {
119
- this.running = false;
120
- }
121
- }
122
- }
123
-
124
- export type DuoButton = 'side' | 'volumeUp' | 'volumeDown';
125
- export const DUO_BUTTONS: Record<DuoButton, string> = {
126
- side: 'Sleep/Wake',
127
- volumeUp: 'Volume Up',
128
- volumeDown: 'Volume Down',
129
- };
130
-
131
- /** Keeps button edges ordered, including releases after cancellation or a failed press. */
132
- export class DuoButtonSender {
133
- private held?: DuoButton;
134
- private tail = Promise.resolve();
135
- private timeout?: ReturnType<typeof setTimeout>;
136
- constructor(
137
- private send: (button: DuoButton, down: boolean) => Promise<void>,
138
- private failed: (error: unknown) => void,
139
- ) {}
140
- press(button: DuoButton): void {
141
- if (this.held) return;
142
- this.held = button;
143
- this.enqueue(button, true);
144
- this.timeout = setTimeout(() => this.release(), 10000);
145
- }
146
- release(): void {
147
- if (!this.held) return;
148
- clearTimeout(this.timeout);
149
- const button = this.held;
150
- this.held = undefined;
151
- this.enqueue(button, false);
152
- }
153
- private enqueue(button: DuoButton, down: boolean): void {
154
- this.tail = this.tail.then(() => this.send(button, down)).catch(this.failed);
155
- }
156
- }