@ledgerhq/device-mockserver-react 0.0.0-mockserver-react-20260929114136 → 0.0.0-mockserver-rn-20261103093711

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 (110) hide show
  1. package/README.md +6 -3
  2. package/lib/cjs/package.json +6 -5
  3. package/lib/cjs/src/DeviceOsInfo.js +1 -1
  4. package/lib/cjs/src/DeviceOsInfo.js.map +3 -3
  5. package/lib/cjs/src/DeviceOsInfo.test.js +2 -0
  6. package/lib/cjs/src/DeviceOsInfo.test.js.map +7 -0
  7. package/lib/cjs/src/DeviceScreen.js +1 -1
  8. package/lib/cjs/src/DeviceScreen.js.map +3 -3
  9. package/lib/cjs/src/DeviceScreen.test.js +2 -0
  10. package/lib/cjs/src/DeviceScreen.test.js.map +7 -0
  11. package/lib/cjs/src/DeviceScreenButtons.js +1 -1
  12. package/lib/cjs/src/DeviceScreenButtons.js.map +3 -3
  13. package/lib/cjs/src/DeviceScreenButtons.test.js +2 -0
  14. package/lib/cjs/src/DeviceScreenButtons.test.js.map +7 -0
  15. package/lib/cjs/src/DeviceScreenImage.js +1 -1
  16. package/lib/cjs/src/DeviceScreenImage.js.map +3 -3
  17. package/lib/cjs/src/DeviceScreenImage.test.js +2 -0
  18. package/lib/cjs/src/DeviceScreenImage.test.js.map +7 -0
  19. package/lib/cjs/src/MockServerDevice.js +1 -1
  20. package/lib/cjs/src/MockServerDevice.js.map +3 -3
  21. package/lib/cjs/src/MockServerDevice.test.js +2 -0
  22. package/lib/cjs/src/MockServerDevice.test.js.map +7 -0
  23. package/lib/cjs/src/deviceModels.js +1 -1
  24. package/lib/cjs/src/deviceModels.js.map +3 -3
  25. package/lib/cjs/src/index.js +1 -1
  26. package/lib/cjs/src/index.js.map +3 -3
  27. package/lib/cjs/src/webScreenPlatform.js +2 -0
  28. package/lib/cjs/src/webScreenPlatform.js.map +7 -0
  29. package/lib/esm/package.json +6 -5
  30. package/lib/esm/src/DeviceOsInfo.js +1 -1
  31. package/lib/esm/src/DeviceOsInfo.js.map +3 -3
  32. package/lib/esm/src/DeviceOsInfo.test.js +2 -0
  33. package/lib/esm/src/DeviceOsInfo.test.js.map +7 -0
  34. package/lib/esm/src/DeviceScreen.js +1 -1
  35. package/lib/esm/src/DeviceScreen.js.map +3 -3
  36. package/lib/esm/src/DeviceScreen.test.js +2 -0
  37. package/lib/esm/src/DeviceScreen.test.js.map +7 -0
  38. package/lib/esm/src/DeviceScreenButtons.js +1 -1
  39. package/lib/esm/src/DeviceScreenButtons.js.map +3 -3
  40. package/lib/esm/src/DeviceScreenButtons.test.js +2 -0
  41. package/lib/esm/src/DeviceScreenButtons.test.js.map +7 -0
  42. package/lib/esm/src/DeviceScreenImage.js +1 -1
  43. package/lib/esm/src/DeviceScreenImage.js.map +3 -3
  44. package/lib/esm/src/DeviceScreenImage.test.js +2 -0
  45. package/lib/esm/src/DeviceScreenImage.test.js.map +7 -0
  46. package/lib/esm/src/MockServerDevice.js +1 -1
  47. package/lib/esm/src/MockServerDevice.js.map +3 -3
  48. package/lib/esm/src/MockServerDevice.test.js +2 -0
  49. package/lib/esm/src/MockServerDevice.test.js.map +7 -0
  50. package/lib/esm/src/deviceModels.js +1 -1
  51. package/lib/esm/src/deviceModels.js.map +3 -3
  52. package/lib/esm/src/index.js +1 -1
  53. package/lib/esm/src/index.js.map +2 -2
  54. package/lib/esm/src/webScreenPlatform.js +2 -0
  55. package/lib/esm/src/webScreenPlatform.js.map +7 -0
  56. package/lib/types/src/DeviceOsInfo.d.ts.map +1 -1
  57. package/lib/types/src/DeviceOsInfo.test.d.ts +2 -0
  58. package/lib/types/src/DeviceOsInfo.test.d.ts.map +1 -0
  59. package/lib/types/src/DeviceScreen.d.ts +1 -1
  60. package/lib/types/src/DeviceScreen.d.ts.map +1 -1
  61. package/lib/types/src/DeviceScreen.test.d.ts +2 -0
  62. package/lib/types/src/DeviceScreen.test.d.ts.map +1 -0
  63. package/lib/types/src/DeviceScreenButtons.d.ts.map +1 -1
  64. package/lib/types/src/DeviceScreenButtons.test.d.ts +2 -0
  65. package/lib/types/src/DeviceScreenButtons.test.d.ts.map +1 -0
  66. package/lib/types/src/DeviceScreenImage.d.ts.map +1 -1
  67. package/lib/types/src/DeviceScreenImage.test.d.ts +2 -0
  68. package/lib/types/src/DeviceScreenImage.test.d.ts.map +1 -0
  69. package/lib/types/src/MockServerDevice.d.ts +2 -2
  70. package/lib/types/src/MockServerDevice.d.ts.map +1 -1
  71. package/lib/types/src/MockServerDevice.test.d.ts +2 -0
  72. package/lib/types/src/MockServerDevice.test.d.ts.map +1 -0
  73. package/lib/types/src/deviceModels.d.ts +2 -9
  74. package/lib/types/src/deviceModels.d.ts.map +1 -1
  75. package/lib/types/src/index.d.ts +1 -3
  76. package/lib/types/src/index.d.ts.map +1 -1
  77. package/lib/types/src/webScreenPlatform.d.ts +3 -0
  78. package/lib/types/src/webScreenPlatform.d.ts.map +1 -0
  79. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  80. package/package.json +11 -10
  81. package/lib/cjs/src/mockServerScreenApi.js +0 -2
  82. package/lib/cjs/src/mockServerScreenApi.js.map +0 -7
  83. package/lib/cjs/src/mockServerScreenApi.test.js +0 -2
  84. package/lib/cjs/src/mockServerScreenApi.test.js.map +0 -7
  85. package/lib/cjs/src/types.js +0 -2
  86. package/lib/cjs/src/types.js.map +0 -7
  87. package/lib/cjs/src/useMockServerDevice.js +0 -2
  88. package/lib/cjs/src/useMockServerDevice.js.map +0 -7
  89. package/lib/cjs/src/useScreenPolling.js +0 -2
  90. package/lib/cjs/src/useScreenPolling.js.map +0 -7
  91. package/lib/esm/src/mockServerScreenApi.js +0 -2
  92. package/lib/esm/src/mockServerScreenApi.js.map +0 -7
  93. package/lib/esm/src/mockServerScreenApi.test.js +0 -2
  94. package/lib/esm/src/mockServerScreenApi.test.js.map +0 -7
  95. package/lib/esm/src/types.js +0 -1
  96. package/lib/esm/src/types.js.map +0 -7
  97. package/lib/esm/src/useMockServerDevice.js +0 -2
  98. package/lib/esm/src/useMockServerDevice.js.map +0 -7
  99. package/lib/esm/src/useScreenPolling.js +0 -2
  100. package/lib/esm/src/useScreenPolling.js.map +0 -7
  101. package/lib/types/src/mockServerScreenApi.d.ts +0 -4
  102. package/lib/types/src/mockServerScreenApi.d.ts.map +0 -1
  103. package/lib/types/src/mockServerScreenApi.test.d.ts +0 -2
  104. package/lib/types/src/mockServerScreenApi.test.d.ts.map +0 -1
  105. package/lib/types/src/types.d.ts +0 -41
  106. package/lib/types/src/types.d.ts.map +0 -1
  107. package/lib/types/src/useMockServerDevice.d.ts +0 -7
  108. package/lib/types/src/useMockServerDevice.d.ts.map +0 -1
  109. package/lib/types/src/useScreenPolling.d.ts +0 -3
  110. package/lib/types/src/useScreenPolling.d.ts.map +0 -1
package/README.md CHANGED
@@ -6,7 +6,9 @@ both buttons for the Nano. A device that runs no app has no Speculos instance,
6
6
  so its record from the mock server is shown instead.
7
7
 
8
8
  Built with [Lumen](https://www.npmjs.com/package/@ledgerhq/lumen-ui-react), so
9
- the host renders it inside a Lumen `ThemeProvider` and a Tailwind build.
9
+ the host renders it inside a Lumen `ThemeProvider` and a Tailwind build. For a React
10
+ Native app, use [`@ledgerhq/device-mockserver-react-native`](../mockserver-device-react-native)
11
+ instead. Both draw the hooks of [`@ledgerhq/device-mockserver-react-core`](../mockserver-device-react-core).
10
12
 
11
13
  ## Usage
12
14
 
@@ -24,14 +26,15 @@ import { MockServerDevice } from "@ledgerhq/device-mockserver-react";
24
26
  | Prop | Description |
25
27
  | ------------------ | ------------------------------------------------------------------------------------ |
26
28
  | `url` | Mock server URL. |
27
- | `token` | Session token. Omitted, the client opens a session of its own. |
29
+ | `token` | Token of the session that owns the device. |
28
30
  | `deviceId` | Mock server device id. |
29
31
  | `floating` | A window over the page, dragged by its header and collapsed to it. Inline otherwise. |
30
32
  | `defaultCollapsed` | Whether the floating window starts collapsed. |
31
33
 
32
34
  `DeviceScreen` renders the same screen from any `ScreenApi`, for instance one
33
35
  that talks to Speculos directly. `mockServerScreenApi(client, deviceId)` is the
34
- one `MockServerDevice` uses, for hosts that already hold a `MockClient`, and
36
+ one `MockServerDevice` uses, for hosts that already hold a `MockClient` (any
37
+ object with the `MockServerClient` methods fits), and
35
38
  `findDeviceScreenModel(deviceType)` gives a model's label for their own header.
36
39
 
37
40
  ## Styles
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "dependencies": {
3
- "@ledgerhq/device-mockserver-client": "workspace:*"
3
+ "@ledgerhq/device-mockserver-client": "workspace:*",
4
+ "@ledgerhq/device-mockserver-react-core": "workspace:*"
4
5
  },
5
6
  "devDependencies": {
6
7
  "@ledgerhq/device-management-kit": "workspace:*",
7
8
  "@ledgerhq/eslint-config-dsdk": "workspace:*",
8
9
  "@ledgerhq/ldmk-tool": "workspace:*",
9
- "@ledgerhq/lumen-design-core": "0.1.26",
10
- "@ledgerhq/lumen-ui-react": "0.1.55",
10
+ "@ledgerhq/lumen-design-core": "0.1.34",
11
+ "@ledgerhq/lumen-ui-react": "0.1.66",
11
12
  "@ledgerhq/prettier-config-dsdk": "workspace:*",
12
13
  "@ledgerhq/tsconfig-dsdk": "workspace:*",
13
14
  "@ledgerhq/vitest-config-dmk": "workspace:*",
@@ -38,7 +39,7 @@
38
39
  "name": "@ledgerhq/device-mockserver-react",
39
40
  "peerDependencies": {
40
41
  "@ledgerhq/device-management-kit": "workspace:*",
41
- "@ledgerhq/lumen-ui-react": "^0.1.55",
42
+ "@ledgerhq/lumen-ui-react": "^0.1.66",
42
43
  "react": "catalog:",
43
44
  "react-dom": "catalog:"
44
45
  },
@@ -63,5 +64,5 @@
63
64
  "watch:types": "concurrently \"tsc --watch -p tsconfig.prod.json\" \"tsc-alias --watch -p tsconfig.prod.json\""
64
65
  },
65
66
  "types": "lib/index.d.ts",
66
- "version": "0.0.0-mockserver-react-20260929114136"
67
+ "version": "0.0.0-mockserver-rn-20261103093711"
67
68
  }
@@ -1,2 +1,2 @@
1
- "use strict";var x=Object.create;var a=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,q=Object.prototype.hasOwnProperty;var N=(t,n)=>{for(var r in n)a(t,r,{get:n[r],enumerable:!0})},f=(t,n,r,l)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of y(n))!q.call(t,i)&&i!==r&&a(t,i,{get:()=>n[i],enumerable:!(l=h(n,i))||l.enumerable});return t};var $=(t,n,r)=>(r=t!=null?x(k(t)):{},f(n||!t||!t.__esModule?a(r,"default",{value:t,enumerable:!0}):r,t)),S=t=>f(a({},"__esModule",{value:!0}),t);var I={};N(I,{DeviceOsInfo:()=>E});module.exports=S(I);var e=$(require("react")),w=require("./ScreenFrame");const O="BOLOS",_=1e4,P="[&::-webkit-scrollbar]:size-[6px] [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full",D=t=>`https://cdn.live.ledger.com/icons/${t.toLowerCase().replace(/ /g,"_")}.png`,g=({name:t})=>{const[n,r]=(0,e.useState)(!1);return n?e.default.createElement("span",{className:"text-muted bg-muted flex size-[12.5cqw] items-center justify-center rounded-full text-[length:6cqw] font-semibold"},t.charAt(0)):e.default.createElement("img",{src:D(t),alt:"",draggable:!1,className:"size-[12.5cqw] rounded-full",onError:()=>r(!0)})},E=({device:t,model:n,onOpenApp:r})=>{const l=(t.apps??[]).filter(({name:s})=>s!==O),[i,d]=(0,e.useState)(),[b,u]=(0,e.useState)(),o=n.lightScreen?{background:"bg-white",text:"text-black",scrollbar:"[&::-webkit-scrollbar-thumb]:bg-black/30",overlay:"bg-white/70"}:{background:"bg-black",text:"text-white",scrollbar:"[&::-webkit-scrollbar-thumb]:bg-white/30",overlay:"bg-black/70"},c=n.touch?{scroll:"overflow-y-auto",list:"grid grid-cols-3 gap-[1.5cqw]",tile:"min-w-0 py-[3cqw]",opening:"flex-col"}:{scroll:"flex items-center overflow-x-auto",list:"flex gap-[2.5cqw]",tile:"w-[28cqw] shrink-0 py-[1.5cqw]",opening:"flex-row"},m=(0,e.useRef)(void 0);(0,e.useEffect)(()=>()=>clearTimeout(m.current),[]);const v=s=>{!r||i||(d(s),u(void 0),r(s).then(()=>{m.current=setTimeout(()=>{d(void 0),u(`${s} opened but its screen never showed up`)},_)},p=>{d(void 0),u(p instanceof Error?p.message:String(p))}))};return e.default.createElement("div",{className:"flex flex-col gap-8","data-testid":"container_device-os-info"},e.default.createElement(w.ScreenFrame,null,e.default.createElement("div",{className:`${o.background} ${c.scroll} ${P} ${o.scrollbar} p-[3cqw]`,style:{aspectRatio:n.screenWidth/n.screenHeight},"data-testid":"list_device-apps"},l.length===0?e.default.createElement("p",{className:`text-[length:5.5cqw] ${o.text} flex h-full w-full items-center justify-center opacity-60`},"No apps installed"):e.default.createElement("div",{className:c.list},l.map(s=>e.default.createElement("button",{key:s.name,type:"button",title:`${s.name} ${s.version}`,disabled:!r||i!==void 0,onClick:()=>v(s.name),className:`${o.text} ${c.tile} flex flex-col items-center gap-[1.5cqw] border-0 bg-transparent px-[1.5cqw] disabled:cursor-default enabled:cursor-pointer`,"data-testid":`button_open-app-${s.name}`},e.default.createElement(g,{name:s.name}),e.default.createElement("span",{className:"w-full truncate text-center text-[length:4.7cqw] leading-[1.3]"},s.name))))),i!==void 0?e.default.createElement("div",{className:`${o.text} ${o.overlay} ${c.opening} pointer-events-none absolute inset-0 flex items-center justify-center gap-[3cqw] backdrop-blur-[1cqw]`,"data-testid":"spinner_opening-app-screen"},e.default.createElement("div",{className:"relative flex size-[18cqw] shrink-0 items-center justify-center"},e.default.createElement("span",{className:"absolute inset-0 rounded-full border-[0.8cqw] border-current opacity-15"}),e.default.createElement("span",{className:"absolute inset-0 animate-spin rounded-full border-[0.8cqw] border-transparent border-t-current"}),e.default.createElement(g,{name:i})),e.default.createElement("span",{className:"animate-pulse text-[length:4.7cqw] font-semibold"},"Opening ",i)):null),b?e.default.createElement("p",{className:"body-4 text-error text-center"},b):null)};0&&(module.exports={DeviceOsInfo});
1
+ "use strict";var g=Object.create;var p=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty;var h=(t,n)=>{for(var r in n)p(t,r,{get:n[r],enumerable:!0})},b=(t,n,r,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of x(n))!y.call(t,s)&&s!==r&&p(t,s,{get:()=>n[s],enumerable:!(a=w(n,s))||a.enumerable});return t};var q=(t,n,r)=>(r=t!=null?g(v(t)):{},b(n||!t||!t.__esModule?p(r,"default",{value:t,enumerable:!0}):r,t)),k=t=>b(p({},"__esModule",{value:!0}),t);var A={};h(A,{DeviceOsInfo:()=>$});module.exports=k(A);var e=q(require("react")),i=require("@ledgerhq/device-mockserver-react-core"),m=require("./ScreenFrame");const N="[&::-webkit-scrollbar]:size-[6px] [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full",u=({name:t})=>{const[n,r]=(0,e.useState)(!1);return n?e.default.createElement("span",{className:"text-muted bg-muted flex size-[12.5cqw] items-center justify-center rounded-full text-[length:6cqw] font-semibold"},t.charAt(0)):e.default.createElement("img",{src:(0,i.appIconUrl)(t),alt:"",draggable:!1,className:"size-[12.5cqw] rounded-full",onError:()=>r(!0)})},$=({device:t,model:n,onOpenApp:r})=>{const a=(0,i.openableApps)(t),{opening:s,error:d,open:f}=(0,i.useOpenApp)(r),o=n.lightScreen?{background:"bg-white",text:"text-black",scrollbar:"[&::-webkit-scrollbar-thumb]:bg-black/30",overlay:"bg-white/70"}:{background:"bg-black",text:"text-white",scrollbar:"[&::-webkit-scrollbar-thumb]:bg-white/30",overlay:"bg-black/70"},c=n.touch?{scroll:"overflow-y-auto",list:"grid grid-cols-3 gap-[1.5cqw]",tile:"min-w-0 py-[3cqw]",opening:"flex-col"}:{scroll:"flex items-center overflow-x-auto",list:"flex gap-[2.5cqw]",tile:"w-[28cqw] shrink-0 py-[1.5cqw]",opening:"flex-row"};return e.default.createElement("div",{className:"flex flex-col gap-8","data-testid":"container_device-os-info"},e.default.createElement(m.ScreenFrame,null,e.default.createElement("div",{className:`${o.background} ${c.scroll} ${N} ${o.scrollbar} p-[3cqw]`,style:{aspectRatio:n.screenWidth/n.screenHeight},"data-testid":"list_device-apps"},a.length===0?e.default.createElement("p",{className:`text-[length:5.5cqw] ${o.text} flex h-full w-full items-center justify-center opacity-60`},"No apps installed"):e.default.createElement("div",{className:c.list},a.map(l=>e.default.createElement("button",{key:l.name,type:"button",title:`${l.name} ${l.version}`,disabled:!r||s!==void 0,onClick:()=>f(l.name),className:`${o.text} ${c.tile} flex flex-col items-center gap-[1.5cqw] border-0 bg-transparent px-[1.5cqw] disabled:cursor-default enabled:cursor-pointer`,"data-testid":`button_open-app-${l.name}`},e.default.createElement(u,{name:l.name}),e.default.createElement("span",{className:"w-full truncate text-center text-[length:4.7cqw] leading-[1.3]"},l.name))))),s!==void 0?e.default.createElement("div",{className:`${o.text} ${o.overlay} ${c.opening} pointer-events-none absolute inset-0 flex items-center justify-center gap-[3cqw] backdrop-blur-[1cqw]`,"data-testid":"spinner_opening-app-screen"},e.default.createElement("div",{className:"relative flex size-[18cqw] shrink-0 items-center justify-center"},e.default.createElement("span",{className:"absolute inset-0 rounded-full border-[0.8cqw] border-current opacity-15"}),e.default.createElement("span",{className:"absolute inset-0 animate-spin rounded-full border-[0.8cqw] border-transparent border-t-current"}),e.default.createElement(u,{name:s})),e.default.createElement("span",{className:"animate-pulse text-[length:4.7cqw] font-semibold"},"Opening ",s)):null),d?e.default.createElement("p",{className:"body-4 text-error text-center"},d):null)};0&&(module.exports={DeviceOsInfo});
2
2
  //# sourceMappingURL=DeviceOsInfo.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/DeviceOsInfo.tsx"],
4
- "sourcesContent": ["import React, { useEffect, useRef, useState } from \"react\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { type DeviceScreenModel } from \"./deviceModels\";\nimport { ScreenFrame } from \"./ScreenFrame\";\n\n/** The OS reports itself as an app; it is not one the user can open. */\nconst OS_APP_NAME = \"BOLOS\";\n\nconst OPEN_TIMEOUT_MS = 10_000;\n\n/** A styled scrollbar stays visible where the OS would hide an overlay one. */\nconst SCROLLBAR =\n \"[&::-webkit-scrollbar]:size-[6px] [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full\";\n\n/** Ledger Live's app icons, keyed by the app's Manager API icon name. */\nconst iconUrl = (appName: string) =>\n `https://cdn.live.ledger.com/icons/${appName.toLowerCase().replace(/ /g, \"_\")}.png`;\n\ntype AppIconProps = {\n name: string;\n};\n\nconst AppIcon: React.FC<AppIconProps> = ({ name }) => {\n const [missing, setMissing] = useState(false);\n\n if (missing) {\n return (\n <span className=\"text-muted bg-muted flex size-[12.5cqw] items-center justify-center rounded-full text-[length:6cqw] font-semibold\">\n {name.charAt(0)}\n </span>\n );\n }\n\n return (\n <img\n src={iconUrl(name)}\n alt=\"\"\n draggable={false}\n className=\"size-[12.5cqw] rounded-full\"\n onError={() => setMissing(true)}\n />\n );\n};\n\ntype DeviceOsInfoProps = {\n device: Device;\n model: DeviceScreenModel;\n /** Omitted, the apps are listed but cannot be opened. */\n onOpenApp?: (appName: string) => Promise<void>;\n};\n\nexport const DeviceOsInfo: React.FC<DeviceOsInfoProps> = ({\n device,\n model,\n onOpenApp,\n}) => {\n const apps = (device.apps ?? []).filter(({ name }) => name !== OS_APP_NAME);\n /** Kept on success, until the live screen replaces the grid or times out. */\n const [opening, setOpening] = useState<string>();\n const [error, setError] = useState<string>();\n const screen = model.lightScreen\n ? {\n background: \"bg-white\",\n text: \"text-black\",\n scrollbar: \"[&::-webkit-scrollbar-thumb]:bg-black/30\",\n overlay: \"bg-white/70\",\n }\n : {\n background: \"bg-black\",\n text: \"text-white\",\n scrollbar: \"[&::-webkit-scrollbar-thumb]:bg-white/30\",\n overlay: \"bg-black/70\",\n };\n const layout = model.touch\n ? {\n scroll: \"overflow-y-auto\",\n list: \"grid grid-cols-3 gap-[1.5cqw]\",\n tile: \"min-w-0 py-[3cqw]\",\n opening: \"flex-col\",\n }\n : {\n scroll: \"flex items-center overflow-x-auto\",\n list: \"flex gap-[2.5cqw]\",\n tile: \"w-[28cqw] shrink-0 py-[1.5cqw]\",\n opening: \"flex-row\",\n };\n\n const openTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n useEffect(() => () => clearTimeout(openTimer.current), []);\n\n const open = (appName: string) => {\n if (!onOpenApp || opening) return;\n setOpening(appName);\n setError(undefined);\n onOpenApp(appName).then(\n () => {\n openTimer.current = setTimeout(() => {\n setOpening(undefined);\n setError(`${appName} opened but its screen never showed up`);\n }, OPEN_TIMEOUT_MS);\n },\n (cause: unknown) => {\n setOpening(undefined);\n setError(cause instanceof Error ? cause.message : String(cause));\n },\n );\n };\n\n return (\n <div className=\"flex flex-col gap-8\" data-testid=\"container_device-os-info\">\n <ScreenFrame>\n <div\n className={`${screen.background} ${layout.scroll} ${SCROLLBAR} ${screen.scrollbar} p-[3cqw]`}\n style={{ aspectRatio: model.screenWidth / model.screenHeight }}\n data-testid=\"list_device-apps\"\n >\n {apps.length === 0 ? (\n <p\n className={`text-[length:5.5cqw] ${screen.text} flex h-full w-full items-center justify-center opacity-60`}\n >\n No apps installed\n </p>\n ) : (\n <div className={layout.list}>\n {apps.map((app) => (\n <button\n key={app.name}\n type=\"button\"\n title={`${app.name} ${app.version}`}\n disabled={!onOpenApp || opening !== undefined}\n onClick={() => open(app.name)}\n className={`${screen.text} ${layout.tile} flex flex-col items-center gap-[1.5cqw] border-0 bg-transparent px-[1.5cqw] disabled:cursor-default enabled:cursor-pointer`}\n data-testid={`button_open-app-${app.name}`}\n >\n <AppIcon name={app.name} />\n <span className=\"w-full truncate text-center text-[length:4.7cqw] leading-[1.3]\">\n {app.name}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n {opening !== undefined ? (\n <div\n className={`${screen.text} ${screen.overlay} ${layout.opening} pointer-events-none absolute inset-0 flex items-center justify-center gap-[3cqw] backdrop-blur-[1cqw]`}\n data-testid=\"spinner_opening-app-screen\"\n >\n <div className=\"relative flex size-[18cqw] shrink-0 items-center justify-center\">\n <span className=\"absolute inset-0 rounded-full border-[0.8cqw] border-current opacity-15\" />\n <span className=\"absolute inset-0 animate-spin rounded-full border-[0.8cqw] border-transparent border-t-current\" />\n <AppIcon name={opening} />\n </div>\n <span className=\"animate-pulse text-[length:4.7cqw] font-semibold\">\n Opening {opening}\n </span>\n </div>\n ) : null}\n </ScreenFrame>\n {error ? <p className=\"body-4 text-error text-center\">{error}</p> : null}\n </div>\n );\n};\n"],
5
- "mappings": "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",
6
- "names": ["DeviceOsInfo_exports", "__export", "DeviceOsInfo", "__toCommonJS", "import_react", "import_ScreenFrame", "OS_APP_NAME", "OPEN_TIMEOUT_MS", "SCROLLBAR", "iconUrl", "appName", "AppIcon", "name", "missing", "setMissing", "React", "device", "model", "onOpenApp", "apps", "opening", "setOpening", "error", "setError", "screen", "layout", "openTimer", "open", "cause", "app"]
4
+ "sourcesContent": ["import React, { useState } from \"react\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\nimport {\n appIconUrl,\n openableApps,\n useOpenApp,\n} from \"@ledgerhq/device-mockserver-react-core\";\n\nimport { type DeviceScreenModel } from \"./deviceModels\";\nimport { ScreenFrame } from \"./ScreenFrame\";\n\n/** A styled scrollbar stays visible where the OS would hide an overlay one. */\nconst SCROLLBAR =\n \"[&::-webkit-scrollbar]:size-[6px] [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full\";\n\ntype AppIconProps = {\n name: string;\n};\n\nconst AppIcon: React.FC<AppIconProps> = ({ name }) => {\n const [missing, setMissing] = useState(false);\n\n if (missing) {\n return (\n <span className=\"text-muted bg-muted flex size-[12.5cqw] items-center justify-center rounded-full text-[length:6cqw] font-semibold\">\n {name.charAt(0)}\n </span>\n );\n }\n\n return (\n <img\n src={appIconUrl(name)}\n alt=\"\"\n draggable={false}\n className=\"size-[12.5cqw] rounded-full\"\n onError={() => setMissing(true)}\n />\n );\n};\n\ntype DeviceOsInfoProps = {\n device: Device;\n model: DeviceScreenModel;\n /** Omitted, the apps are listed but cannot be opened. */\n onOpenApp?: (appName: string) => Promise<void>;\n};\n\nexport const DeviceOsInfo: React.FC<DeviceOsInfoProps> = ({\n device,\n model,\n onOpenApp,\n}) => {\n const apps = openableApps(device);\n const { opening, error, open } = useOpenApp(onOpenApp);\n const screen = model.lightScreen\n ? {\n background: \"bg-white\",\n text: \"text-black\",\n scrollbar: \"[&::-webkit-scrollbar-thumb]:bg-black/30\",\n overlay: \"bg-white/70\",\n }\n : {\n background: \"bg-black\",\n text: \"text-white\",\n scrollbar: \"[&::-webkit-scrollbar-thumb]:bg-white/30\",\n overlay: \"bg-black/70\",\n };\n const layout = model.touch\n ? {\n scroll: \"overflow-y-auto\",\n list: \"grid grid-cols-3 gap-[1.5cqw]\",\n tile: \"min-w-0 py-[3cqw]\",\n opening: \"flex-col\",\n }\n : {\n scroll: \"flex items-center overflow-x-auto\",\n list: \"flex gap-[2.5cqw]\",\n tile: \"w-[28cqw] shrink-0 py-[1.5cqw]\",\n opening: \"flex-row\",\n };\n\n return (\n <div className=\"flex flex-col gap-8\" data-testid=\"container_device-os-info\">\n <ScreenFrame>\n <div\n className={`${screen.background} ${layout.scroll} ${SCROLLBAR} ${screen.scrollbar} p-[3cqw]`}\n style={{ aspectRatio: model.screenWidth / model.screenHeight }}\n data-testid=\"list_device-apps\"\n >\n {apps.length === 0 ? (\n <p\n className={`text-[length:5.5cqw] ${screen.text} flex h-full w-full items-center justify-center opacity-60`}\n >\n No apps installed\n </p>\n ) : (\n <div className={layout.list}>\n {apps.map((app) => (\n <button\n key={app.name}\n type=\"button\"\n title={`${app.name} ${app.version}`}\n disabled={!onOpenApp || opening !== undefined}\n onClick={() => open(app.name)}\n className={`${screen.text} ${layout.tile} flex flex-col items-center gap-[1.5cqw] border-0 bg-transparent px-[1.5cqw] disabled:cursor-default enabled:cursor-pointer`}\n data-testid={`button_open-app-${app.name}`}\n >\n <AppIcon name={app.name} />\n <span className=\"w-full truncate text-center text-[length:4.7cqw] leading-[1.3]\">\n {app.name}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n {opening !== undefined ? (\n <div\n className={`${screen.text} ${screen.overlay} ${layout.opening} pointer-events-none absolute inset-0 flex items-center justify-center gap-[3cqw] backdrop-blur-[1cqw]`}\n data-testid=\"spinner_opening-app-screen\"\n >\n <div className=\"relative flex size-[18cqw] shrink-0 items-center justify-center\">\n <span className=\"absolute inset-0 rounded-full border-[0.8cqw] border-current opacity-15\" />\n <span className=\"absolute inset-0 animate-spin rounded-full border-[0.8cqw] border-transparent border-t-current\" />\n <AppIcon name={opening} />\n </div>\n <span className=\"animate-pulse text-[length:4.7cqw] font-semibold\">\n Opening {opening}\n </span>\n </div>\n ) : null}\n </ScreenFrame>\n {error ? <p className=\"body-4 text-error text-center\">{error}</p> : null}\n </div>\n );\n};\n"],
5
+ "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAgC,oBAEhCC,EAIO,kDAGPC,EAA4B,yBAG5B,MAAMC,EACJ,0HAMIC,EAAkC,CAAC,CAAE,KAAAC,CAAK,IAAM,CACpD,KAAM,CAACC,EAASC,CAAU,KAAI,YAAS,EAAK,EAE5C,OAAID,EAEA,EAAAE,QAAA,cAAC,QAAK,UAAU,qHACbH,EAAK,OAAO,CAAC,CAChB,EAKF,EAAAG,QAAA,cAAC,OACC,OAAK,cAAWH,CAAI,EACpB,IAAI,GACJ,UAAW,GACX,UAAU,8BACV,QAAS,IAAME,EAAW,EAAI,EAChC,CAEJ,EASaT,EAA4C,CAAC,CACxD,OAAAW,EACA,MAAAC,EACA,UAAAC,CACF,IAAM,CACJ,MAAMC,KAAO,gBAAaH,CAAM,EAC1B,CAAE,QAAAI,EAAS,MAAAC,EAAO,KAAAC,CAAK,KAAI,cAAWJ,CAAS,EAC/CK,EAASN,EAAM,YACjB,CACE,WAAY,WACZ,KAAM,aACN,UAAW,2CACX,QAAS,aACX,EACA,CACE,WAAY,WACZ,KAAM,aACN,UAAW,2CACX,QAAS,aACX,EACEO,EAASP,EAAM,MACjB,CACE,OAAQ,kBACR,KAAM,gCACN,KAAM,oBACN,QAAS,UACX,EACA,CACE,OAAQ,oCACR,KAAM,oBACN,KAAM,iCACN,QAAS,UACX,EAEJ,OACE,EAAAF,QAAA,cAAC,OAAI,UAAU,sBAAsB,cAAY,4BAC/C,EAAAA,QAAA,cAAC,mBACC,EAAAA,QAAA,cAAC,OACC,UAAW,GAAGQ,EAAO,UAAU,IAAIC,EAAO,MAAM,IAAId,CAAS,IAAIa,EAAO,SAAS,YACjF,MAAO,CAAE,YAAaN,EAAM,YAAcA,EAAM,YAAa,EAC7D,cAAY,oBAEXE,EAAK,SAAW,EACf,EAAAJ,QAAA,cAAC,KACC,UAAW,wBAAwBQ,EAAO,IAAI,8DAC/C,mBAED,EAEA,EAAAR,QAAA,cAAC,OAAI,UAAWS,EAAO,MACpBL,EAAK,IAAKM,GACT,EAAAV,QAAA,cAAC,UACC,IAAKU,EAAI,KACT,KAAK,SACL,MAAO,GAAGA,EAAI,IAAI,IAAIA,EAAI,OAAO,GACjC,SAAU,CAACP,GAAaE,IAAY,OACpC,QAAS,IAAME,EAAKG,EAAI,IAAI,EAC5B,UAAW,GAAGF,EAAO,IAAI,IAAIC,EAAO,IAAI,8HACxC,cAAa,mBAAmBC,EAAI,IAAI,IAExC,EAAAV,QAAA,cAACJ,EAAA,CAAQ,KAAMc,EAAI,KAAM,EACzB,EAAAV,QAAA,cAAC,QAAK,UAAU,kEACbU,EAAI,IACP,CACF,CACD,CACH,CAEJ,EACCL,IAAY,OACX,EAAAL,QAAA,cAAC,OACC,UAAW,GAAGQ,EAAO,IAAI,IAAIA,EAAO,OAAO,IAAIC,EAAO,OAAO,yGAC7D,cAAY,8BAEZ,EAAAT,QAAA,cAAC,OAAI,UAAU,mEACb,EAAAA,QAAA,cAAC,QAAK,UAAU,0EAA0E,EAC1F,EAAAA,QAAA,cAAC,QAAK,UAAU,iGAAiG,EACjH,EAAAA,QAAA,cAACJ,EAAA,CAAQ,KAAMS,EAAS,CAC1B,EACA,EAAAL,QAAA,cAAC,QAAK,UAAU,oDAAmD,WACxDK,CACX,CACF,EACE,IACN,EACCC,EAAQ,EAAAN,QAAA,cAAC,KAAE,UAAU,iCAAiCM,CAAM,EAAO,IACtE,CAEJ",
6
+ "names": ["DeviceOsInfo_exports", "__export", "DeviceOsInfo", "__toCommonJS", "import_react", "import_device_mockserver_react_core", "import_ScreenFrame", "SCROLLBAR", "AppIcon", "name", "missing", "setMissing", "React", "device", "model", "onOpenApp", "apps", "opening", "error", "open", "screen", "layout", "app"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ "use strict";var u=Object.create;var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var b=(e,o,t,i)=>{if(o&&typeof o=="object"||typeof o=="function")for(let p of B(o))!x.call(e,p)&&p!==t&&d(e,p,{get:()=>o[p],enumerable:!(i=v(o,p))||i.enumerable});return e};var w=(e,o,t)=>(t=e!=null?u(h(e)):{},b(o||!e||!e.__esModule?d(t,"default",{value:e,enumerable:!0}):t,e));var n=w(require("react")),m=require("react-dom/client"),a=require("./deviceModels"),c=require("./DeviceOsInfo");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const r=e=>({id:"device-1",device_type:"stax",apps:e}),l=[{name:"BOLOS",version:"1.10.1"},{name:"Bitcoin",version:"2.5.0"},{name:"Ethereum",version:"1.22.3"}];describe("DeviceOsInfo",()=>{let e,o;const t=s=>e.querySelector(`[data-testid="${s}"]`),i=s=>(0,n.act)(()=>o.render(s)),p=async s=>(0,n.act)(async()=>{t(s).click(),await Promise.resolve()});beforeEach(()=>{vi.useFakeTimers(),e=document.createElement("div"),document.body.append(e),o=(0,m.createRoot)(e)}),afterEach(()=>{(0,n.act)(()=>o.unmount()),e.remove(),vi.useRealTimers()}),it("lists the installed apps, without the OS",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax")})),expect(t("button_open-app-Bitcoin")).not.toBeNull(),expect(t("button_open-app-Ethereum")).not.toBeNull(),expect(t("button_open-app-BOLOS")).toBeNull()}),it("says when no app is installed",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r([l[0]]),model:(0,a.findDeviceScreenModel)("nanoX")})),expect(t("list_device-apps")?.textContent).toBe("No apps installed")}),it("cannot open apps without an emulator",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("nanoS")})),expect(t("button_open-app-Bitcoin").disabled).toBe(!0)}),it("shows the app being opened",async()=>{const s=vi.fn(()=>new Promise(()=>{}));i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:s})),await p("button_open-app-Bitcoin"),expect(s).toHaveBeenCalledWith("Bitcoin"),expect(t("spinner_opening-app-screen")?.textContent).toContain("Opening Bitcoin")}),it("shows why an app could not be opened",async()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:()=>Promise.reject(new Error("Could not open (6d00)"))})),await p("button_open-app-Bitcoin"),expect(t("spinner_opening-app-screen")).toBeNull(),expect(e.textContent).toContain("Could not open (6d00)")}),it("gives up when an opened app never shows its screen",async()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:()=>Promise.resolve()})),await p("button_open-app-Ethereum"),(0,n.act)(()=>{vi.advanceTimersByTime(1e4)}),expect(e.textContent).toContain("Ethereum opened but its screen never showed up")}),it("falls back to the app's initial when its icon is missing",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax")})),(0,n.act)(()=>{t("button_open-app-Bitcoin").querySelector("img").dispatchEvent(new Event("error"))}),expect(t("button_open-app-Bitcoin")?.querySelector("img")).toBeNull(),expect(t("button_open-app-Bitcoin")?.textContent).toBe("BBitcoin")})});
2
+ //# sourceMappingURL=DeviceOsInfo.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceOsInfo.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { findDeviceScreenModel } from \"./deviceModels\";\nimport { DeviceOsInfo } from \"./DeviceOsInfo\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst aDevice = (apps: { name: string; version: string }[]) =>\n ({ id: \"device-1\", device_type: \"stax\", apps }) as unknown as Device;\n\nconst APPS = [\n { name: \"BOLOS\", version: \"1.10.1\" },\n { name: \"Bitcoin\", version: \"2.5.0\" },\n { name: \"Ethereum\", version: \"1.22.3\" },\n];\n\ndescribe(\"DeviceOsInfo\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (\n element: React.ReactElement<React.ComponentProps<typeof DeviceOsInfo>>,\n ) => act(() => root.render(element));\n const click = async (testId: string) =>\n act(async () => {\n query(testId)!.click();\n await Promise.resolve();\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"lists the installed apps, without the OS\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n expect(query(\"button_open-app-Ethereum\")).not.toBeNull();\n expect(query(\"button_open-app-BOLOS\")).toBeNull();\n });\n\n it(\"says when no app is installed\", () => {\n render(\n <DeviceOsInfo\n device={aDevice([APPS[0]!])}\n model={findDeviceScreenModel(\"nanoX\")}\n />,\n );\n\n expect(query(\"list_device-apps\")?.textContent).toBe(\"No apps installed\");\n });\n\n it(\"cannot open apps without an emulator\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"nanoS\")}\n />,\n );\n\n expect(\n (query(\"button_open-app-Bitcoin\") as HTMLButtonElement).disabled,\n ).toBe(true);\n });\n\n it(\"shows the app being opened\", async () => {\n const onOpenApp = vi.fn(() => new Promise<void>(() => {}));\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={onOpenApp}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(onOpenApp).toHaveBeenCalledWith(\"Bitcoin\");\n expect(query(\"spinner_opening-app-screen\")?.textContent).toContain(\n \"Opening Bitcoin\",\n );\n });\n\n it(\"shows why an app could not be opened\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.reject(new Error(\"Could not open (6d00)\"))}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(query(\"spinner_opening-app-screen\")).toBeNull();\n expect(container.textContent).toContain(\"Could not open (6d00)\");\n });\n\n it(\"gives up when an opened app never shows its screen\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.resolve()}\n />,\n );\n\n await click(\"button_open-app-Ethereum\");\n act(() => {\n vi.advanceTimersByTime(10_000);\n });\n\n expect(container.textContent).toContain(\n \"Ethereum opened but its screen never showed up\",\n );\n });\n\n it(\"falls back to the app's initial when its icon is missing\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n act(() => {\n query(\"button_open-app-Bitcoin\")!\n .querySelector(\"img\")!\n .dispatchEvent(new Event(\"error\"));\n });\n\n expect(query(\"button_open-app-Bitcoin\")?.querySelector(\"img\")).toBeNull();\n expect(query(\"button_open-app-Bitcoin\")?.textContent).toBe(\"BBitcoin\");\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["import_react", "import_client", "import_deviceModels", "import_DeviceOsInfo", "aDevice", "apps", "APPS", "container", "root", "query", "testId", "render", "element", "click", "React", "onOpenApp"]
7
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var F=Object.create;var f=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var k=(n,t)=>{for(var r in t)f(n,r,{get:t[r],enumerable:!0})},S=(n,t,r,l)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of L(t))!U.call(n,s)&&s!==r&&f(n,s,{get:()=>t[s],enumerable:!(l=B(t,s))||l.enumerable});return n};var $=(n,t,r)=>(r=n!=null?F(X(n)):{},S(t||!n||!n.__esModule?f(r,"default",{value:n,enumerable:!0}):r,n)),j=n=>S(f({},"__esModule",{value:!0}),n);var K={};k(K,{DeviceScreen:()=>q});module.exports=j(K);var e=$(require("react")),v=require("@ledgerhq/lumen-ui-react"),h=require("@ledgerhq/lumen-ui-react/symbols"),y=require("./deviceModels"),C=require("./DeviceOsInfo"),T=require("./DeviceScreenButtons"),E=require("./DeviceScreenImage"),N=require("./ScreenFrame"),_=require("./useScreenPolling");const w=16,G=224,O=200,Y=(n,t)=>t?`${n} - ${t}`:n,q=({api:n,deviceType:t,firmwareVersion:r,floating:l=!1,defaultCollapsed:s=!1})=>{const c=(0,y.findDeviceScreenModel)(t),[u,p]=(0,e.useState)(s),m=(0,_.useScreenPolling)(n,!l||!u);return l?e.default.createElement(J,{title:Y(c.label,r),model:c,collapsed:u,onToggle:()=>p(x=>!x)},M(m,c,n)):e.default.createElement("div",{className:"mx-auto flex w-full max-w-[420px] flex-col gap-8","data-testid":"container_device-screen"},M(m,c,n))},J=({title:n,model:t,collapsed:r,onToggle:l,children:s})=>{const c=(0,e.useRef)(null),[u,p]=(0,e.useState)(),[m,x]=(0,e.useState)(G),a=(0,e.useRef)(null),d=(0,e.useRef)(null),z=o=>{const i=c.current?.getBoundingClientRect();!i||o.button!==0||o.target.closest("button")||(o.currentTarget.setPointerCapture(o.pointerId),a.current={dx:o.clientX-i.left,dy:o.clientY-i.top})},H=o=>{const i=c.current?.getBoundingClientRect();if(!a.current||!i)return;const g=(b,A)=>Math.min(Math.max(b,0),Math.max(A,0));p({left:g(o.clientX-a.current.dx,window.innerWidth-i.width),top:g(o.clientY-a.current.dy,window.innerHeight-i.height)})},P=()=>{a.current=null},I=o=>{const i=c.current?.getBoundingClientRect();!i||o.button!==0||(o.currentTarget.setPointerCapture(o.pointerId),p({left:i.left,top:i.top}),d.current={x:o.clientX,width:i.width})},W=o=>{const i=c.current?.getBoundingClientRect();if(!d.current||!i)return;const g=d.current.width+o.clientX-d.current.x,b=window.innerWidth-i.left-w;x(Math.max(O,Math.min(g,b)))},D=()=>{d.current=null};return e.default.createElement("div",{ref:c,className:"bg-base border-muted fixed flex max-w-[calc(100vw-32px)] flex-col rounded-lg border shadow-lg",style:{zIndex:1e3,width:m,...u??{right:w,bottom:w}},"data-testid":"container_device-screen"},e.default.createElement("div",{className:"flex cursor-move items-center gap-8 px-12 py-8 select-none",style:{touchAction:"none"},onPointerDown:z,onPointerMove:H,onPointerUp:P,onPointerCancel:P},e.default.createElement(t.icon,{size:16}),e.default.createElement("span",{className:"body-3-semi-bold text-base flex-1"},n),e.default.createElement(v.IconButton,{appearance:"no-background",size:"xs",icon:r?h.ChevronUp:h.ChevronDown,"aria-label":r?"Expand device screen":"Collapse device screen",onClick:l,"data-testid":"button_toggle-device-screen"})),r?null:e.default.createElement(e.default.Fragment,null,e.default.createElement("div",{className:"flex flex-col gap-8 px-12 pb-12"},s),e.default.createElement("div",{className:"group absolute top-0 right-[-6px] bottom-0 flex w-[12px] cursor-ew-resize items-center justify-center",style:{touchAction:"none"},onPointerDown:I,onPointerMove:W,onPointerUp:D,onPointerCancel:D,"aria-hidden":!0,"data-testid":"handle_resize-device-screen"},e.default.createElement("span",{className:"border-muted bg-base group-hover:bg-muted group-active:bg-muted h-40 w-[6px] rounded-full border transition-colors"}))))};function M(n,t,r){switch(n.kind){case"loading":return e.default.createElement(N.ScreenFrame,null,e.default.createElement("div",{className:`${t.lightScreen?"bg-white text-black":"bg-black text-white"} flex items-center justify-center`,style:{aspectRatio:t.screenWidth/t.screenHeight},"data-testid":"placeholder_device-screen-loading"},e.default.createElement("span",{className:"animate-pulse opacity-60"},e.default.createElement(t.icon,{size:32}))));case"error":return e.default.createElement(v.Banner,{appearance:"error",title:"Screen unavailable",description:n.message});case"os-info":return e.default.createElement(C.DeviceOsInfo,{device:n.device,model:t,onOpenApp:t.speculos?r.openApp:void 0});case"image":return e.default.createElement(e.default.Fragment,null,e.default.createElement(E.DeviceScreenImage,{src:n.src,onTouch:t.touch?n.input.touch:void 0}),t.touch?null:e.default.createElement(T.DeviceScreenButtons,{onPress:n.input.pressButton}))}}0&&(module.exports={DeviceScreen});
1
+ "use strict";var F=Object.create;var h=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var $=(n,t)=>{for(var o in t)h(n,o,{get:t[o],enumerable:!0})},D=(n,t,o,l)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of X(t))!U.call(n,s)&&s!==o&&h(n,s,{get:()=>t[s],enumerable:!(l=L(t,s))||l.enumerable});return n};var O=(n,t,o)=>(o=n!=null?F(k(n)):{},D(t||!n||!n.__esModule?h(o,"default",{value:n,enumerable:!0}):o,n)),j=n=>D(h({},"__esModule",{value:!0}),n);var K={};$(K,{DeviceScreen:()=>q});module.exports=j(K);var e=O(require("react")),y=require("@ledgerhq/device-mockserver-react-core"),m=require("@ledgerhq/lumen-ui-react"),v=require("@ledgerhq/lumen-ui-react/symbols"),C=require("./deviceModels"),E=require("./DeviceOsInfo"),T=require("./DeviceScreenButtons"),z=require("./DeviceScreenImage"),N=require("./ScreenFrame"),_=require("./webScreenPlatform");const w=16,G=224,V=200,S=(n,t,o)=>{const l=(s,c)=>Math.min(Math.max(s,0),Math.max(c,0));return{left:l(n,window.innerWidth-o.width),top:l(t,window.innerHeight-o.height)}},Y=(n,t)=>t?`${n} - ${t}`:n,q=({api:n,deviceType:t,firmwareVersion:o,floating:l=!1,defaultCollapsed:s=!1})=>{const c=(0,C.findDeviceScreenModel)(t),[g,a]=(0,e.useState)(s),f=(0,y.useScreenPolling)(n,!l||!g,_.webScreenPlatform);return l?e.default.createElement(J,{title:Y(c.label,o),model:c,collapsed:g,onToggle:()=>a(b=>!b)},M(f,c,n)):e.default.createElement("div",{className:"mx-auto flex w-full max-w-[420px] flex-col gap-8","data-testid":"container_device-screen"},M(f,c,n))},J=({title:n,model:t,collapsed:o,onToggle:l,children:s})=>{const c=(0,e.useRef)(null),[g,a]=(0,e.useState)(),[f,b]=(0,e.useState)(G),d=(0,e.useRef)(null),u=(0,e.useRef)(null);(0,e.useEffect)(()=>{const r=()=>{const i=c.current?.getBoundingClientRect();i&&a(p=>p&&S(p.left,p.top,i))};return window.addEventListener("resize",r),()=>window.removeEventListener("resize",r)},[]);const H=r=>{const i=c.current?.getBoundingClientRect();!i||r.button!==0||r.target.closest("button")||(r.currentTarget.setPointerCapture(r.pointerId),d.current={dx:r.clientX-i.left,dy:r.clientY-i.top})},I=r=>{const i=c.current?.getBoundingClientRect();!d.current||!i||a(S(r.clientX-d.current.dx,r.clientY-d.current.dy,i))},x=()=>{d.current=null},W=r=>{const i=c.current?.getBoundingClientRect();!i||r.button!==0||(r.currentTarget.setPointerCapture(r.pointerId),a({left:i.left,top:i.top}),u.current={x:r.clientX,width:i.width})},A=r=>{const i=c.current?.getBoundingClientRect();if(!u.current||!i)return;const p=u.current.width+r.clientX-u.current.x,B=window.innerWidth-i.left-w;b(Math.max(V,Math.min(p,B)))},P=()=>{u.current=null};return e.default.createElement("div",{ref:c,className:"bg-base border-muted fixed flex max-w-[calc(100vw-32px)] flex-col rounded-lg border shadow-lg",style:{zIndex:1e3,width:f,...g??{right:w,bottom:w}},"data-testid":"container_device-screen"},e.default.createElement("div",{className:"flex cursor-move items-center gap-8 px-12 py-8 select-none",style:{touchAction:"none"},onPointerDown:H,onPointerMove:I,onPointerUp:x,onPointerCancel:x},e.default.createElement(t.icon,{size:16}),e.default.createElement("span",{className:"body-3-semi-bold text-base flex-1"},n),e.default.createElement(m.IconButton,{appearance:"no-background",size:"xs",icon:o?v.ChevronUp:v.ChevronDown,"aria-label":o?"Expand device screen":"Collapse device screen",onClick:l,"data-testid":"button_toggle-device-screen"})),o?null:e.default.createElement(e.default.Fragment,null,e.default.createElement("div",{className:"flex flex-col gap-8 px-12 pb-12"},s),e.default.createElement("div",{className:"group absolute top-0 right-[-6px] bottom-0 flex w-[12px] cursor-ew-resize items-center justify-center",style:{touchAction:"none"},onPointerDown:W,onPointerMove:A,onPointerUp:P,onPointerCancel:P,"aria-hidden":!0,"data-testid":"handle_resize-device-screen"},e.default.createElement("span",{className:"border-muted bg-base group-hover:bg-muted group-active:bg-muted h-40 w-[6px] rounded-full border transition-colors"}))))};function M(n,t,o){switch(n.kind){case"loading":return e.default.createElement(N.ScreenFrame,null,e.default.createElement("div",{className:`${t.lightScreen?"bg-white text-black":"bg-black text-white"} flex items-center justify-center`,style:{aspectRatio:t.screenWidth/t.screenHeight},"data-testid":"placeholder_device-screen-loading"},e.default.createElement("span",{className:"animate-pulse opacity-60"},e.default.createElement(t.icon,{size:32}))));case"error":return e.default.createElement(m.Banner,{appearance:"error",title:"Screen unavailable",description:n.message});case"os-info":return e.default.createElement(e.default.Fragment,null,e.default.createElement(E.DeviceOsInfo,{device:n.device,model:t,onOpenApp:t.speculos?o.openApp:void 0}),t.speculos?null:e.default.createElement(m.Banner,{appearance:"info",title:"No screen to capture",description:`Speculos has no emulator for the ${t.label}, so this device is mocked all the way through and has no screen.`,"data-testid":"banner_device-screen-no-emulator"}));case"image":return e.default.createElement(e.default.Fragment,null,e.default.createElement(z.DeviceScreenImage,{src:n.src,onTouch:t.touch?n.input.touch:void 0}),t.touch?null:e.default.createElement(T.DeviceScreenButtons,{onPress:n.input.pressButton}))}}0&&(module.exports={DeviceScreen});
2
2
  //# sourceMappingURL=DeviceScreen.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/DeviceScreen.tsx"],
4
- "sourcesContent": ["import React, { useRef, useState } from \"react\";\nimport { Banner, IconButton } from \"@ledgerhq/lumen-ui-react\";\nimport { ChevronDown, ChevronUp } from \"@ledgerhq/lumen-ui-react/symbols\";\n\nimport { type DeviceScreenModel, findDeviceScreenModel } from \"./deviceModels\";\nimport { DeviceOsInfo } from \"./DeviceOsInfo\";\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\nimport { ScreenFrame } from \"./ScreenFrame\";\nimport { type DeviceScreenState, type ScreenApi } from \"./types\";\nimport { useScreenPolling } from \"./useScreenPolling\";\n\nexport type DeviceScreenProps = {\n api: ScreenApi;\n /** The mock server's `device_type`, a DMK `DeviceModelId`. */\n deviceType: string;\n /** Shown next to the model name in the floating window's header. */\n firmwareVersion?: string;\n /**\n * Render as a window over the page that can be dragged by its header and\n * collapsed to it, rather than inline where it is placed.\n */\n floating?: boolean;\n defaultCollapsed?: boolean;\n};\n\ninterface Position {\n readonly left: number;\n readonly top: number;\n}\n\nconst EDGE_MARGIN_PX = 16;\nconst DEFAULT_WIDTH_PX = 224;\nconst MIN_WIDTH_PX = 200;\n\nconst deviceScreenTitle = (label: string, firmwareVersion?: string) =>\n firmwareVersion ? `${label} - ${firmwareVersion}` : label;\n\nexport const DeviceScreen: React.FC<DeviceScreenProps> = ({\n api,\n deviceType,\n firmwareVersion,\n floating = false,\n defaultCollapsed = false,\n}) => {\n const model = findDeviceScreenModel(deviceType);\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const state = useScreenPolling(api, !floating || !collapsed);\n\n if (!floating) {\n return (\n <div\n className=\"mx-auto flex w-full max-w-[420px] flex-col gap-8\"\n data-testid=\"container_device-screen\"\n >\n {renderScreen(state, model, api)}\n </div>\n );\n }\n\n return (\n <FloatingWindow\n title={deviceScreenTitle(model.label, firmwareVersion)}\n model={model}\n collapsed={collapsed}\n onToggle={() => setCollapsed((value) => !value)}\n >\n {renderScreen(state, model, api)}\n </FloatingWindow>\n );\n};\n\ntype FloatingWindowProps = {\n title: string;\n model: DeviceScreenModel;\n collapsed: boolean;\n onToggle: () => void;\n children: React.ReactNode;\n};\n\nconst FloatingWindow: React.FC<FloatingWindowProps> = ({\n title,\n model,\n collapsed,\n onToggle,\n children,\n}) => {\n const windowRef = useRef<HTMLDivElement>(null);\n /** Unset until first dragged: the window starts in the bottom right corner. */\n const [position, setPosition] = useState<Position>();\n const [width, setWidth] = useState(DEFAULT_WIDTH_PX);\n const drag = useRef<{ dx: number; dy: number } | null>(null);\n const resize = useRef<{ x: number; width: number } | null>(null);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n // A drag captures the pointer, which would steal the button's click.\n if (\n !rect ||\n event.button !== 0 ||\n (event.target as HTMLElement).closest(\"button\")\n ) {\n return;\n }\n event.currentTarget.setPointerCapture(event.pointerId);\n drag.current = {\n dx: event.clientX - rect.left,\n dy: event.clientY - rect.top,\n };\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!drag.current || !rect) return;\n // Keep the header on screen, or the window could not be dragged back.\n const clamp = (value: number, max: number) =>\n Math.min(Math.max(value, 0), Math.max(max, 0));\n setPosition({\n left: clamp(\n event.clientX - drag.current.dx,\n window.innerWidth - rect.width,\n ),\n top: clamp(\n event.clientY - drag.current.dy,\n window.innerHeight - rect.height,\n ),\n });\n };\n\n const handlePointerUp = () => {\n drag.current = null;\n };\n\n const handleResizeDown = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!rect || event.button !== 0) return;\n event.currentTarget.setPointerCapture(event.pointerId);\n // Pin the top left corner, or growing would push the window leftwards\n // while it is still anchored to the bottom right.\n setPosition({ left: rect.left, top: rect.top });\n resize.current = { x: event.clientX, width: rect.width };\n };\n\n const handleResizeMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!resize.current || !rect) return;\n const next = resize.current.width + event.clientX - resize.current.x;\n const max = window.innerWidth - rect.left - EDGE_MARGIN_PX;\n setWidth(Math.max(MIN_WIDTH_PX, Math.min(next, max)));\n };\n\n const handleResizeUp = () => {\n resize.current = null;\n };\n\n return (\n <div\n ref={windowRef}\n className=\"bg-base border-muted fixed flex max-w-[calc(100vw-32px)] flex-col rounded-lg border shadow-lg\"\n style={{\n zIndex: 1000,\n width,\n ...(position ?? { right: EDGE_MARGIN_PX, bottom: EDGE_MARGIN_PX }),\n }}\n data-testid=\"container_device-screen\"\n >\n <div\n className=\"flex cursor-move items-center gap-8 px-12 py-8 select-none\"\n style={{ touchAction: \"none\" }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n >\n <model.icon size={16} />\n <span className=\"body-3-semi-bold text-base flex-1\">{title}</span>\n <IconButton\n appearance=\"no-background\"\n size=\"xs\"\n icon={collapsed ? ChevronUp : ChevronDown}\n aria-label={\n collapsed ? \"Expand device screen\" : \"Collapse device screen\"\n }\n onClick={onToggle}\n data-testid=\"button_toggle-device-screen\"\n />\n </div>\n {collapsed ? null : (\n <>\n <div className=\"flex flex-col gap-8 px-12 pb-12\">{children}</div>\n <div\n className=\"group absolute top-0 right-[-6px] bottom-0 flex w-[12px] cursor-ew-resize items-center justify-center\"\n style={{ touchAction: \"none\" }}\n onPointerDown={handleResizeDown}\n onPointerMove={handleResizeMove}\n onPointerUp={handleResizeUp}\n onPointerCancel={handleResizeUp}\n aria-hidden\n data-testid=\"handle_resize-device-screen\"\n >\n <span className=\"border-muted bg-base group-hover:bg-muted group-active:bg-muted h-40 w-[6px] rounded-full border transition-colors\" />\n </div>\n </>\n )}\n </div>\n );\n};\n\nfunction renderScreen(\n state: DeviceScreenState,\n model: DeviceScreenModel,\n api: ScreenApi,\n) {\n switch (state.kind) {\n case \"loading\":\n return (\n <ScreenFrame>\n <div\n className={`${model.lightScreen ? \"bg-white text-black\" : \"bg-black text-white\"} flex items-center justify-center`}\n style={{ aspectRatio: model.screenWidth / model.screenHeight }}\n data-testid=\"placeholder_device-screen-loading\"\n >\n <span className=\"animate-pulse opacity-60\">\n <model.icon size={32} />\n </span>\n </div>\n </ScreenFrame>\n );\n case \"error\":\n return (\n <Banner\n appearance=\"error\"\n title=\"Screen unavailable\"\n description={state.message}\n />\n );\n case \"os-info\":\n return (\n <DeviceOsInfo\n device={state.device}\n model={model}\n onOpenApp={model.speculos ? api.openApp : undefined}\n />\n );\n case \"image\":\n return (\n <>\n <DeviceScreenImage\n src={state.src}\n onTouch={model.touch ? state.input.touch : undefined}\n />\n {model.touch ? null : (\n <DeviceScreenButtons onPress={state.input.pressButton} />\n )}\n </>\n );\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["DeviceScreen_exports", "__export", "DeviceScreen", "__toCommonJS", "import_react", "import_lumen_ui_react", "import_symbols", "import_deviceModels", "import_DeviceOsInfo", "import_DeviceScreenButtons", "import_DeviceScreenImage", "import_ScreenFrame", "import_useScreenPolling", "EDGE_MARGIN_PX", "DEFAULT_WIDTH_PX", "MIN_WIDTH_PX", "deviceScreenTitle", "label", "firmwareVersion", "api", "deviceType", "floating", "defaultCollapsed", "model", "collapsed", "setCollapsed", "state", "React", "FloatingWindow", "value", "renderScreen", "title", "onToggle", "children", "windowRef", "position", "setPosition", "width", "setWidth", "drag", "resize", "handlePointerDown", "event", "rect", "handlePointerMove", "clamp", "max", "handlePointerUp", "handleResizeDown", "handleResizeMove", "next", "handleResizeUp"]
4
+ "sourcesContent": ["import React, { useEffect, useRef, useState } from \"react\";\nimport {\n type DeviceScreenState,\n type ScreenApi,\n useScreenPolling,\n} from \"@ledgerhq/device-mockserver-react-core\";\nimport { Banner, IconButton } from \"@ledgerhq/lumen-ui-react\";\nimport { ChevronDown, ChevronUp } from \"@ledgerhq/lumen-ui-react/symbols\";\n\nimport { type DeviceScreenModel, findDeviceScreenModel } from \"./deviceModels\";\nimport { DeviceOsInfo } from \"./DeviceOsInfo\";\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\nimport { ScreenFrame } from \"./ScreenFrame\";\nimport { webScreenPlatform } from \"./webScreenPlatform\";\n\nexport type DeviceScreenProps = {\n api: ScreenApi;\n /** The mock server's `device_type`, a DMK `DeviceModelId`. */\n deviceType: string;\n /** Shown next to the model name in the floating window's header. */\n firmwareVersion?: string;\n /**\n * Render as a window over the page that can be dragged by its header and\n * collapsed to it, rather than inline where it is placed.\n */\n floating?: boolean;\n defaultCollapsed?: boolean;\n};\n\ninterface Position {\n readonly left: number;\n readonly top: number;\n}\n\nconst EDGE_MARGIN_PX = 16;\nconst DEFAULT_WIDTH_PX = 224;\nconst MIN_WIDTH_PX = 200;\n\n/** Keeps the whole window, header included, where it can be dragged back. */\nconst clampToViewport = (\n left: number,\n top: number,\n rect: DOMRect,\n): Position => {\n const clamp = (value: number, max: number) =>\n Math.min(Math.max(value, 0), Math.max(max, 0));\n return {\n left: clamp(left, window.innerWidth - rect.width),\n top: clamp(top, window.innerHeight - rect.height),\n };\n};\n\nconst deviceScreenTitle = (label: string, firmwareVersion?: string) =>\n firmwareVersion ? `${label} - ${firmwareVersion}` : label;\n\nexport const DeviceScreen: React.FC<DeviceScreenProps> = ({\n api,\n deviceType,\n firmwareVersion,\n floating = false,\n defaultCollapsed = false,\n}) => {\n const model = findDeviceScreenModel(deviceType);\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const state = useScreenPolling(\n api,\n !floating || !collapsed,\n webScreenPlatform,\n );\n\n if (!floating) {\n return (\n <div\n className=\"mx-auto flex w-full max-w-[420px] flex-col gap-8\"\n data-testid=\"container_device-screen\"\n >\n {renderScreen(state, model, api)}\n </div>\n );\n }\n\n return (\n <FloatingWindow\n title={deviceScreenTitle(model.label, firmwareVersion)}\n model={model}\n collapsed={collapsed}\n onToggle={() => setCollapsed((value) => !value)}\n >\n {renderScreen(state, model, api)}\n </FloatingWindow>\n );\n};\n\ntype FloatingWindowProps = {\n title: string;\n model: DeviceScreenModel;\n collapsed: boolean;\n onToggle: () => void;\n children: React.ReactNode;\n};\n\nconst FloatingWindow: React.FC<FloatingWindowProps> = ({\n title,\n model,\n collapsed,\n onToggle,\n children,\n}) => {\n const windowRef = useRef<HTMLDivElement>(null);\n /** Unset until first dragged: the window starts in the bottom right corner. */\n const [position, setPosition] = useState<Position>();\n const [width, setWidth] = useState(DEFAULT_WIDTH_PX);\n const drag = useRef<{ dx: number; dy: number } | null>(null);\n const resize = useRef<{ x: number; width: number } | null>(null);\n\n // Once moved, the window holds fixed coordinates that a shrinking viewport\n // could leave off-screen.\n useEffect(() => {\n const handleViewportResize = () => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!rect) return;\n setPosition(\n (current) =>\n current && clampToViewport(current.left, current.top, rect),\n );\n };\n window.addEventListener(\"resize\", handleViewportResize);\n return () => window.removeEventListener(\"resize\", handleViewportResize);\n }, []);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n // A drag captures the pointer, which would steal the button's click.\n if (\n !rect ||\n event.button !== 0 ||\n (event.target as HTMLElement).closest(\"button\")\n ) {\n return;\n }\n event.currentTarget.setPointerCapture(event.pointerId);\n drag.current = {\n dx: event.clientX - rect.left,\n dy: event.clientY - rect.top,\n };\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!drag.current || !rect) return;\n setPosition(\n clampToViewport(\n event.clientX - drag.current.dx,\n event.clientY - drag.current.dy,\n rect,\n ),\n );\n };\n\n const handlePointerUp = () => {\n drag.current = null;\n };\n\n const handleResizeDown = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!rect || event.button !== 0) return;\n event.currentTarget.setPointerCapture(event.pointerId);\n // Pin the top left corner, or growing would push the window leftwards\n // while it is still anchored to the bottom right.\n setPosition({ left: rect.left, top: rect.top });\n resize.current = { x: event.clientX, width: rect.width };\n };\n\n const handleResizeMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const rect = windowRef.current?.getBoundingClientRect();\n if (!resize.current || !rect) return;\n const next = resize.current.width + event.clientX - resize.current.x;\n const max = window.innerWidth - rect.left - EDGE_MARGIN_PX;\n setWidth(Math.max(MIN_WIDTH_PX, Math.min(next, max)));\n };\n\n const handleResizeUp = () => {\n resize.current = null;\n };\n\n return (\n <div\n ref={windowRef}\n className=\"bg-base border-muted fixed flex max-w-[calc(100vw-32px)] flex-col rounded-lg border shadow-lg\"\n style={{\n zIndex: 1000,\n width,\n ...(position ?? { right: EDGE_MARGIN_PX, bottom: EDGE_MARGIN_PX }),\n }}\n data-testid=\"container_device-screen\"\n >\n <div\n className=\"flex cursor-move items-center gap-8 px-12 py-8 select-none\"\n style={{ touchAction: \"none\" }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n >\n <model.icon size={16} />\n <span className=\"body-3-semi-bold text-base flex-1\">{title}</span>\n <IconButton\n appearance=\"no-background\"\n size=\"xs\"\n icon={collapsed ? ChevronUp : ChevronDown}\n aria-label={\n collapsed ? \"Expand device screen\" : \"Collapse device screen\"\n }\n onClick={onToggle}\n data-testid=\"button_toggle-device-screen\"\n />\n </div>\n {collapsed ? null : (\n <>\n <div className=\"flex flex-col gap-8 px-12 pb-12\">{children}</div>\n <div\n className=\"group absolute top-0 right-[-6px] bottom-0 flex w-[12px] cursor-ew-resize items-center justify-center\"\n style={{ touchAction: \"none\" }}\n onPointerDown={handleResizeDown}\n onPointerMove={handleResizeMove}\n onPointerUp={handleResizeUp}\n onPointerCancel={handleResizeUp}\n aria-hidden\n data-testid=\"handle_resize-device-screen\"\n >\n <span className=\"border-muted bg-base group-hover:bg-muted group-active:bg-muted h-40 w-[6px] rounded-full border transition-colors\" />\n </div>\n </>\n )}\n </div>\n );\n};\n\nfunction renderScreen(\n state: DeviceScreenState,\n model: DeviceScreenModel,\n api: ScreenApi,\n) {\n switch (state.kind) {\n case \"loading\":\n return (\n <ScreenFrame>\n <div\n className={`${model.lightScreen ? \"bg-white text-black\" : \"bg-black text-white\"} flex items-center justify-center`}\n style={{ aspectRatio: model.screenWidth / model.screenHeight }}\n data-testid=\"placeholder_device-screen-loading\"\n >\n <span className=\"animate-pulse opacity-60\">\n <model.icon size={32} />\n </span>\n </div>\n </ScreenFrame>\n );\n case \"error\":\n return (\n <Banner\n appearance=\"error\"\n title=\"Screen unavailable\"\n description={state.message}\n />\n );\n case \"os-info\":\n return (\n <>\n <DeviceOsInfo\n device={state.device}\n model={model}\n onOpenApp={model.speculos ? api.openApp : undefined}\n />\n {model.speculos ? null : (\n <Banner\n appearance=\"info\"\n title=\"No screen to capture\"\n description={`Speculos has no emulator for the ${model.label}, so this device is mocked all the way through and has no screen.`}\n data-testid=\"banner_device-screen-no-emulator\"\n />\n )}\n </>\n );\n case \"image\":\n return (\n <>\n <DeviceScreenImage\n src={state.src}\n onTouch={model.touch ? state.input.touch : undefined}\n />\n {model.touch ? null : (\n <DeviceScreenButtons onPress={state.input.pressButton} />\n )}\n </>\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["DeviceScreen_exports", "__export", "DeviceScreen", "__toCommonJS", "import_react", "import_device_mockserver_react_core", "import_lumen_ui_react", "import_symbols", "import_deviceModels", "import_DeviceOsInfo", "import_DeviceScreenButtons", "import_DeviceScreenImage", "import_ScreenFrame", "import_webScreenPlatform", "EDGE_MARGIN_PX", "DEFAULT_WIDTH_PX", "MIN_WIDTH_PX", "clampToViewport", "left", "top", "rect", "clamp", "value", "max", "deviceScreenTitle", "label", "firmwareVersion", "api", "deviceType", "floating", "defaultCollapsed", "model", "collapsed", "setCollapsed", "state", "React", "FloatingWindow", "renderScreen", "title", "onToggle", "children", "windowRef", "position", "setPosition", "width", "setWidth", "drag", "resize", "handleViewportResize", "current", "handlePointerDown", "event", "handlePointerMove", "handlePointerUp", "handleResizeDown", "handleResizeMove", "next", "handleResizeUp"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ "use strict";var f=Object.create;var h=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var B=(n,l,i,o)=>{if(l&&typeof l=="object"||typeof l=="function")for(let t of x(l))!b.call(n,t)&&t!==i&&h(n,t,{get:()=>l[t],enumerable:!(o=g(l,t))||o.enumerable});return n};var C=(n,l,i)=>(i=n!=null?f(y(n)):{},B(l||!n||!n.__esModule?h(i,"default",{value:n,enumerable:!0}):i,n));var p=C(require("react")),w=require("react-dom/client"),u=require("./DeviceScreen");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const E={id:"device-1",device_type:"stax",apps:[{name:"Bitcoin",version:"2.5.0"}]},s=(n={})=>({screenshot:vi.fn(()=>Promise.resolve(null)),idle:vi.fn(()=>Promise.resolve({kind:"os-info",device:E})),pressButton:vi.fn(()=>Promise.resolve()),touch:vi.fn(()=>Promise.resolve()),openApp:vi.fn(()=>Promise.resolve()),...n});describe("DeviceScreen",()=>{let n,l;const i=e=>n.querySelector(`[data-testid="${e}"]`),o=e=>(0,p.act)(()=>l.render(p.default.createElement(u.DeviceScreen,{deviceType:"stax",...e}))),t=(e=0)=>(0,p.act)(()=>vi.advanceTimersByTimeAsync(e)),a=(e,r,c={})=>(0,p.act)(()=>{e.dispatchEvent(new MouseEvent(r,{bubbles:!0,button:0,...c}))});beforeEach(()=>{vi.useFakeTimers(),URL.createObjectURL=vi.fn(()=>"blob:frame"),URL.revokeObjectURL=vi.fn(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),n=document.createElement("div"),document.body.append(n),l=(0,w.createRoot)(n)}),afterEach(()=>{(0,p.act)(()=>l.unmount()),n.remove(),vi.useRealTimers()}),describe("inline",()=>{it("shows a placeholder until the screen answers",()=>{o({api:s({screenshot:()=>new Promise(()=>{})})}),expect(i("placeholder_device-screen-loading")).not.toBeNull()}),it("shows the live screen of a running app",async()=>{o({api:s({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await t(),expect(i("image_device-screen")?.getAttribute("src")).toBe("blob:frame")}),it("shows the installed apps while no app runs",async()=>{o({api:s()}),await t(),expect(i("button_open-app-Bitcoin")).not.toBeNull()}),it("says why a device without an emulator has no screen",async()=>{o({api:s(),deviceType:"nanoS"}),await t(),expect(i("button_open-app-Bitcoin")).not.toBeNull(),expect(n.textContent).toContain("Speculos has no emulator for the Nano S")}),it("says nothing about emulators for a device that has one",async()=>{o({api:s()}),await t(),expect(n.textContent).not.toContain("has no emulator")}),it("says there is no screen when the api has nothing to show instead",async()=>{o({api:s({idle:void 0})}),await t(),expect(n.textContent).toContain("No screen to capture")}),it("shows why the screen is unavailable",async()=>{o({api:s({screenshot:()=>Promise.reject(new Error("offline"))})}),await t(),expect(n.textContent).toContain("Screen unavailable"),expect(n.textContent).toContain("offline")}),it("keeps the last frame through a few failures while live",async()=>{const e=vi.fn().mockResolvedValueOnce(new Blob(["png"])).mockRejectedValue(new Error("app quitting")),r=()=>e.mock.calls.length-1;for(o({api:s({screenshot:e})}),await t();r()<3;)await t(500);for(expect(i("image_device-screen")).not.toBeNull();r()<4;)await t(500);expect(i("image_device-screen")).toBeNull(),expect(n.textContent).toContain("app quitting")}),it("drives a Nano with its buttons and refreshes after",async()=>{const e=s({screenshot:vi.fn(()=>Promise.resolve(new Blob(["png"])))});o({api:e,deviceType:"nanoX"}),await t(),a(i("button_device-screen-left"),"pointerdown"),a(i("button_device-screen-left"),"pointerup"),await t(),expect(e.pressButton).toHaveBeenCalledWith("left","press-and-release"),expect(e.screenshot).toHaveBeenCalledTimes(2)}),it("drives a touch device by its screen, without buttons",async()=>{o({api:s({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await t(),expect(i("image_device-screen")).not.toBeNull(),expect(i("button_device-screen-left")).toBeNull()})}),describe("floating",()=>{const e=()=>i("container_device-screen"),r=()=>e().firstElementChild;it("titles the window with the model and firmware",async()=>{o({api:s(),floating:!0,firmwareVersion:"1.10.1"}),await t(),expect(e().textContent).toContain("Stax - 1.10.1"),expect(e().style.width).toBe("224px")}),it("stops polling while collapsed",async()=>{const c=s();o({api:c,floating:!0,defaultCollapsed:!0}),await t(5e3),expect(c.screenshot).not.toHaveBeenCalled(),expect(i("button_open-app-Bitcoin")).toBeNull(),(0,p.act)(()=>i("button_toggle-device-screen").click()),await t(),expect(c.screenshot).toHaveBeenCalled(),expect(i("button_open-app-Bitcoin")).not.toBeNull()}),it("is dragged by its header",async()=>{o({api:s(),floating:!0}),await t(),a(r(),"pointerdown",{clientX:10,clientY:10}),a(r(),"pointermove",{clientX:110,clientY:60}),a(r(),"pointerup"),a(r(),"pointermove",{clientX:500,clientY:500}),expect(e().style.left).toBe("100px"),expect(e().style.top).toBe("50px")}),describe("when the viewport shrinks",()=>{const c={width:window.innerWidth,height:window.innerHeight},d=(v,m)=>(0,p.act)(()=>{Object.assign(window,{innerWidth:v,innerHeight:m}),window.dispatchEvent(new Event("resize"))});afterEach(()=>{Object.assign(window,{innerWidth:c.width,innerHeight:c.height})}),it("pulls a dragged window back on screen",async()=>{o({api:s(),floating:!0}),await t(),e().getBoundingClientRect=()=>({left:0,top:0,width:224,height:300}),a(r(),"pointerdown",{clientX:10,clientY:10}),a(r(),"pointermove",{clientX:610,clientY:410}),a(r(),"pointerup"),expect(e().style.left).toBe("600px"),d(500,500),expect(e().style.left).toBe("276px"),expect(e().style.top).toBe("200px")}),it("leaves a window that was never moved in its corner",async()=>{o({api:s(),floating:!0}),await t(),d(500,500),expect(e().style.left).toBe(""),expect(e().style.right).toBe("16px")})}),it("is not dragged from its button or with another mouse button",async()=>{o({api:s(),floating:!0}),await t(),a(i("button_toggle-device-screen"),"pointerdown"),a(r(),"pointerdown",{button:2}),a(r(),"pointermove",{clientX:110,clientY:60}),expect(e().style.left).toBe("")}),it("is resized from its edge, down to a minimum width",async()=>{o({api:s(),floating:!0}),await t(),e().getBoundingClientRect=()=>({left:100,top:80,width:224,height:300});const c=i("handle_resize-device-screen");a(c,"pointerdown",{clientX:324}),a(c,"pointermove",{clientX:424}),expect(e().style.width).toBe("324px"),expect(e().style.left).toBe("100px"),a(c,"pointermove",{clientX:0}),a(c,"pointerup"),a(c,"pointermove",{clientX:600}),expect(e().style.width).toBe("200px")})})});
2
+ //# sourceMappingURL=DeviceScreen.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreen.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\nimport { type ScreenApi } from \"@ledgerhq/device-mockserver-react-core\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = {\n id: \"device-1\",\n device_type: \"stax\",\n apps: [{ name: \"Bitcoin\", version: \"2.5.0\" }],\n} as unknown as Device;\n\nconst anApi = (overrides: Partial<ScreenApi> = {}): ScreenApi => ({\n screenshot: vi.fn(() => Promise.resolve(null)),\n idle: vi.fn(() =>\n Promise.resolve({ kind: \"os-info\" as const, device: DEVICE }),\n ),\n pressButton: vi.fn(() => Promise.resolve()),\n touch: vi.fn(() => Promise.resolve()),\n openApp: vi.fn(() => Promise.resolve()),\n ...overrides,\n});\n\ndescribe(\"DeviceScreen\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (props: Partial<DeviceScreenProps> & { api: ScreenApi }) =>\n act(() => root.render(<DeviceScreen deviceType=\"stax\" {...props} />));\n const wait = (ms = 0) => act(() => vi.advanceTimersByTimeAsync(ms));\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n URL.createObjectURL = vi.fn(() => \"blob:frame\");\n URL.revokeObjectURL = vi.fn();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n describe(\"inline\", () => {\n it(\"shows a placeholder until the screen answers\", () => {\n render({ api: anApi({ screenshot: () => new Promise(() => {}) }) });\n\n expect(query(\"placeholder_device-screen-loading\")).not.toBeNull();\n });\n\n it(\"shows the live screen of a running app\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")?.getAttribute(\"src\")).toBe(\n \"blob:frame\",\n );\n });\n\n it(\"shows the installed apps while no app runs\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"says why a device without an emulator has no screen\", async () => {\n render({ api: anApi(), deviceType: \"nanoS\" });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n expect(container.textContent).toContain(\n \"Speculos has no emulator for the Nano S\",\n );\n });\n\n it(\"says nothing about emulators for a device that has one\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(container.textContent).not.toContain(\"has no emulator\");\n });\n\n it(\"says there is no screen when the api has nothing to show instead\", async () => {\n render({ api: anApi({ idle: undefined }) });\n await wait();\n\n expect(container.textContent).toContain(\"No screen to capture\");\n });\n\n it(\"shows why the screen is unavailable\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.reject(new Error(\"offline\")) }),\n });\n await wait();\n\n expect(container.textContent).toContain(\"Screen unavailable\");\n expect(container.textContent).toContain(\"offline\");\n });\n\n it(\"keeps the last frame through a few failures while live\", async () => {\n const screenshot = vi\n .fn<() => Promise<Blob | null>>()\n .mockResolvedValueOnce(new Blob([\"png\"]))\n .mockRejectedValue(new Error(\"app quitting\"));\n const failures = () => screenshot.mock.calls.length - 1;\n render({ api: anApi({ screenshot }) });\n await wait();\n\n while (failures() < 3) await wait(500);\n expect(query(\"image_device-screen\")).not.toBeNull();\n\n while (failures() < 4) await wait(500);\n expect(query(\"image_device-screen\")).toBeNull();\n expect(container.textContent).toContain(\"app quitting\");\n });\n\n it(\"drives a Nano with its buttons and refreshes after\", async () => {\n const api = anApi({\n screenshot: vi.fn(() => Promise.resolve(new Blob([\"png\"]))),\n });\n render({ api, deviceType: \"nanoX\" });\n await wait();\n\n pointer(query(\"button_device-screen-left\")!, \"pointerdown\");\n pointer(query(\"button_device-screen-left\")!, \"pointerup\");\n await wait();\n\n expect(api.pressButton).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(api.screenshot).toHaveBeenCalledTimes(2);\n });\n\n it(\"drives a touch device by its screen, without buttons\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")).not.toBeNull();\n expect(query(\"button_device-screen-left\")).toBeNull();\n });\n });\n\n describe(\"floating\", () => {\n const windowElement = () => query(\"container_device-screen\")!;\n const header = () => windowElement().firstElementChild!;\n\n it(\"titles the window with the model and firmware\", async () => {\n render({ api: anApi(), floating: true, firmwareVersion: \"1.10.1\" });\n await wait();\n\n expect(windowElement().textContent).toContain(\"Stax - 1.10.1\");\n expect(windowElement().style.width).toBe(\"224px\");\n });\n\n it(\"stops polling while collapsed\", async () => {\n const api = anApi();\n render({ api, floating: true, defaultCollapsed: true });\n await wait(5000);\n\n expect(api.screenshot).not.toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).toBeNull();\n\n act(() => query(\"button_toggle-device-screen\")!.click());\n await wait();\n\n expect(api.screenshot).toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"is dragged by its header\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n pointer(header(), \"pointerup\");\n pointer(header(), \"pointermove\", { clientX: 500, clientY: 500 });\n\n expect(windowElement().style.left).toBe(\"100px\");\n expect(windowElement().style.top).toBe(\"50px\");\n });\n\n describe(\"when the viewport shrinks\", () => {\n const viewport = { width: window.innerWidth, height: window.innerHeight };\n const resizeViewport = (width: number, height: number) =>\n act(() => {\n Object.assign(window, { innerWidth: width, innerHeight: height });\n window.dispatchEvent(new Event(\"resize\"));\n });\n\n afterEach(() => {\n Object.assign(window, {\n innerWidth: viewport.width,\n innerHeight: viewport.height,\n });\n });\n\n it(\"pulls a dragged window back on screen\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 224, height: 300 }) as DOMRect;\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 610, clientY: 410 });\n pointer(header(), \"pointerup\");\n expect(windowElement().style.left).toBe(\"600px\");\n\n resizeViewport(500, 500);\n\n expect(windowElement().style.left).toBe(\"276px\");\n expect(windowElement().style.top).toBe(\"200px\");\n });\n\n it(\"leaves a window that was never moved in its corner\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n resizeViewport(500, 500);\n\n expect(windowElement().style.left).toBe(\"\");\n expect(windowElement().style.right).toBe(\"16px\");\n });\n });\n\n it(\"is not dragged from its button or with another mouse button\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(query(\"button_toggle-device-screen\")!, \"pointerdown\");\n pointer(header(), \"pointerdown\", { button: 2 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n\n expect(windowElement().style.left).toBe(\"\");\n });\n\n it(\"is resized from its edge, down to a minimum width\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 100, top: 80, width: 224, height: 300 }) as DOMRect;\n const handle = query(\"handle_resize-device-screen\")!;\n\n pointer(handle, \"pointerdown\", { clientX: 324 });\n pointer(handle, \"pointermove\", { clientX: 424 });\n expect(windowElement().style.width).toBe(\"324px\");\n expect(windowElement().style.left).toBe(\"100px\");\n\n pointer(handle, \"pointermove\", { clientX: 0 });\n pointer(handle, \"pointerup\");\n pointer(handle, \"pointermove\", { clientX: 600 });\n expect(windowElement().style.width).toBe(\"200px\");\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["import_react", "import_client", "import_DeviceScreen", "DEVICE", "anApi", "overrides", "container", "root", "query", "testId", "render", "props", "React", "wait", "ms", "pointer", "target", "type", "init", "screenshot", "failures", "api", "windowElement", "header", "viewport", "resizeViewport", "width", "height", "handle"]
7
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var f=Object.create;var i=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,v=Object.prototype.hasOwnProperty;var B=(e,t)=>{for(var r in t)i(e,r,{get:t[r],enumerable:!0})},d=(e,t,r,c)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of m(t))!v.call(e,s)&&s!==r&&i(e,s,{get:()=>t[s],enumerable:!(c=y(t,s))||c.enumerable});return e};var T=(e,t,r)=>(r=e!=null?f(S(e)):{},d(t||!e||!e.__esModule?i(r,"default",{value:e,enumerable:!0}):r,e)),g=e=>d(i({},"__esModule",{value:!0}),e);var P={};B(P,{DeviceScreenButtons:()=>H});module.exports=g(P);var o=T(require("react"));const h=[{button:"left",label:"Left"},{button:"both",label:"Both"},{button:"right",label:"Right"}],b=e=>e===" "||e==="Enter",D=150,H=({onPress:e})=>{const t=(0,o.useRef)(null),r=(0,o.useRef)(null),c=(0,o.useRef)(e);c.current=e;const s=(0,o.useCallback)(()=>{r.current&&(clearTimeout(r.current),r.current=null)},[]),p=(0,o.useCallback)(n=>{if(t.current)return;const l={button:n,pressed:!1};t.current=l,r.current=setTimeout(()=>{l.pressed=!0,c.current(n,"press")},D)},[]),a=(0,o.useCallback)(()=>{const n=t.current;n&&(t.current=null,s(),c.current(n.button,n.pressed?"release":"press-and-release"))},[s]);return(0,o.useEffect)(()=>()=>{const n=t.current;t.current=null,s(),n?.pressed&&c.current(n.button,"release")},[s]),o.default.createElement("div",{className:"flex gap-8"},h.map(({button:n,label:l})=>o.default.createElement("button",{key:n,type:"button","aria-label":`${l} button`,onPointerDown:u=>{u.button===0&&(u.currentTarget.setPointerCapture(u.pointerId),p(n))},onPointerUp:a,onPointerCancel:a,onKeyDown:u=>{b(u.key)&&(u.preventDefault(),p(n))},onKeyUp:u=>b(u.key)&&a(),className:"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none",style:{touchAction:"none"},"data-testid":`button_device-screen-${n}`},l)))};0&&(module.exports={DeviceScreenButtons});
1
+ "use strict";var p=Object.create;var s=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty;var f=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},c=(t,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of d(e))!y.call(t,n)&&n!==o&&s(t,n,{get:()=>e[n],enumerable:!(i=b(e,n))||i.enumerable});return t};var v=(t,e,o)=>(o=t!=null?p(m(t)):{},c(e||!t||!t.__esModule?s(o,"default",{value:t,enumerable:!0}):o,t)),g=t=>c(s({},"__esModule",{value:!0}),t);var h={};f(h,{DeviceScreenButtons:()=>S});module.exports=g(h);var u=v(require("react")),l=require("@ledgerhq/device-mockserver-react-core");const B=[{button:"left",label:"Left"},{button:"both",label:"Both"},{button:"right",label:"Right"}],a=t=>t===" "||t==="Enter",S=({onPress:t})=>{const{hold:e,release:o}=(0,l.useButtonHold)(t);return u.default.createElement("div",{className:"flex gap-8"},B.map(({button:i,label:n})=>u.default.createElement("button",{key:i,type:"button","aria-label":`${n} button`,onPointerDown:r=>{r.button===0&&(r.currentTarget.setPointerCapture(r.pointerId),e(i))},onPointerUp:o,onPointerCancel:o,onKeyDown:r=>{a(r.key)&&(r.preventDefault(),e(i))},onKeyUp:r=>a(r.key)&&o(),className:"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none",style:{touchAction:"none"},"data-testid":`button_device-screen-${i}`},n)))};0&&(module.exports={DeviceScreenButtons});
2
2
  //# sourceMappingURL=DeviceScreenButtons.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/DeviceScreenButtons.tsx"],
4
- "sourcesContent": ["import React, { useCallback, useEffect, useRef } from \"react\";\nimport {\n type SpeculosAction,\n type SpeculosButton,\n} from \"@ledgerhq/device-mockserver-client\";\n\nconst BUTTONS: { button: SpeculosButton; label: string }[] = [\n { button: \"left\", label: \"Left\" },\n { button: \"both\", label: \"Both\" },\n { button: \"right\", label: \"Right\" },\n];\n\nconst isActivationKey = (key: string) => key === \" \" || key === \"Enter\";\n\n/**\n * How long the pointer stays down before the interaction counts as a hold.\n * A click sent as a separate press and release is two requests the device can\n * receive in either order, and a chord of both buttons is only seen when they\n * are down together as its event loop samples them, so a click that beats the\n * loop is dropped. One press-and-release leaves the timing to the emulator.\n */\nconst HOLD_THRESHOLD_MS = 150;\n\ninterface Held {\n button: SpeculosButton;\n pressed: boolean;\n}\n\ntype DeviceScreenButtonsProps = {\n onPress: (button: SpeculosButton, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenButtons: React.FC<DeviceScreenButtonsProps> = ({\n onPress,\n}) => {\n const held = useRef<Held | null>(null);\n const holdTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pressRef = useRef(onPress);\n pressRef.current = onPress;\n\n const clearHoldTimer = useCallback(() => {\n if (!holdTimer.current) return;\n clearTimeout(holdTimer.current);\n holdTimer.current = null;\n }, []);\n\n const hold = useCallback((button: SpeculosButton) => {\n if (held.current) return;\n const entry: Held = { button, pressed: false };\n held.current = entry;\n holdTimer.current = setTimeout(() => {\n entry.pressed = true;\n pressRef.current(button, \"press\");\n }, HOLD_THRESHOLD_MS);\n }, []);\n\n const release = useCallback(() => {\n const entry = held.current;\n if (!entry) return;\n held.current = null;\n clearHoldTimer();\n pressRef.current(\n entry.button,\n entry.pressed ? \"release\" : \"press-and-release\",\n );\n }, [clearHoldTimer]);\n\n useEffect(\n () => () => {\n const entry = held.current;\n held.current = null;\n clearHoldTimer();\n if (entry?.pressed) pressRef.current(entry.button, \"release\");\n },\n [clearHoldTimer],\n );\n\n // Plain buttons: Lumen's Button does not report press and release apart,\n // and a hold needs both.\n return (\n <div className=\"flex gap-8\">\n {BUTTONS.map(({ button, label }) => (\n <button\n key={button}\n type=\"button\"\n aria-label={`${label} button`}\n onPointerDown={(event) => {\n if (event.button !== 0) return;\n // Capture so the release still arrives if the pointer wanders off.\n event.currentTarget.setPointerCapture(event.pointerId);\n hold(button);\n }}\n onPointerUp={release}\n onPointerCancel={release}\n // Keyboard activation fires no pointer events.\n onKeyDown={(event) => {\n if (!isActivationKey(event.key)) return;\n event.preventDefault();\n hold(button);\n }}\n onKeyUp={(event) => isActivationKey(event.key) && release()}\n className=\"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none\"\n style={{ touchAction: \"none\" }}\n data-testid={`button_device-screen-${button}`}\n >\n {label}\n </button>\n ))}\n </div>\n );\n};\n"],
5
- "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,yBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAsD,oBAMtD,MAAMC,EAAuD,CAC3D,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,QAAS,MAAO,OAAQ,CACpC,EAEMC,EAAmBC,GAAgBA,IAAQ,KAAOA,IAAQ,QAS1DC,EAAoB,IAWbN,EAA0D,CAAC,CACtE,QAAAO,CACF,IAAM,CACJ,MAAMC,KAAO,UAAoB,IAAI,EAC/BC,KAAY,UAA6C,IAAI,EAC7DC,KAAW,UAAOH,CAAO,EAC/BG,EAAS,QAAUH,EAEnB,MAAMI,KAAiB,eAAY,IAAM,CAClCF,EAAU,UACf,aAAaA,EAAU,OAAO,EAC9BA,EAAU,QAAU,KACtB,EAAG,CAAC,CAAC,EAECG,KAAO,eAAaC,GAA2B,CACnD,GAAIL,EAAK,QAAS,OAClB,MAAMM,EAAc,CAAE,OAAAD,EAAQ,QAAS,EAAM,EAC7CL,EAAK,QAAUM,EACfL,EAAU,QAAU,WAAW,IAAM,CACnCK,EAAM,QAAU,GAChBJ,EAAS,QAAQG,EAAQ,OAAO,CAClC,EAAGP,CAAiB,CACtB,EAAG,CAAC,CAAC,EAECS,KAAU,eAAY,IAAM,CAChC,MAAMD,EAAQN,EAAK,QACdM,IACLN,EAAK,QAAU,KACfG,EAAe,EACfD,EAAS,QACPI,EAAM,OACNA,EAAM,QAAU,UAAY,mBAC9B,EACF,EAAG,CAACH,CAAc,CAAC,EAEnB,sBACE,IAAM,IAAM,CACV,MAAMG,EAAQN,EAAK,QACnBA,EAAK,QAAU,KACfG,EAAe,EACXG,GAAO,SAASJ,EAAS,QAAQI,EAAM,OAAQ,SAAS,CAC9D,EACA,CAACH,CAAc,CACjB,EAKE,EAAAK,QAAA,cAAC,OAAI,UAAU,cACZb,EAAQ,IAAI,CAAC,CAAE,OAAAU,EAAQ,MAAAI,CAAM,IAC5B,EAAAD,QAAA,cAAC,UACC,IAAKH,EACL,KAAK,SACL,aAAY,GAAGI,CAAK,UACpB,cAAgBC,GAAU,CACpBA,EAAM,SAAW,IAErBA,EAAM,cAAc,kBAAkBA,EAAM,SAAS,EACrDN,EAAKC,CAAM,EACb,EACA,YAAaE,EACb,gBAAiBA,EAEjB,UAAYG,GAAU,CACfd,EAAgBc,EAAM,GAAG,IAC9BA,EAAM,eAAe,EACrBN,EAAKC,CAAM,EACb,EACA,QAAUK,GAAUd,EAAgBc,EAAM,GAAG,GAAKH,EAAQ,EAC1D,UAAU,2HACV,MAAO,CAAE,YAAa,MAAO,EAC7B,cAAa,wBAAwBF,CAAM,IAE1CI,CACH,CACD,CACH,CAEJ",
6
- "names": ["DeviceScreenButtons_exports", "__export", "DeviceScreenButtons", "__toCommonJS", "import_react", "BUTTONS", "isActivationKey", "key", "HOLD_THRESHOLD_MS", "onPress", "held", "holdTimer", "pressRef", "clearHoldTimer", "hold", "button", "entry", "release", "React", "label", "event"]
4
+ "sourcesContent": ["import React from \"react\";\nimport {\n type SpeculosAction,\n type SpeculosButton,\n} from \"@ledgerhq/device-mockserver-client\";\nimport { useButtonHold } from \"@ledgerhq/device-mockserver-react-core\";\n\nconst BUTTONS: { button: SpeculosButton; label: string }[] = [\n { button: \"left\", label: \"Left\" },\n { button: \"both\", label: \"Both\" },\n { button: \"right\", label: \"Right\" },\n];\n\nconst isActivationKey = (key: string) => key === \" \" || key === \"Enter\";\n\ntype DeviceScreenButtonsProps = {\n onPress: (button: SpeculosButton, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenButtons: React.FC<DeviceScreenButtonsProps> = ({\n onPress,\n}) => {\n const { hold, release } = useButtonHold(onPress);\n\n // Plain buttons: Lumen's Button does not report press and release apart,\n // and a hold needs both.\n return (\n <div className=\"flex gap-8\">\n {BUTTONS.map(({ button, label }) => (\n <button\n key={button}\n type=\"button\"\n aria-label={`${label} button`}\n onPointerDown={(event) => {\n if (event.button !== 0) return;\n // Capture so the release still arrives if the pointer wanders off.\n event.currentTarget.setPointerCapture(event.pointerId);\n hold(button);\n }}\n onPointerUp={release}\n onPointerCancel={release}\n // Keyboard activation fires no pointer events.\n onKeyDown={(event) => {\n if (!isActivationKey(event.key)) return;\n event.preventDefault();\n hold(button);\n }}\n onKeyUp={(event) => isActivationKey(event.key) && release()}\n className=\"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none\"\n style={{ touchAction: \"none\" }}\n data-testid={`button_device-screen-${button}`}\n >\n {label}\n </button>\n ))}\n </div>\n );\n};\n"],
5
+ "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,yBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAkB,oBAKlBC,EAA8B,kDAE9B,MAAMC,EAAuD,CAC3D,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,QAAS,MAAO,OAAQ,CACpC,EAEMC,EAAmBC,GAAgBA,IAAQ,KAAOA,IAAQ,QAMnDN,EAA0D,CAAC,CACtE,QAAAO,CACF,IAAM,CACJ,KAAM,CAAE,KAAAC,EAAM,QAAAC,CAAQ,KAAI,iBAAcF,CAAO,EAI/C,OACE,EAAAG,QAAA,cAAC,OAAI,UAAU,cACZN,EAAQ,IAAI,CAAC,CAAE,OAAAO,EAAQ,MAAAC,CAAM,IAC5B,EAAAF,QAAA,cAAC,UACC,IAAKC,EACL,KAAK,SACL,aAAY,GAAGC,CAAK,UACpB,cAAgBC,GAAU,CACpBA,EAAM,SAAW,IAErBA,EAAM,cAAc,kBAAkBA,EAAM,SAAS,EACrDL,EAAKG,CAAM,EACb,EACA,YAAaF,EACb,gBAAiBA,EAEjB,UAAYI,GAAU,CACfR,EAAgBQ,EAAM,GAAG,IAC9BA,EAAM,eAAe,EACrBL,EAAKG,CAAM,EACb,EACA,QAAUE,GAAUR,EAAgBQ,EAAM,GAAG,GAAKJ,EAAQ,EAC1D,UAAU,2HACV,MAAO,CAAE,YAAa,MAAO,EAC7B,cAAa,wBAAwBE,CAAM,IAE1CC,CACH,CACD,CACH,CAEJ",
6
+ "names": ["DeviceScreenButtons_exports", "__export", "DeviceScreenButtons", "__toCommonJS", "import_react", "import_device_mockserver_react_core", "BUTTONS", "isActivationKey", "key", "onPress", "hold", "release", "React", "button", "label", "event"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ "use strict";var h=Object.create;var p=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,E=Object.prototype.hasOwnProperty;var f=(t,r,e,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of v(r))!E.call(t,o)&&o!==e&&p(t,o,{get:()=>r[o],enumerable:!(n=m(r,o))||n.enumerable});return t};var g=(t,r,e)=>(e=t!=null?h(b(t)):{},f(r||!t||!t.__esModule?p(e,"default",{value:t,enumerable:!0}):e,t));var s=g(require("react")),c=require("react-dom/client"),u=require("./DeviceScreenButtons");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenButtons",()=>{let t,r;const e=vi.fn(),n=i=>t.querySelector(`[data-testid="button_device-screen-${i}"]`),o=(i,l,d={})=>(0,s.act)(()=>{i.dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=(i,l,d)=>(0,s.act)(()=>{i.dispatchEvent(new KeyboardEvent(l,{bubbles:!0,key:d}))});beforeEach(()=>{vi.useFakeTimers(),e.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),t=document.createElement("div"),document.body.append(t),r=(0,c.createRoot)(t),(0,s.act)(()=>r.render(s.default.createElement(u.DeviceScreenButtons,{onPress:e})))}),afterEach(()=>{(0,s.act)(()=>r.unmount()),t.remove(),vi.useRealTimers()}),it("sends a quick click as one press-and-release",()=>{o(n("left"),"pointerdown"),o(n("left"),"pointerup"),expect(e).toHaveBeenCalledOnce(),expect(e).toHaveBeenCalledWith("left","press-and-release")}),it("sends a hold as a press then a release",()=>{o(n("both"),"pointerdown"),(0,s.act)(()=>{vi.advanceTimersByTime(200)}),o(n("both"),"pointerup"),expect(e.mock.calls).toEqual([["both","press"],["both","release"]])}),it("ignores buttons other than the main one",()=>{o(n("right"),"pointerdown",{button:2}),o(n("right"),"pointerup",{button:2}),expect(e).not.toHaveBeenCalled()}),it("handles one button at a time",()=>{o(n("left"),"pointerdown"),o(n("right"),"pointerdown"),o(n("left"),"pointerup"),expect(e).toHaveBeenCalledWith("left","press-and-release"),expect(e).toHaveBeenCalledOnce()}),it("drives the buttons from the keyboard",()=>{a(n("right"),"keydown","Enter"),a(n("right"),"keyup","Enter"),a(n("right"),"keydown","a"),expect(e).toHaveBeenCalledOnce(),expect(e).toHaveBeenCalledWith("right","press-and-release")}),it("releases a held button when unmounted",()=>{o(n("left"),"pointerdown"),(0,s.act)(()=>{vi.advanceTimersByTime(200)}),(0,s.act)(()=>r.render(s.default.createElement(s.default.Fragment,null))),expect(e).toHaveBeenLastCalledWith("left","release")})});
2
+ //# sourceMappingURL=DeviceScreenButtons.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenButtons.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenButtons\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onPress = vi.fn();\n\n const button = (name: string) =>\n container.querySelector<HTMLButtonElement>(\n `[data-testid=\"button_device-screen-${name}\"]`,\n )!;\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n const key = (target: Element, type: string, value: string) =>\n act(() => {\n target.dispatchEvent(\n new KeyboardEvent(type, { bubbles: true, key: value }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n onPress.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n act(() => root.render(<DeviceScreenButtons onPress={onPress} />));\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"sends a quick click as one press-and-release\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n });\n\n it(\"sends a hold as a press then a release\", () => {\n pointer(button(\"both\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n pointer(button(\"both\"), \"pointerup\");\n\n expect(onPress.mock.calls).toEqual([\n [\"both\", \"press\"],\n [\"both\", \"release\"],\n ]);\n });\n\n it(\"ignores buttons other than the main one\", () => {\n pointer(button(\"right\"), \"pointerdown\", { button: 2 });\n pointer(button(\"right\"), \"pointerup\", { button: 2 });\n\n expect(onPress).not.toHaveBeenCalled();\n });\n\n it(\"handles one button at a time\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"right\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(onPress).toHaveBeenCalledOnce();\n });\n\n it(\"drives the buttons from the keyboard\", () => {\n key(button(\"right\"), \"keydown\", \"Enter\");\n key(button(\"right\"), \"keyup\", \"Enter\");\n key(button(\"right\"), \"keydown\", \"a\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"right\", \"press-and-release\");\n });\n\n it(\"releases a held button when unmounted\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n act(() => root.render(<></>));\n\n expect(onPress).toHaveBeenLastCalledWith(\"left\", \"release\");\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["import_react", "import_client", "import_DeviceScreenButtons", "container", "root", "onPress", "button", "name", "pointer", "target", "type", "init", "key", "value", "React"]
7
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var v=Object.create;var s=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var H=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},p=(t,e,o,u)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of I(e))!D.call(t,i)&&i!==o&&s(t,i,{get:()=>e[i],enumerable:!(u=E(e,i))||u.enumerable});return t};var M=(t,e,o)=>(o=t!=null?v(S(t)):{},p(e||!t||!t.__esModule?s(o,"default",{value:t,enumerable:!0}):o,t)),R=t=>p(s({},"__esModule",{value:!0}),t);var w={};H(w,{DeviceScreenImage:()=>C});module.exports=R(w);var r=M(require("react")),f=require("./ScreenFrame");const C=({src:t,onTouch:e})=>{const o=(0,r.useRef)(null),[u,i]=(0,r.useState)(!1),c=(0,r.useRef)(null),m=(0,r.useRef)(e);m.current=e,(0,r.useEffect)(()=>i(!1),[t]);const h=a=>{const n=o.current;if(!n?.naturalWidth||!n.naturalHeight)return null;const l=n.getBoundingClientRect();if(!l.width||!l.height)return null;const g=(x,y)=>Math.min(Math.max(Math.round(x),0),y-1);return{x:g((a.clientX-l.left)/l.width*n.naturalWidth,n.naturalWidth),y:g((a.clientY-l.top)/l.height*n.naturalHeight,n.naturalHeight)}},b=a=>{if(!e||c.current||a.button!==0)return;const n=h(a);n&&(o.current?.setPointerCapture(a.pointerId),c.current=n,e(n.x,n.y,"press"))},d=a=>{const n=c.current;!e||!n||(c.current=null,o.current?.releasePointerCapture(a.pointerId),e(n.x,n.y,"release"))};(0,r.useEffect)(()=>()=>{const a=c.current;a&&(c.current=null,m.current?.(a.x,a.y,"release"))},[]);const P=(0,r.useCallback)(()=>i(!0),[]);return u?r.default.createElement("p",{className:"body-4 text-error text-center","data-testid":"text_device-screen-undecodable"},"Screenshot could not be decoded"):r.default.createElement(f.ScreenFrame,null,r.default.createElement("img",{ref:o,src:t,alt:"Device screen",draggable:!1,onError:P,onPointerDown:b,onPointerUp:d,onPointerCancel:d,className:`block w-full bg-black select-none ${e?"cursor-pointer":""}`,style:{imageRendering:"pixelated",touchAction:"none"},"data-testid":"image_device-screen"}))};0&&(module.exports={DeviceScreenImage});
1
+ "use strict";var P=Object.create;var s=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var E=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var x=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},p=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of v(t))!I.call(e,o)&&o!==n&&s(e,o,{get:()=>t[o],enumerable:!(i=S(t,o))||i.enumerable});return e};var D=(e,t,n)=>(n=e!=null?P(E(e)):{},p(t||!e||!e.__esModule?s(n,"default",{value:e,enumerable:!0}):n,e)),y=e=>p(s({},"__esModule",{value:!0}),e);var H={};x(H,{DeviceScreenImage:()=>C});module.exports=y(H);var r=D(require("react")),l=require("@ledgerhq/device-mockserver-react-core"),f=require("./ScreenFrame");const C=({src:e,onTouch:t})=>{const n=(0,r.useRef)(null),[i,o]=(0,r.useState)(!1),m=(0,l.useTouchHold)(t);(0,r.useEffect)(()=>o(!1),[e]);const h=c=>{const a=n.current;if(!t||!a||c.button!==0)return;const d=a.getBoundingClientRect(),g=(0,l.toScreenPoint)(c.clientX-d.left,c.clientY-d.top,d,{width:a.naturalWidth,height:a.naturalHeight});g&&m.hold(g)&&a.setPointerCapture(c.pointerId)},u=c=>{m.release()&&n.current?.releasePointerCapture(c.pointerId)},b=(0,r.useCallback)(()=>o(!0),[]);return i?r.default.createElement("p",{className:"body-4 text-error text-center","data-testid":"text_device-screen-undecodable"},"Screenshot could not be decoded"):r.default.createElement(f.ScreenFrame,null,r.default.createElement("img",{ref:n,src:e,alt:"Device screen",draggable:!1,onError:b,onPointerDown:h,onPointerUp:u,onPointerCancel:u,className:`block w-full bg-black select-none ${t?"cursor-pointer":""}`,style:{imageRendering:"pixelated",touchAction:"none"},"data-testid":"image_device-screen"}))};0&&(module.exports={DeviceScreenImage});
2
2
  //# sourceMappingURL=DeviceScreenImage.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/DeviceScreenImage.tsx"],
4
- "sourcesContent": ["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type SpeculosAction } from \"@ledgerhq/device-mockserver-client\";\n\nimport { ScreenFrame } from \"./ScreenFrame\";\n\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\ntype DeviceScreenImageProps = {\n src: string;\n /** Omitted for a button-driven device. */\n onTouch?: (x: number, y: number, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenImage: React.FC<DeviceScreenImageProps> = ({\n src,\n onTouch,\n}) => {\n const imageRef = useRef<HTMLImageElement>(null);\n const [undecodable, setUndecodable] = useState(false);\n /** Where the finger went down, so the release lands on the same spot. */\n const held = useRef<Point | null>(null);\n const touchRef = useRef(onTouch);\n touchRef.current = onTouch;\n\n useEffect(() => setUndecodable(false), [src]);\n\n const toDevicePoint = (\n event: React.PointerEvent<HTMLImageElement>,\n ): Point | null => {\n const image = imageRef.current;\n if (!image?.naturalWidth || !image.naturalHeight) return null;\n const rect = image.getBoundingClientRect();\n if (!rect.width || !rect.height) return null;\n // The far edge maps to the pixel count itself, one past the last pixel.\n const clamp = (value: number, size: number) =>\n Math.min(Math.max(Math.round(value), 0), size - 1);\n return {\n x: clamp(\n ((event.clientX - rect.left) / rect.width) * image.naturalWidth,\n image.naturalWidth,\n ),\n y: clamp(\n ((event.clientY - rect.top) / rect.height) * image.naturalHeight,\n image.naturalHeight,\n ),\n };\n };\n\n const handlePointerDown = (event: React.PointerEvent<HTMLImageElement>) => {\n if (!onTouch || held.current || event.button !== 0) return;\n const point = toDevicePoint(event);\n if (!point) return;\n // Capture so the release still arrives if the pointer wanders off the\n // image mid-hold; without it the device would stay pressed forever.\n imageRef.current?.setPointerCapture(event.pointerId);\n held.current = point;\n onTouch(point.x, point.y, \"press\");\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLImageElement>) => {\n const point = held.current;\n if (!onTouch || !point) return;\n held.current = null;\n imageRef.current?.releasePointerCapture(event.pointerId);\n onTouch(point.x, point.y, \"release\");\n };\n\n useEffect(\n () => () => {\n const point = held.current;\n if (!point) return;\n held.current = null;\n touchRef.current?.(point.x, point.y, \"release\");\n },\n [],\n );\n\n const handleError = useCallback(() => setUndecodable(true), []);\n\n if (undecodable) {\n return (\n <p\n className=\"body-4 text-error text-center\"\n data-testid=\"text_device-screen-undecodable\"\n >\n Screenshot could not be decoded\n </p>\n );\n }\n\n return (\n <ScreenFrame>\n <img\n ref={imageRef}\n src={src}\n alt=\"Device screen\"\n draggable={false}\n onError={handleError}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n className={`block w-full bg-black select-none ${\n onTouch ? \"cursor-pointer\" : \"\"\n }`}\n // Device screens are tiny; smoothing them turns text to mush, and a\n // hold must not start a native image drag.\n style={{ imageRendering: \"pixelated\", touchAction: \"none\" }}\n data-testid=\"image_device-screen\"\n />\n </ScreenFrame>\n );\n};\n"],
5
- "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAgE,oBAGhEC,EAA4B,yBAarB,MAAMH,EAAsD,CAAC,CAClE,IAAAI,EACA,QAAAC,CACF,IAAM,CACJ,MAAMC,KAAW,UAAyB,IAAI,EACxC,CAACC,EAAaC,CAAc,KAAI,YAAS,EAAK,EAE9CC,KAAO,UAAqB,IAAI,EAChCC,KAAW,UAAOL,CAAO,EAC/BK,EAAS,QAAUL,KAEnB,aAAU,IAAMG,EAAe,EAAK,EAAG,CAACJ,CAAG,CAAC,EAE5C,MAAMO,EACJC,GACiB,CACjB,MAAMC,EAAQP,EAAS,QACvB,GAAI,CAACO,GAAO,cAAgB,CAACA,EAAM,cAAe,OAAO,KACzD,MAAMC,EAAOD,EAAM,sBAAsB,EACzC,GAAI,CAACC,EAAK,OAAS,CAACA,EAAK,OAAQ,OAAO,KAExC,MAAMC,EAAQ,CAACC,EAAeC,IAC5B,KAAK,IAAI,KAAK,IAAI,KAAK,MAAMD,CAAK,EAAG,CAAC,EAAGC,EAAO,CAAC,EACnD,MAAO,CACL,EAAGF,GACCH,EAAM,QAAUE,EAAK,MAAQA,EAAK,MAASD,EAAM,aACnDA,EAAM,YACR,EACA,EAAGE,GACCH,EAAM,QAAUE,EAAK,KAAOA,EAAK,OAAUD,EAAM,cACnDA,EAAM,aACR,CACF,CACF,EAEMK,EAAqBN,GAAgD,CACzE,GAAI,CAACP,GAAWI,EAAK,SAAWG,EAAM,SAAW,EAAG,OACpD,MAAMO,EAAQR,EAAcC,CAAK,EAC5BO,IAGLb,EAAS,SAAS,kBAAkBM,EAAM,SAAS,EACnDH,EAAK,QAAUU,EACfd,EAAQc,EAAM,EAAGA,EAAM,EAAG,OAAO,EACnC,EAEMC,EAAmBR,GAAgD,CACvE,MAAMO,EAAQV,EAAK,QACf,CAACJ,GAAW,CAACc,IACjBV,EAAK,QAAU,KACfH,EAAS,SAAS,sBAAsBM,EAAM,SAAS,EACvDP,EAAQc,EAAM,EAAGA,EAAM,EAAG,SAAS,EACrC,KAEA,aACE,IAAM,IAAM,CACV,MAAMA,EAAQV,EAAK,QACdU,IACLV,EAAK,QAAU,KACfC,EAAS,UAAUS,EAAM,EAAGA,EAAM,EAAG,SAAS,EAChD,EACA,CAAC,CACH,EAEA,MAAME,KAAc,eAAY,IAAMb,EAAe,EAAI,EAAG,CAAC,CAAC,EAE9D,OAAID,EAEA,EAAAe,QAAA,cAAC,KACC,UAAU,gCACV,cAAY,kCACb,iCAED,EAKF,EAAAA,QAAA,cAAC,mBACC,EAAAA,QAAA,cAAC,OACC,IAAKhB,EACL,IAAKF,EACL,IAAI,gBACJ,UAAW,GACX,QAASiB,EACT,cAAeH,EACf,YAAaE,EACb,gBAAiBA,EACjB,UAAW,qCACTf,EAAU,iBAAmB,EAC/B,GAGA,MAAO,CAAE,eAAgB,YAAa,YAAa,MAAO,EAC1D,cAAY,sBACd,CACF,CAEJ",
6
- "names": ["DeviceScreenImage_exports", "__export", "DeviceScreenImage", "__toCommonJS", "import_react", "import_ScreenFrame", "src", "onTouch", "imageRef", "undecodable", "setUndecodable", "held", "touchRef", "toDevicePoint", "event", "image", "rect", "clamp", "value", "size", "handlePointerDown", "point", "handlePointerUp", "handleError", "React"]
4
+ "sourcesContent": ["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type SpeculosAction } from \"@ledgerhq/device-mockserver-client\";\nimport {\n toScreenPoint,\n useTouchHold,\n} from \"@ledgerhq/device-mockserver-react-core\";\n\nimport { ScreenFrame } from \"./ScreenFrame\";\n\ntype DeviceScreenImageProps = {\n src: string;\n /** Omitted for a button-driven device. */\n onTouch?: (x: number, y: number, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenImage: React.FC<DeviceScreenImageProps> = ({\n src,\n onTouch,\n}) => {\n const imageRef = useRef<HTMLImageElement>(null);\n const [undecodable, setUndecodable] = useState(false);\n const touch = useTouchHold(onTouch);\n\n useEffect(() => setUndecodable(false), [src]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLImageElement>) => {\n const image = imageRef.current;\n if (!onTouch || !image || event.button !== 0) return;\n const rect = image.getBoundingClientRect();\n const point = toScreenPoint(\n event.clientX - rect.left,\n event.clientY - rect.top,\n rect,\n { width: image.naturalWidth, height: image.naturalHeight },\n );\n // Capture so the release still arrives if the pointer wanders off the\n // image mid-hold; without it the device would stay pressed forever.\n if (point && touch.hold(point)) {\n image.setPointerCapture(event.pointerId);\n }\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLImageElement>) => {\n if (touch.release()) {\n imageRef.current?.releasePointerCapture(event.pointerId);\n }\n };\n\n const handleError = useCallback(() => setUndecodable(true), []);\n\n if (undecodable) {\n return (\n <p\n className=\"body-4 text-error text-center\"\n data-testid=\"text_device-screen-undecodable\"\n >\n Screenshot could not be decoded\n </p>\n );\n }\n\n return (\n <ScreenFrame>\n <img\n ref={imageRef}\n src={src}\n alt=\"Device screen\"\n draggable={false}\n onError={handleError}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n className={`block w-full bg-black select-none ${\n onTouch ? \"cursor-pointer\" : \"\"\n }`}\n // Device screens are tiny; smoothing them turns text to mush, and a\n // hold must not start a native image drag.\n style={{ imageRendering: \"pixelated\", touchAction: \"none\" }}\n data-testid=\"image_device-screen\"\n />\n </ScreenFrame>\n );\n};\n"],
5
+ "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAgE,oBAEhEC,EAGO,kDAEPC,EAA4B,yBAQrB,MAAMJ,EAAsD,CAAC,CAClE,IAAAK,EACA,QAAAC,CACF,IAAM,CACJ,MAAMC,KAAW,UAAyB,IAAI,EACxC,CAACC,EAAaC,CAAc,KAAI,YAAS,EAAK,EAC9CC,KAAQ,gBAAaJ,CAAO,KAElC,aAAU,IAAMG,EAAe,EAAK,EAAG,CAACJ,CAAG,CAAC,EAE5C,MAAMM,EAAqBC,GAAgD,CACzE,MAAMC,EAAQN,EAAS,QACvB,GAAI,CAACD,GAAW,CAACO,GAASD,EAAM,SAAW,EAAG,OAC9C,MAAME,EAAOD,EAAM,sBAAsB,EACnCE,KAAQ,iBACZH,EAAM,QAAUE,EAAK,KACrBF,EAAM,QAAUE,EAAK,IACrBA,EACA,CAAE,MAAOD,EAAM,aAAc,OAAQA,EAAM,aAAc,CAC3D,EAGIE,GAASL,EAAM,KAAKK,CAAK,GAC3BF,EAAM,kBAAkBD,EAAM,SAAS,CAE3C,EAEMI,EAAmBJ,GAAgD,CACnEF,EAAM,QAAQ,GAChBH,EAAS,SAAS,sBAAsBK,EAAM,SAAS,CAE3D,EAEMK,KAAc,eAAY,IAAMR,EAAe,EAAI,EAAG,CAAC,CAAC,EAE9D,OAAID,EAEA,EAAAU,QAAA,cAAC,KACC,UAAU,gCACV,cAAY,kCACb,iCAED,EAKF,EAAAA,QAAA,cAAC,mBACC,EAAAA,QAAA,cAAC,OACC,IAAKX,EACL,IAAKF,EACL,IAAI,gBACJ,UAAW,GACX,QAASY,EACT,cAAeN,EACf,YAAaK,EACb,gBAAiBA,EACjB,UAAW,qCACTV,EAAU,iBAAmB,EAC/B,GAGA,MAAO,CAAE,eAAgB,YAAa,YAAa,MAAO,EAC1D,cAAY,sBACd,CACF,CAEJ",
6
+ "names": ["DeviceScreenImage_exports", "__export", "DeviceScreenImage", "__toCommonJS", "import_react", "import_device_mockserver_react_core", "import_ScreenFrame", "src", "onTouch", "imageRef", "undecodable", "setUndecodable", "touch", "handlePointerDown", "event", "image", "rect", "point", "handlePointerUp", "handleError", "React"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ "use strict";var h=Object.create;var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var g=(t,n,e,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of E(n))!b.call(t,o)&&o!==e&&d(t,o,{get:()=>n[o],enumerable:!(a=v(n,o))||a.enumerable});return t};var f=(t,n,e)=>(e=t!=null?h(T(t)):{},g(n||!t||!t.__esModule?d(e,"default",{value:t,enumerable:!0}):e,t));var i=f(require("react")),p=require("react-dom/client"),s=require("@ledgerhq/device-mockserver-react-core"),u=require("./DeviceScreenImage");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenImage",()=>{let t,n;const e=vi.fn(),a=()=>t.querySelector('[data-testid="image_device-screen"]'),o=l=>(0,i.act)(()=>n.render(i.default.createElement(u.DeviceScreenImage,{src:"blob:1",onTouch:l}))),r=(l,m={})=>(0,i.act)(()=>{a().dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...m}))}),c=()=>{Object.defineProperty(a(),"naturalWidth",{value:400}),Object.defineProperty(a(),"naturalHeight",{value:600}),a().getBoundingClientRect=()=>({left:0,top:0,width:200,height:300})};beforeEach(()=>{vi.useFakeTimers(),e.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),t=document.createElement("div"),document.body.append(t),n=(0,p.createRoot)(t)}),afterEach(()=>{(0,i.act)(()=>n.unmount()),t.remove(),vi.useRealTimers()}),it("maps a tap to device pixels",()=>{o(e),c(),r("pointerdown",{clientX:50,clientY:150}),r("pointerup",{clientX:190,clientY:10}),expect(e).toHaveBeenCalledExactlyOnceWith(100,300,"press-and-release")}),it("releases a hold where it went down",()=>{o(e),c(),r("pointerdown",{clientX:50,clientY:150}),(0,i.act)(()=>{vi.advanceTimersByTime(s.HOLD_THRESHOLD_MS)}),r("pointerup",{clientX:190,clientY:10}),expect(e.mock.calls).toEqual([[100,300,"press"],[100,300,"release"]])}),it("clamps a touch on the far edge to the last pixel",()=>{o(e),c(),r("pointerdown",{clientX:200,clientY:300}),r("pointerup"),expect(e).toHaveBeenCalledWith(399,599,"press-and-release")}),it("ignores touches before the image has a size",()=>{o(e),r("pointerdown",{clientX:10,clientY:10}),r("pointerup"),expect(e).not.toHaveBeenCalled()}),it("ignores touches on a button-driven device",()=>{o(),c(),r("pointerdown",{clientX:10,clientY:10}),expect(e).not.toHaveBeenCalled()}),it("releases a held touch when unmounted",()=>{o(e),c(),r("pointerdown",{clientX:50,clientY:150}),(0,i.act)(()=>{vi.advanceTimersByTime(s.HOLD_THRESHOLD_MS)}),(0,i.act)(()=>n.render(i.default.createElement(i.default.Fragment,null))),expect(e).toHaveBeenLastCalledWith(100,300,"release")}),it("says so when the screenshot cannot be decoded",()=>{o(e),(0,i.act)(()=>{a().dispatchEvent(new Event("error"))}),expect(t.querySelector('[data-testid="text_device-screen-undecodable"]')).not.toBeNull()})});
2
+ //# sourceMappingURL=DeviceScreenImage.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenImage.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { HOLD_THRESHOLD_MS } from \"@ledgerhq/device-mockserver-react-core\";\n\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenImage\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onTouch = vi.fn();\n\n const image = () =>\n container.querySelector<HTMLImageElement>(\n '[data-testid=\"image_device-screen\"]',\n )!;\n const render = (touch?: typeof onTouch) =>\n act(() => root.render(<DeviceScreenImage src=\"blob:1\" onTouch={touch} />));\n const pointer = (type: string, init: MouseEventInit = {}) =>\n act(() => {\n image().dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n /** A 400x600 screen drawn at 200x300 from the page's top left corner. */\n const sizeImage = () => {\n Object.defineProperty(image(), \"naturalWidth\", { value: 400 });\n Object.defineProperty(image(), \"naturalHeight\", { value: 600 });\n image().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 200, height: 300 }) as DOMRect;\n };\n\n beforeEach(() => {\n vi.useFakeTimers();\n onTouch.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"maps a tap to device pixels\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n pointer(\"pointerup\", { clientX: 190, clientY: 10 });\n\n expect(onTouch).toHaveBeenCalledExactlyOnceWith(\n 100,\n 300,\n \"press-and-release\",\n );\n });\n\n it(\"releases a hold where it went down\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n act(() => {\n vi.advanceTimersByTime(HOLD_THRESHOLD_MS);\n });\n pointer(\"pointerup\", { clientX: 190, clientY: 10 });\n\n expect(onTouch.mock.calls).toEqual([\n [100, 300, \"press\"],\n [100, 300, \"release\"],\n ]);\n });\n\n it(\"clamps a touch on the far edge to the last pixel\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 200, clientY: 300 });\n pointer(\"pointerup\");\n\n expect(onTouch).toHaveBeenCalledWith(399, 599, \"press-and-release\");\n });\n\n it(\"ignores touches before the image has a size\", () => {\n render(onTouch);\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(\"pointerup\");\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"ignores touches on a button-driven device\", () => {\n render();\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"releases a held touch when unmounted\", () => {\n render(onTouch);\n sizeImage();\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n act(() => {\n vi.advanceTimersByTime(HOLD_THRESHOLD_MS);\n });\n\n act(() => root.render(<></>));\n\n expect(onTouch).toHaveBeenLastCalledWith(100, 300, \"release\");\n });\n\n it(\"says so when the screenshot cannot be decoded\", () => {\n render(onTouch);\n\n act(() => {\n image().dispatchEvent(new Event(\"error\"));\n });\n\n expect(\n container.querySelector('[data-testid=\"text_device-screen-undecodable\"]'),\n ).not.toBeNull();\n });\n});\n"],
5
+ "mappings": "wdAAA,IAAAA,EAA2B,oBAC3BC,EAAsC,4BACtCC,EAAkC,kDAElCC,EAAkC,+BAElC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,SAAS,oBAAqB,IAAM,CAClC,IAAIC,EACAC,EACJ,MAAMC,EAAU,GAAG,GAAG,EAEhBC,EAAQ,IACZH,EAAU,cACR,qCACF,EACII,EAAUC,MACd,OAAI,IAAMJ,EAAK,OAAO,EAAAK,QAAA,cAAC,qBAAkB,IAAI,SAAS,QAASD,EAAO,CAAE,CAAC,EACrEE,EAAU,CAACC,EAAcC,EAAuB,CAAC,OACrD,OAAI,IAAM,CACRN,EAAM,EAAE,cACN,IAAI,WAAWK,EAAM,CAAE,QAAS,GAAM,OAAQ,EAAG,GAAGC,CAAK,CAAC,CAC5D,CACF,CAAC,EAEGC,EAAY,IAAM,CACtB,OAAO,eAAeP,EAAM,EAAG,eAAgB,CAAE,MAAO,GAAI,CAAC,EAC7D,OAAO,eAAeA,EAAM,EAAG,gBAAiB,CAAE,MAAO,GAAI,CAAC,EAC9DA,EAAM,EAAE,sBAAwB,KAC7B,CAAE,KAAM,EAAG,IAAK,EAAG,MAAO,IAAK,OAAQ,GAAI,EAChD,EAEA,WAAW,IAAM,CACf,GAAG,cAAc,EACjBD,EAAQ,UAAU,EAClB,YAAY,UAAU,kBAAoB,GAAG,GAAG,EAChD,YAAY,UAAU,sBAAwB,GAAG,GAAG,EACpDF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,KAAO,cAAWD,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,IACd,OAAI,IAAMC,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,EACjB,GAAG,cAAc,CACnB,CAAC,EAED,GAAG,8BAA+B,IAAM,CACtCI,EAAOF,CAAO,EACdQ,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,EACpDA,EAAQ,YAAa,CAAE,QAAS,IAAK,QAAS,EAAG,CAAC,EAElD,OAAOL,CAAO,EAAE,gCACd,IACA,IACA,mBACF,CACF,CAAC,EAED,GAAG,qCAAsC,IAAM,CAC7CE,EAAOF,CAAO,EACdQ,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,KACpD,OAAI,IAAM,CACR,GAAG,oBAAoB,mBAAiB,CAC1C,CAAC,EACDA,EAAQ,YAAa,CAAE,QAAS,IAAK,QAAS,EAAG,CAAC,EAElD,OAAOL,EAAQ,KAAK,KAAK,EAAE,QAAQ,CACjC,CAAC,IAAK,IAAK,OAAO,EAClB,CAAC,IAAK,IAAK,SAAS,CACtB,CAAC,CACH,CAAC,EAED,GAAG,mDAAoD,IAAM,CAC3DE,EAAOF,CAAO,EACdQ,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,IAAK,QAAS,GAAI,CAAC,EACrDA,EAAQ,WAAW,EAEnB,OAAOL,CAAO,EAAE,qBAAqB,IAAK,IAAK,mBAAmB,CACpE,CAAC,EAED,GAAG,8CAA+C,IAAM,CACtDE,EAAOF,CAAO,EAEdK,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EACnDA,EAAQ,WAAW,EAEnB,OAAOL,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,4CAA6C,IAAM,CACpDE,EAAO,EACPM,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EAEnD,OAAOL,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,uCAAwC,IAAM,CAC/CE,EAAOF,CAAO,EACdQ,EAAU,EACVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,KACpD,OAAI,IAAM,CACR,GAAG,oBAAoB,mBAAiB,CAC1C,CAAC,KAED,OAAI,IAAMN,EAAK,OAAO,EAAAK,QAAA,gBAAAA,QAAA,aAAE,CAAG,CAAC,EAE5B,OAAOJ,CAAO,EAAE,yBAAyB,IAAK,IAAK,SAAS,CAC9D,CAAC,EAED,GAAG,gDAAiD,IAAM,CACxDE,EAAOF,CAAO,KAEd,OAAI,IAAM,CACRC,EAAM,EAAE,cAAc,IAAI,MAAM,OAAO,CAAC,CAC1C,CAAC,EAED,OACEH,EAAU,cAAc,gDAAgD,CAC1E,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
6
+ "names": ["import_react", "import_client", "import_device_mockserver_react_core", "import_DeviceScreenImage", "container", "root", "onTouch", "image", "render", "touch", "React", "pointer", "type", "init", "sizeImage"]
7
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var l=Object.create;var n=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var d=(e,r)=>{for(var o in r)n(e,o,{get:r[o],enumerable:!0})},s=(e,r,o,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of u(r))!M.call(e,i)&&i!==o&&n(e,i,{get:()=>r[i],enumerable:!(t=k(r,i))||t.enumerable});return e};var P=(e,r,o)=>(o=e!=null?l(D(e)):{},s(r||!e||!e.__esModule?n(o,"default",{value:e,enumerable:!0}):o,e)),g=e=>s(n({},"__esModule",{value:!0}),e);var w={};d(w,{MockServerDevice:()=>y});module.exports=g(w);var c=P(require("react")),v=require("@ledgerhq/device-mockserver-client"),m=require("./DeviceScreen"),f=require("./mockServerScreenApi"),S=require("./useMockServerDevice");const y=({url:e,token:r,deviceId:o,...t})=>{const i=(0,c.useMemo)(()=>new v.MockClient(e,{token:r}),[e,r]),a=(0,c.useMemo)(()=>(0,f.mockServerScreenApi)(i,o),[i,o]),p=(0,S.useMockServerDevice)(i,o);return p===void 0?null:c.default.createElement(m.DeviceScreen,{api:a,deviceType:p?.device_type??"",firmwareVersion:p?.firmware_version,...t})};0&&(module.exports={MockServerDevice});
1
+ "use strict";var a=Object.create;var n=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var u=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var M=(e,r)=>{for(var i in r)n(e,i,{get:r[i],enumerable:!0})},v=(e,r,i,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of k(r))!D.call(e,o)&&o!==i&&n(e,o,{get:()=>r[o],enumerable:!(t=l(r,o))||t.enumerable});return e};var d=(e,r,i)=>(i=e!=null?a(u(e)):{},v(r||!e||!e.__esModule?n(i,"default",{value:e,enumerable:!0}):i,e)),P=e=>v(n({},"__esModule",{value:!0}),e);var y={};M(y,{MockServerDevice:()=>g});module.exports=P(y);var c=d(require("react")),m=require("@ledgerhq/device-mockserver-client"),p=require("@ledgerhq/device-mockserver-react-core"),f=require("./DeviceScreen");const g=({url:e,token:r,deviceId:i,...t})=>{const o=(0,c.useMemo)(()=>new m.MockClient(e,{token:r}),[e,r]),S=(0,c.useMemo)(()=>(0,p.mockServerScreenApi)(o,i),[o,i]),s=(0,p.useMockServerDevice)(o,i);return s===void 0?null:c.default.createElement(f.DeviceScreen,{api:S,deviceType:s?.device_type??"",firmwareVersion:s?.firmware_version,...t})};0&&(module.exports={MockServerDevice});
2
2
  //# sourceMappingURL=MockServerDevice.js.map