@shopify/cli-kit 3.44.1 → 3.45.0-pre.1

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 (93) hide show
  1. package/README.md +15 -7
  2. package/assets/cli-ruby/lib/project_types/extension/messages/messages.rb +0 -2
  3. package/assets/cli-ruby/lib/project_types/theme/commands/pull.rb +6 -0
  4. package/assets/cli-ruby/lib/project_types/theme/commands/push.rb +6 -0
  5. package/assets/cli-ruby/lib/shopify_cli/constants.rb +1 -0
  6. package/assets/cli-ruby/lib/shopify_cli/environment.rb +4 -0
  7. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/proxy.rb +3 -0
  8. package/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +3 -3
  9. package/assets/cli-ruby/lib/shopify_cli/theme/extension/host_theme.rb +4 -4
  10. package/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer/extension_serve_job.rb +4 -6
  11. package/assets/cli-ruby/lib/shopify_cli/theme/extension/ui/host_theme_raw_progress_bar.rb +40 -0
  12. package/assets/cli-ruby/vendor/deps/cli-ui/lib/cli/ui/progress_plain.rb +50 -0
  13. package/assets/cli-ruby/vendor/deps/cli-ui/lib/cli/ui.rb +15 -14
  14. package/dist/private/node/api/graphql.js +12 -23
  15. package/dist/private/node/api/graphql.js.map +1 -1
  16. package/dist/private/node/api.d.ts +1 -1
  17. package/dist/private/node/api.js +28 -9
  18. package/dist/private/node/api.js.map +1 -1
  19. package/dist/private/node/constants.d.ts +0 -1
  20. package/dist/private/node/constants.js +0 -1
  21. package/dist/private/node/constants.js.map +1 -1
  22. package/dist/private/node/testing/ui.d.ts +11 -0
  23. package/dist/private/node/testing/ui.js +15 -1
  24. package/dist/private/node/testing/ui.js.map +1 -1
  25. package/dist/private/node/ui/alert.d.ts +5 -1
  26. package/dist/private/node/ui/alert.js +2 -2
  27. package/dist/private/node/ui/alert.js.map +1 -1
  28. package/dist/private/node/ui/components/AutocompletePrompt.js +38 -12
  29. package/dist/private/node/ui/components/AutocompletePrompt.js.map +1 -1
  30. package/dist/private/node/ui/components/AutocompletePrompt.test.js +56 -36
  31. package/dist/private/node/ui/components/AutocompletePrompt.test.js.map +1 -1
  32. package/dist/private/node/ui/components/ConcurrentOutput.d.ts +5 -1
  33. package/dist/private/node/ui/components/ConcurrentOutput.js +15 -13
  34. package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
  35. package/dist/private/node/ui/components/ConcurrentOutput.test.js +20 -11
  36. package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -1
  37. package/dist/private/node/ui/components/FullScreen.js +1 -1
  38. package/dist/private/node/ui/components/FullScreen.js.map +1 -1
  39. package/dist/private/node/ui/components/SelectInput.d.ts +7 -2
  40. package/dist/private/node/ui/components/SelectInput.js +73 -60
  41. package/dist/private/node/ui/components/SelectInput.js.map +1 -1
  42. package/dist/private/node/ui/components/SelectInput.test.js +72 -2
  43. package/dist/private/node/ui/components/SelectInput.test.js.map +1 -1
  44. package/dist/private/node/ui/components/SelectPrompt.js +38 -12
  45. package/dist/private/node/ui/components/SelectPrompt.js.map +1 -1
  46. package/dist/private/node/ui/components/SelectPrompt.test.js +52 -1
  47. package/dist/private/node/ui/components/SelectPrompt.test.js.map +1 -1
  48. package/dist/private/node/ui/components/Tasks.js +9 -1
  49. package/dist/private/node/ui/components/Tasks.js.map +1 -1
  50. package/dist/private/node/ui/components/TextAnimation.js +4 -4
  51. package/dist/private/node/ui/components/TextAnimation.js.map +1 -1
  52. package/dist/private/node/ui/components/TextPrompt.js +4 -4
  53. package/dist/private/node/ui/components/TextPrompt.js.map +1 -1
  54. package/dist/private/node/ui/hooks/use-layout.d.ts +6 -0
  55. package/dist/private/node/ui/hooks/use-layout.js +31 -12
  56. package/dist/private/node/ui/hooks/use-layout.js.map +1 -1
  57. package/dist/private/node/ui.d.ts +11 -3
  58. package/dist/private/node/ui.js +14 -10
  59. package/dist/private/node/ui.js.map +1 -1
  60. package/dist/public/common/object.d.ts +1 -1
  61. package/dist/public/common/object.js +1 -1
  62. package/dist/public/common/object.js.map +1 -1
  63. package/dist/public/common/version.d.ts +1 -1
  64. package/dist/public/common/version.js +1 -1
  65. package/dist/public/common/version.js.map +1 -1
  66. package/dist/public/node/base-command.d.ts +1 -4
  67. package/dist/public/node/base-command.js +17 -19
  68. package/dist/public/node/base-command.js.map +1 -1
  69. package/dist/public/node/context/local.d.ts +0 -7
  70. package/dist/public/node/context/local.js +0 -9
  71. package/dist/public/node/context/local.js.map +1 -1
  72. package/dist/public/node/environments.d.ts +7 -8
  73. package/dist/public/node/environments.js +23 -25
  74. package/dist/public/node/environments.js.map +1 -1
  75. package/dist/public/node/node-package-manager.d.ts +1 -1
  76. package/dist/public/node/node-package-manager.js.map +1 -1
  77. package/dist/public/node/output.d.ts +1 -1
  78. package/dist/public/node/output.js.map +1 -1
  79. package/dist/public/node/path.js +1 -1
  80. package/dist/public/node/path.js.map +1 -1
  81. package/dist/public/node/ruby.d.ts +1 -0
  82. package/dist/public/node/ruby.js +35 -15
  83. package/dist/public/node/ruby.js.map +1 -1
  84. package/dist/public/node/themes/theme-manager.d.ts +1 -1
  85. package/dist/public/node/themes/theme-manager.js.map +1 -1
  86. package/dist/public/node/ui.d.ts +182 -121
  87. package/dist/public/node/ui.js +172 -120
  88. package/dist/public/node/ui.js.map +1 -1
  89. package/dist/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +20 -18
  91. package/dist/private/node/ui/components/TextWithBackground.d.ts +0 -12
  92. package/dist/private/node/ui/components/TextWithBackground.js +0 -39
  93. package/dist/private/node/ui/components/TextWithBackground.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ConcurrentOutput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,yBAAyB,CAAA;AAE1D,OAAO,kBAAkB,MAAM,mCAAmC,CAAA;AAElE,OAAO,EAAC,WAAW,EAAC,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,EAAoB,WAAW,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AACrE,OAAO,EAAC,GAAG,EAAO,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAC,MAAM,KAAK,CAAA;AACpD,OAAO,SAAS,MAAM,YAAY,CAAA;AAClC,OAAO,QAAQ,MAAM,WAAW,CAAA;AAChC,OAAO,EAAC,QAAQ,EAAC,MAAM,QAAQ,CAAA;AAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,gBAAgB,GAA6C,CAAC,EAClE,SAAS,EACT,eAAe,EACf,cAAc,GAAG,IAAI,EACrB,OAAO,EACP,MAAM,GACP,EAAE,EAAE;IACH,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAU,EAAE,CAAC,CAAA;IAC/D,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;IACvE,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IAEvF,SAAS,SAAS,CAAC,KAAa;QAC9B,MAAM,UAAU,GAAG,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAA;QAC5F,OAAO,gBAAgB,CAAC,UAAU,CAAE,CAAA;IACtC,CAAC;IAED,MAAM,cAAc,GAAG,CAAC,OAAsB,EAAE,KAAa,EAAE,EAAE;QAC/D,OAAO,IAAI,QAAQ,CAAC;YAClB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI;gBAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBAEjF,gBAAgB,CAAC,CAAC,qBAAqB,EAAE,EAAE,CAAC;oBAC1C,GAAG,qBAAqB;oBACxB;wBACE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;wBACvB,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,KAAK;qBACN;iBACF,CAAC,CAAA;gBAEF,IAAI,EAAE,CAAA;YACR,CAAC;SACF,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,OAAO,OAAO,CAAC,GAAG,CAChB,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAE7C,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;QAC9D,CAAC,CAAC,CACH,CAAA;IACH,CAAC,CAAA;IAED,IAAI,OAAO,EAAE;QACX,QAAQ,CACN,WAAW,CACT,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACb,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YACvB,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC5D,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CACF,CAAA;KACF;IAED,kBAAkB,CAAC,YAAY,EAAE,EAAC,UAAU,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,EAAC,CAAC,CAAA;IAE7E,OAAO,CACL;QACE,oBAAC,MAAM,IAAC,KAAK,EAAE,aAAa,IACzB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAChB,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,KAAK,IACnC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK,EAAE,aAAa,EAAC,KAAK;gBACjC,cAAc,CAAC,CAAC,CAAC,CAChB,oBAAC,GAAG;oBACF,oBAAC,GAAG,IAAC,WAAW,EAAE,CAAC;wBACjB,oBAAC,IAAI,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IACrB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAC1D,CACH;oBAEN,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,KAAK,CAAC,KAAK,QAEtB,CACH,CACP,CAAC,CAAC,CAAC,IAAI;gBAER,oBAAC,GAAG,IAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;oBACtC,oBAAC,IAAI,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAG,KAAK,CAAC,MAAM,CAAQ,CAC3C;gBAEN,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,KAAK,CAAC,KAAK,QAEtB;gBAEP,oBAAC,GAAG,IAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;oBAC9B,oBAAC,IAAI,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAG,IAAI,CAAQ,CACnC,CACF,CACP,CAAC,CACE,CACP,CAAA;QACH,CAAC,CACM;QACR,MAAM,CAAC,CAAC,CAAC,CACR,oBAAC,GAAG,IAAC,OAAO,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ;YACrC,oBAAC,GAAG,IAAC,QAAQ,EAAE,CAAC;gBACd,oBAAC,kBAAkB,IAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,QAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAI,CACxE;YACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CACjB,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;gBAC5B,oBAAC,IAAI,QAAE,MAAM,CAAC,QAAQ,CAAQ,CAC1B,CACP,CAAC,CAAC,CAAC,IAAI,CACJ,CACP,CAAC,CAAC,CAAC,IAAI,CACP,CACJ,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,gBAAgB,EAAC,CAAA","sourcesContent":["import {TextWithBackground} from './TextWithBackground.js'\nimport {OutputProcess} from '../../../../public/node/output.js'\nimport useAsyncAndUnmount from '../hooks/use-async-and-unmount.js'\nimport {AbortController} from '../../../../public/node/abort.js'\nimport {handleCtrlC} from '../../ui.js'\nimport React, {FunctionComponent, useCallback, useState} from 'react'\nimport {Box, Key, Static, Text, useInput} from 'ink'\nimport stripAnsi from 'strip-ansi'\nimport treeKill from 'tree-kill'\nimport {Writable} from 'stream'\n\nexport type WritableStream = (process: OutputProcess, index: number) => Writable\n\nexport interface ConcurrentOutputProps {\n processes: OutputProcess[]\n abortController: AbortController\n showTimestamps?: boolean\n onInput?: (input: string, key: Key, exit: () => void) => void\n footer?: {\n title: string\n subTitle?: string\n }\n}\ninterface Chunk {\n color: string\n prefix: string\n lines: string[]\n}\n\n/**\n * Renders output from concurrent processes to the terminal.\n * Output will be divided in a three column layout\n * with the left column containing the timestamp,\n * the right column containing the output,\n * and the middle column containing the process prefix.\n * Every process will be rendered with a different color, up to 4 colors.\n *\n * For example running `shopify app dev`:\n *\n * ```shell\n * 2022-10-10 13:11:03 | backend | npm\n * 2022-10-10 13:11:03 | backend | WARN ignoring workspace config at ...\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | > shopify-app-template-node@0.1.0 dev\n * 2022-10-10 13:11:03 | backend | > cross-env NODE_ENV=development nodemon backend/index.js --watch ./backend\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend | > starter-react-frontend-app@0.1.0 dev\n * 2022-10-10 13:11:03 | frontend | > cross-env NODE_ENV=development node vite-server.js\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | [nodemon] to restart at any time, enter `rs`\n * 2022-10-10 13:11:03 | backend | [nodemon] watching path(s): backend/\n * 2022-10-10 13:11:03 | backend | [nodemon] watching extensions: js,mjs,json\n * 2022-10-10 13:11:03 | backend | [nodemon] starting `node backend/index.js`\n * 2022-10-10 13:11:03 | backend |\n *\n * ```\n */\nconst ConcurrentOutput: FunctionComponent<ConcurrentOutputProps> = ({\n processes,\n abortController,\n showTimestamps = true,\n onInput,\n footer,\n}) => {\n const [processOutput, setProcessOutput] = useState<Chunk[]>([])\n const concurrentColors = ['yellow', 'cyan', 'magenta', 'green', 'blue']\n const prefixColumnSize = Math.max(...processes.map((process) => process.prefix.length))\n\n function lineColor(index: number) {\n const colorIndex = index < concurrentColors.length ? index : index % concurrentColors.length\n return concurrentColors[colorIndex]!\n }\n\n const writableStream = (process: OutputProcess, index: number) => {\n return new Writable({\n write(chunk, _encoding, next) {\n const lines = stripAnsi(chunk.toString('ascii').replace(/(\\n)$/, '')).split(/\\n/)\n\n setProcessOutput((previousProcessOutput) => [\n ...previousProcessOutput,\n {\n color: lineColor(index),\n prefix: process.prefix,\n lines,\n },\n ])\n\n next()\n },\n })\n }\n\n const runProcesses = () => {\n return Promise.all(\n processes.map(async (process, index) => {\n const stdout = writableStream(process, index)\n const stderr = writableStream(process, index)\n\n await process.action(stdout, stderr, abortController.signal)\n }),\n )\n }\n\n if (onInput) {\n useInput(\n useCallback(\n (input, key) => {\n handleCtrlC(input, key)\n onInput(input, key, () => treeKill(process.pid, 'SIGINT'))\n },\n [onInput],\n ),\n )\n }\n\n useAsyncAndUnmount(runProcesses, {onRejected: () => abortController.abort()})\n\n return (\n <>\n <Static items={processOutput}>\n {(chunk, index) => {\n return (\n <Box flexDirection=\"column\" key={index}>\n {chunk.lines.map((line, index) => (\n <Box key={index} flexDirection=\"row\">\n {showTimestamps ? (\n <Box>\n <Box marginRight={1}>\n <Text color={chunk.color}>\n {new Date().toISOString().replace(/T/, ' ').replace(/\\..+/, '')}\n </Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n </Box>\n ) : null}\n\n <Box width={prefixColumnSize} marginX={1}>\n <Text color={chunk.color}>{chunk.prefix}</Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n\n <Box flexGrow={1} paddingLeft={1}>\n <Text color={chunk.color}>{line}</Text>\n </Box>\n </Box>\n ))}\n </Box>\n )\n }}\n </Static>\n {footer ? (\n <Box marginY={1} flexDirection=\"column\">\n <Box flexGrow={1}>\n <TextWithBackground text={footer.title} inverse paddingX={2} paddingY={1} />\n </Box>\n {footer.subTitle ? (\n <Box marginTop={1} flexGrow={1}>\n <Text>{footer.subTitle}</Text>\n </Box>\n ) : null}\n </Box>\n ) : null}\n </>\n )\n}\n\nexport {ConcurrentOutput}\n"]}
1
+ {"version":3,"file":"ConcurrentOutput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {OutputProcess} from '../../../../public/node/output.js'\nimport useAsyncAndUnmount from '../hooks/use-async-and-unmount.js'\nimport {AbortController} from '../../../../public/node/abort.js'\nimport {handleCtrlC} from '../../ui.js'\nimport React, {FunctionComponent, useState} from 'react'\nimport {Box, Key, Static, Text, useInput} from 'ink'\nimport stripAnsi from 'strip-ansi'\nimport treeKill from 'tree-kill'\nimport figures from 'figures'\nimport {Writable} from 'stream'\n\nexport type WritableStream = (process: OutputProcess, index: number) => Writable\n\ninterface Shortcut {\n key: string\n action: string\n}\nexport interface ConcurrentOutputProps {\n processes: OutputProcess[]\n abortController: AbortController\n showTimestamps?: boolean\n onInput?: (input: string, key: Key, exit: () => void) => void\n footer?: {\n shortcuts: Shortcut[]\n subTitle?: string\n }\n}\ninterface Chunk {\n color: string\n prefix: string\n lines: string[]\n}\n\n/**\n * Renders output from concurrent processes to the terminal.\n * Output will be divided in a three column layout\n * with the left column containing the timestamp,\n * the right column containing the output,\n * and the middle column containing the process prefix.\n * Every process will be rendered with a different color, up to 4 colors.\n *\n * For example running `shopify app dev`:\n *\n * ```shell\n * 2022-10-10 13:11:03 | backend | npm\n * 2022-10-10 13:11:03 | backend | WARN ignoring workspace config at ...\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | > shopify-app-template-node@0.1.0 dev\n * 2022-10-10 13:11:03 | backend | > cross-env NODE_ENV=development nodemon backend/index.js --watch ./backend\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend | > starter-react-frontend-app@0.1.0 dev\n * 2022-10-10 13:11:03 | frontend | > cross-env NODE_ENV=development node vite-server.js\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | [nodemon] to restart at any time, enter `rs`\n * 2022-10-10 13:11:03 | backend | [nodemon] watching path(s): backend/\n * 2022-10-10 13:11:03 | backend | [nodemon] watching extensions: js,mjs,json\n * 2022-10-10 13:11:03 | backend | [nodemon] starting `node backend/index.js`\n * 2022-10-10 13:11:03 | backend |\n *\n * ```\n */\nconst ConcurrentOutput: FunctionComponent<ConcurrentOutputProps> = ({\n processes,\n abortController,\n showTimestamps = true,\n onInput,\n footer,\n}) => {\n const [processOutput, setProcessOutput] = useState<Chunk[]>([])\n const concurrentColors = ['yellow', 'cyan', 'magenta', 'green', 'blue']\n const prefixColumnSize = Math.max(...processes.map((process) => process.prefix.length))\n\n function lineColor(index: number) {\n const colorIndex = index < concurrentColors.length ? index : index % concurrentColors.length\n return concurrentColors[colorIndex]!\n }\n\n const writableStream = (process: OutputProcess, index: number) => {\n return new Writable({\n write(chunk, _encoding, next) {\n const lines = stripAnsi(chunk.toString('utf8').replace(/(\\n)$/, '')).split(/\\n/)\n\n setProcessOutput((previousProcessOutput) => [\n ...previousProcessOutput,\n {\n color: lineColor(index),\n prefix: process.prefix,\n lines,\n },\n ])\n\n next()\n },\n })\n }\n\n const runProcesses = () => {\n return Promise.all(\n processes.map(async (process, index) => {\n const stdout = writableStream(process, index)\n const stderr = writableStream(process, index)\n\n await process.action(stdout, stderr, abortController.signal)\n }),\n )\n }\n\n useInput(\n (input, key) => {\n handleCtrlC(input, key)\n\n onInput!(input, key, () => treeKill(process.pid, 'SIGINT'))\n },\n {isActive: typeof onInput !== 'undefined'},\n )\n\n useAsyncAndUnmount(runProcesses, {onRejected: () => abortController.abort()})\n\n return (\n <>\n <Static items={processOutput}>\n {(chunk, index) => {\n return (\n <Box flexDirection=\"column\" key={index}>\n {chunk.lines.map((line, index) => (\n <Box key={index} flexDirection=\"row\">\n {showTimestamps ? (\n <Box>\n <Box marginRight={1}>\n <Text color={chunk.color}>\n {new Date().toISOString().replace(/T/, ' ').replace(/\\..+/, '')}\n </Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n </Box>\n ) : null}\n\n <Box width={prefixColumnSize} marginX={1}>\n <Text color={chunk.color}>{chunk.prefix}</Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n\n <Box flexGrow={1} paddingLeft={1}>\n <Text color={chunk.color}>{line}</Text>\n </Box>\n </Box>\n ))}\n </Box>\n )\n }}\n </Static>\n {footer ? (\n <Box marginY={1} flexDirection=\"column\" flexGrow={1}>\n <Box flexDirection=\"column\">\n {footer.shortcuts.map((shortcut, index) => (\n <Text key={index}>\n {figures.pointerSmall} Press <Text bold>{shortcut.key}</Text> | {shortcut.action}\n </Text>\n ))}\n </Box>\n {footer.subTitle ? (\n <Box marginTop={1}>\n <Text>{footer.subTitle}</Text>\n </Box>\n ) : null}\n </Box>\n ) : null}\n </>\n )\n}\n\nexport {ConcurrentOutput}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { ConcurrentOutput } from './ConcurrentOutput.js';
2
2
  import { getLastFrameAfterUnmount, waitForInputsToBeReady } from '../../testing/ui.js';
3
3
  import { AbortController } from '../../../../public/node/abort.js';
4
+ import { unstyled } from '../../../../public/node/output.js';
4
5
  import React from 'react';
5
6
  import { describe, expect, test, vi } from 'vitest';
6
7
  import { render } from 'ink-testing-library';
@@ -36,23 +37,31 @@ describe('ConcurrentOutput', () => {
36
37
  };
37
38
  // When
38
39
  const renderInstance = render(React.createElement(ConcurrentOutput, { processes: [backendProcess, frontendProcess], abortController: new AbortController(), footer: {
39
- title: 'Press `p` to open your browser. Press `q` to quit.',
40
+ shortcuts: [
41
+ {
42
+ key: 'p',
43
+ action: 'open your browser',
44
+ },
45
+ {
46
+ key: 'q',
47
+ action: 'quit',
48
+ },
49
+ ],
40
50
  subTitle: `Preview URL: https://shopify.com`,
41
51
  } }));
42
52
  // wait for all output to be rendered
43
53
  await frontendPromise;
44
54
  // Then
45
- expect(getLastFrameAfterUnmount(renderInstance).replace(/\d/g, '0')).toMatchInlineSnapshot(`
46
- "0000-00-00 00:00:00 | backend | first backend message
47
- 0000-00-00 00:00:00 | backend | second backend message
48
- 0000-00-00 00:00:00 | backend | third backend message
49
- 0000-00-00 00:00:00 | frontend | first frontend message
50
- 0000-00-00 00:00:00 | frontend | second frontend message
51
- 0000-00-00 00:00:00 | frontend | third frontend message
55
+ expect(unstyled(getLastFrameAfterUnmount(renderInstance).replace(/\d/g, '0'))).toMatchInlineSnapshot(`
56
+ "0000-00-00 00:00:00 | backend | first backend message
57
+ 0000-00-00 00:00:00 | backend | second backend message
58
+ 0000-00-00 00:00:00 | backend | third backend message
59
+ 0000-00-00 00:00:00 | frontend | first frontend message
60
+ 0000-00-00 00:00:00 | frontend | second frontend message
61
+ 0000-00-00 00:00:00 | frontend | third frontend message
52
62
 
53
-  
54
-  Press \`p\` to open your browser. Press \`q\` to quit. 
55
-  
63
+ › Press p | open your browser
64
+ Press q | quit
56
65
 
57
66
  Preview URL: https://shopify.com
58
67
  "
@@ -1 +1 @@
1
- {"version":3,"file":"ConcurrentOutput.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ConcurrentOutput} from './ConcurrentOutput.js'\nimport {getLastFrameAfterUnmount, waitForInputsToBeReady} from '../../testing/ui.js'\nimport {AbortController, AbortSignal} from '../../../../public/node/abort.js'\nimport React from 'react'\nimport {describe, expect, test, vi} from 'vitest'\nimport {render} from 'ink-testing-library'\nimport {Writable} from 'stream'\n\ndescribe('ConcurrentOutput', () => {\n test('renders a stream of concurrent outputs from sub-processes', async () => {\n // Given\n let backendPromiseResolve: () => void\n let frontendPromiseResolve: () => void\n\n const backendPromise = new Promise<void>(function (resolve, _reject) {\n backendPromiseResolve = resolve\n })\n\n const frontendPromise = new Promise<void>(function (resolve, _reject) {\n frontendPromiseResolve = resolve\n })\n\n const backendProcess = {\n prefix: 'backend',\n action: async (stdout: Writable, _stderr: Writable, _signal: AbortSignal) => {\n stdout.write('first backend message')\n stdout.write('second backend message')\n stdout.write('third backend message')\n\n backendPromiseResolve()\n },\n }\n\n const frontendProcess = {\n prefix: 'frontend',\n action: async (stdout: Writable, _stderr: Writable, _signal: AbortSignal) => {\n await backendPromise\n\n stdout.write('first frontend message')\n stdout.write('second frontend message')\n stdout.write('third frontend message')\n\n frontendPromiseResolve()\n },\n }\n // When\n\n const renderInstance = render(\n <ConcurrentOutput\n processes={[backendProcess, frontendProcess]}\n abortController={new AbortController()}\n footer={{\n title: 'Press `p` to open your browser. Press `q` to quit.',\n subTitle: `Preview URL: https://shopify.com`,\n }}\n />,\n )\n\n // wait for all output to be rendered\n await frontendPromise\n\n // Then\n expect(getLastFrameAfterUnmount(renderInstance)!.replace(/\\d/g, '0')).toMatchInlineSnapshot(`\n \"\u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mbackend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mfirst backend message\u001b[00m\n \u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mbackend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00msecond backend message\u001b[00m\n \u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mbackend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mthird backend message\u001b[00m\n \u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mfrontend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mfirst frontend message\u001b[00m\n \u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mfrontend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00msecond frontend message\u001b[00m\n \u001b[00m0000-00-00 00:00:00\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mfrontend\u001b[00m \u001b[0m\u001b[00m|\u001b[00m\u001b[00m \u001b[00mthird frontend message\u001b[00m\n\n \u001b[0m \u001b[00m\n \u001b[0m Press \\`p\\` to open your browser. Press \\`q\\` to quit. \u001b[00m\n \u001b[0m \u001b[00m\n\n Preview URL: https://shopify.com\n \"\n `)\n })\n\n test('accepts a onInput function that fires when a key is pressed', async () => {\n const neverEndingPromise = new Promise<void>(function (_resolve, _reject) {})\n\n const neverEndingProcess = {\n prefix: 'never-ending-process',\n action: async () => {\n await neverEndingPromise\n },\n }\n\n const onInput = vi.fn()\n\n const renderInstance = render(\n <ConcurrentOutput\n processes={[neverEndingProcess]}\n abortController={new AbortController()}\n onInput={(input, key) => onInput(input, key)}\n />,\n )\n\n await waitForInputsToBeReady()\n expect(onInput).toHaveBeenCalledTimes(0)\n\n renderInstance.stdin.write('a')\n expect(onInput).toHaveBeenCalledTimes(1)\n expect(onInput.mock.calls[0]![0]).toBe('a')\n })\n})\n"]}
1
+ {"version":3,"file":"ConcurrentOutput.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ConcurrentOutput} from './ConcurrentOutput.js'\nimport {getLastFrameAfterUnmount, waitForInputsToBeReady} from '../../testing/ui.js'\nimport {AbortController, AbortSignal} from '../../../../public/node/abort.js'\nimport {unstyled} from '../../../../public/node/output.js'\nimport React from 'react'\nimport {describe, expect, test, vi} from 'vitest'\nimport {render} from 'ink-testing-library'\nimport {Writable} from 'stream'\n\ndescribe('ConcurrentOutput', () => {\n test('renders a stream of concurrent outputs from sub-processes', async () => {\n // Given\n let backendPromiseResolve: () => void\n let frontendPromiseResolve: () => void\n\n const backendPromise = new Promise<void>(function (resolve, _reject) {\n backendPromiseResolve = resolve\n })\n\n const frontendPromise = new Promise<void>(function (resolve, _reject) {\n frontendPromiseResolve = resolve\n })\n\n const backendProcess = {\n prefix: 'backend',\n action: async (stdout: Writable, _stderr: Writable, _signal: AbortSignal) => {\n stdout.write('first backend message')\n stdout.write('second backend message')\n stdout.write('third backend message')\n\n backendPromiseResolve()\n },\n }\n\n const frontendProcess = {\n prefix: 'frontend',\n action: async (stdout: Writable, _stderr: Writable, _signal: AbortSignal) => {\n await backendPromise\n\n stdout.write('first frontend message')\n stdout.write('second frontend message')\n stdout.write('third frontend message')\n\n frontendPromiseResolve()\n },\n }\n // When\n\n const renderInstance = render(\n <ConcurrentOutput\n processes={[backendProcess, frontendProcess]}\n abortController={new AbortController()}\n footer={{\n shortcuts: [\n {\n key: 'p',\n action: 'open your browser',\n },\n {\n key: 'q',\n action: 'quit',\n },\n ],\n subTitle: `Preview URL: https://shopify.com`,\n }}\n />,\n )\n\n // wait for all output to be rendered\n await frontendPromise\n\n // Then\n expect(unstyled(getLastFrameAfterUnmount(renderInstance)!.replace(/\\d/g, '0'))).toMatchInlineSnapshot(`\n \"0000-00-00 00:00:00 | backend | first backend message\n 0000-00-00 00:00:00 | backend | second backend message\n 0000-00-00 00:00:00 | backend | third backend message\n 0000-00-00 00:00:00 | frontend | first frontend message\n 0000-00-00 00:00:00 | frontend | second frontend message\n 0000-00-00 00:00:00 | frontend | third frontend message\n\n Press p | open your browser\n › Press q | quit\n\n Preview URL: https://shopify.com\n \"\n `)\n })\n\n test('accepts a onInput function that fires when a key is pressed', async () => {\n const neverEndingPromise = new Promise<void>(function (_resolve, _reject) {})\n\n const neverEndingProcess = {\n prefix: 'never-ending-process',\n action: async () => {\n await neverEndingPromise\n },\n }\n\n const onInput = vi.fn()\n\n const renderInstance = render(\n <ConcurrentOutput\n processes={[neverEndingProcess]}\n abortController={new AbortController()}\n onInput={(input, key) => onInput(input, key)}\n />,\n )\n\n await waitForInputsToBeReady()\n expect(onInput).toHaveBeenCalledTimes(0)\n\n renderInstance.stdin.write('a')\n expect(onInput).toHaveBeenCalledTimes(1)\n expect(onInput.mock.calls[0]![0]).toBe('a')\n })\n})\n"]}
@@ -27,7 +27,7 @@ const FullScreen = ({ children }) => {
27
27
  // switch back to the main buffer
28
28
  standardOutput.write('\u001B[?1049l');
29
29
  };
30
- }, []);
30
+ }, [standardOutput]);
31
31
  return (React.createElement(Box, { width: size.columns, height: size.rows }, children));
32
32
  };
33
33
  export { FullScreen };
@@ -1 +1 @@
1
- {"version":3,"file":"FullScreen.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/FullScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,KAAK,CAAA;AAClC,OAAO,KAAK,EAAE,EAAoB,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEnE;;;;GAIG;AACH,MAAM,UAAU,GAAsB,CAAC,EAAC,QAAQ,EAAC,EAAe,EAAE;IAChE,MAAM,EAAC,MAAM,EAAC,GAAG,SAAS,EAAE,CAAA;IAC5B,MAAM,cAAc,GAAG,MAAO,CAAA;IAE9B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;QAC/B,OAAO,EAAE,cAAc,CAAC,OAAO;QAC/B,IAAI,EAAE,cAAc,CAAC,IAAI;KAC1B,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,QAAQ;YACf,OAAO,CAAC;gBACN,OAAO,EAAE,cAAc,CAAC,OAAO;gBAC/B,IAAI,EAAE,cAAc,CAAC,IAAI;aAC1B,CAAC,CAAA;QACJ,CAAC;QAED,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACrC,gCAAgC;QAChC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACrC,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YACtC,iCAAiC;YACjC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACvC,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,oBAAC,GAAG,IAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,IACxC,QAAQ,CACL,CACP,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,UAAU,EAAC,CAAA","sourcesContent":["import {Box, useStdout} from 'ink'\nimport React, {FunctionComponent, useEffect, useState} from 'react'\n\n/**\n * `FullScreen` renders all output in a new buffer and makes it full screen. This is useful when:\n * - You want to preserve terminal history. `ink` [normally clears the terminal history](https://github.com/vadimdemedes/ink/issues/382) if the rendered output is taller than the terminal window. By rendering in a separate buffer history will be preserved and will be visible after pressing `Ctrl+C`.\n * - You want to respond to the resize event of the terminal. Whenever the user resizes their terminal window the output's height and width will be recalculated and re-rendered properly.\n */\nconst FullScreen: FunctionComponent = ({children}): JSX.Element => {\n const {stdout} = useStdout()\n const standardOutput = stdout!\n\n const [size, setSize] = useState({\n columns: standardOutput.columns,\n rows: standardOutput.rows,\n })\n\n useEffect(() => {\n function onResize() {\n setSize({\n columns: standardOutput.columns,\n rows: standardOutput.rows,\n })\n }\n\n standardOutput.on('resize', onResize)\n // switch to an alternate buffer\n standardOutput.write('\\u001B[?1049h')\n return () => {\n standardOutput.off('resize', onResize)\n // switch back to the main buffer\n standardOutput.write('\\u001B[?1049l')\n }\n }, [])\n\n return (\n <Box width={size.columns} height={size.rows}>\n {children}\n </Box>\n )\n}\n\nexport {FullScreen}\n"]}
1
+ {"version":3,"file":"FullScreen.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/FullScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,KAAK,CAAA;AAClC,OAAO,KAAK,EAAE,EAAoB,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEnE;;;;GAIG;AACH,MAAM,UAAU,GAAsB,CAAC,EAAC,QAAQ,EAAC,EAAe,EAAE;IAChE,MAAM,EAAC,MAAM,EAAC,GAAG,SAAS,EAAE,CAAA;IAC5B,MAAM,cAAc,GAAG,MAAO,CAAA;IAE9B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;QAC/B,OAAO,EAAE,cAAc,CAAC,OAAO;QAC/B,IAAI,EAAE,cAAc,CAAC,IAAI;KAC1B,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,QAAQ;YACf,OAAO,CAAC;gBACN,OAAO,EAAE,cAAc,CAAC,OAAO;gBAC/B,IAAI,EAAE,cAAc,CAAC,IAAI;aAC1B,CAAC,CAAA;QACJ,CAAC;QAED,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACrC,gCAAgC;QAChC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACrC,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YACtC,iCAAiC;YACjC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACvC,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAA;IAEpB,OAAO,CACL,oBAAC,GAAG,IAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,IACxC,QAAQ,CACL,CACP,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,UAAU,EAAC,CAAA","sourcesContent":["import {Box, useStdout} from 'ink'\nimport React, {FunctionComponent, useEffect, useState} from 'react'\n\n/**\n * `FullScreen` renders all output in a new buffer and makes it full screen. This is useful when:\n * - You want to preserve terminal history. `ink` [normally clears the terminal history](https://github.com/vadimdemedes/ink/issues/382) if the rendered output is taller than the terminal window. By rendering in a separate buffer history will be preserved and will be visible after pressing `Ctrl+C`.\n * - You want to respond to the resize event of the terminal. Whenever the user resizes their terminal window the output's height and width will be recalculated and re-rendered properly.\n */\nconst FullScreen: FunctionComponent = ({children}): JSX.Element => {\n const {stdout} = useStdout()\n const standardOutput = stdout!\n\n const [size, setSize] = useState({\n columns: standardOutput.columns,\n rows: standardOutput.rows,\n })\n\n useEffect(() => {\n function onResize() {\n setSize({\n columns: standardOutput.columns,\n rows: standardOutput.rows,\n })\n }\n\n standardOutput.on('resize', onResize)\n // switch to an alternate buffer\n standardOutput.write('\\u001B[?1049h')\n return () => {\n standardOutput.off('resize', onResize)\n // switch back to the main buffer\n standardOutput.write('\\u001B[?1049l')\n }\n }, [standardOutput])\n\n return (\n <Box width={size.columns} height={size.rows}>\n {children}\n </Box>\n )\n}\n\nexport {FullScreen}\n"]}
@@ -1,4 +1,8 @@
1
1
  import React from 'react';
2
+ import { DOMElement } from 'ink';
3
+ declare module 'react' {
4
+ function forwardRef<T, P>(render: (props: P, ref: React.Ref<T>) => JSX.Element | null): (props: P & React.RefAttributes<T>) => JSX.Element | null;
5
+ }
2
6
  interface OnChangeOptions<T> {
3
7
  item: Item<T> | undefined;
4
8
  usedShortcut: boolean;
@@ -16,6 +20,7 @@ export interface SelectInputProps<T> {
16
20
  hasMorePages?: boolean;
17
21
  morePagesMessage?: string;
18
22
  infoMessage?: string;
23
+ limit?: number;
19
24
  }
20
25
  export interface Item<T> {
21
26
  label: string;
@@ -23,5 +28,5 @@ export interface Item<T> {
23
28
  key?: string;
24
29
  group?: string;
25
30
  }
26
- declare function SelectInput<T>({ items, onChange, enableShortcuts, focus, emptyMessage, defaultValue, highlightedTerm, loading, errorMessage, hasMorePages, morePagesMessage, infoMessage, }: React.PropsWithChildren<SelectInputProps<T>>): JSX.Element | null;
27
- export { SelectInput };
31
+ export declare const SelectInput: <T>(props: SelectInputProps<T> & React.RefAttributes<DOMElement>) => JSX.Element | null;
32
+ export {};
@@ -1,11 +1,15 @@
1
1
  import { isEqual } from '../../../../public/common/lang.js';
2
- import { groupBy, partition } from '../../../../public/common/collection.js';
3
- import { mapValues } from '../../../../public/common/object.js';
4
- import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { debounce } from '../../../../public/common/function.js';
3
+ import React, { useState, useEffect, useRef, useCallback, forwardRef } from 'react';
5
4
  import { Box, useInput, Text } from 'ink';
6
- import { debounce } from '@shopify/cli-kit/common/function';
7
5
  import chalk from 'chalk';
8
6
  import figures from 'figures';
7
+ import { createRequire } from 'module';
8
+ const require = createRequire(import.meta.url);
9
+ function rotateArray(array, rotation) {
10
+ const arrayCopy = array.slice();
11
+ return arrayCopy.splice(-(rotation ?? 0) % arrayCopy.length).concat(arrayCopy);
12
+ }
9
13
  function highlightedLabel(label, term) {
10
14
  if (!term) {
11
15
  return label;
@@ -15,53 +19,48 @@ function highlightedLabel(label, term) {
15
19
  return chalk.bold(match);
16
20
  });
17
21
  }
18
- function groupItems(items) {
19
- let index = 0;
20
- const [withGroup, withoutGroup] = partition(items, 'group');
21
- const withGroupMapped = mapValues(groupBy(withGroup, 'group'), (groupItems) => groupItems.map((groupItem) => {
22
- const item = { ...groupItem, key: groupItem.key ?? (index + 1).toString(), index };
23
- index += 1;
24
- return item;
25
- }));
26
- const withoutGroupMapped = withoutGroup.map((item) => {
27
- const newItem = { ...item, key: item.key ?? (index + 1).toString(), index };
28
- index += 1;
29
- return newItem;
30
- });
31
- return [withGroupMapped, withoutGroupMapped];
32
- }
33
22
  // eslint-disable-next-line react/function-component-definition
34
- function SelectItemsGroup({ title, items, selectedIndex, hasMarginTop, enableShortcuts, highlightedTerm, }) {
35
- return (React.createElement(Box, { key: title, flexDirection: "column", marginTop: hasMarginTop ? 1 : 0 },
23
+ function Item({ item, previousItem, selectedIndex, highlightedTerm, enableShortcuts, items, hasAnyGroup, }) {
24
+ const isSelected = items.indexOf(item) === selectedIndex;
25
+ const label = highlightedLabel(item.label, highlightedTerm);
26
+ let title;
27
+ if (typeof previousItem === 'undefined' || item.group !== previousItem.group) {
28
+ title = item.group ?? (hasAnyGroup ? 'Other' : undefined);
29
+ }
30
+ return (React.createElement(Box, { key: item.key, flexDirection: "column", marginTop: items.indexOf(item) !== 0 && title ? 1 : 0 },
36
31
  title ? (React.createElement(Box, { marginLeft: 3 },
37
32
  React.createElement(Text, { bold: true }, title))) : null,
38
- items.map((item) => {
39
- const isSelected = item.index === selectedIndex;
40
- const label = highlightedLabel(item.label, highlightedTerm);
41
- return (React.createElement(Box, { key: item.key },
42
- React.createElement(Box, { marginRight: 2 }, isSelected ? React.createElement(Text, { color: "cyan" }, `>`) : React.createElement(Text, null, " ")),
43
- React.createElement(Text, { color: isSelected ? 'cyan' : undefined }, enableShortcuts ? `(${item.key}) ${label}` : label)));
44
- })));
33
+ React.createElement(Box, { key: item.key },
34
+ React.createElement(Box, { marginRight: 2 }, isSelected ? React.createElement(Text, { color: "cyan" }, `>`) : React.createElement(Text, null, " ")),
35
+ React.createElement(Text, { color: isSelected ? 'cyan' : undefined }, enableShortcuts ? `(${item.key}) ${label}` : label))));
45
36
  }
46
37
  // eslint-disable-next-line react/function-component-definition
47
- function SelectInput({ items, onChange, enableShortcuts = true, focus = true, emptyMessage = 'No items to select.', defaultValue, highlightedTerm, loading = false, errorMessage, hasMorePages = false, morePagesMessage, infoMessage, }) {
38
+ function SelectInputInner({ items: initialItems, onChange, enableShortcuts = true, focus = true, emptyMessage = 'No items to select.', defaultValue, highlightedTerm, loading = false, errorMessage, hasMorePages = false, morePagesMessage, infoMessage, limit, }, ref) {
39
+ const sortBy = require('lodash/sortBy');
40
+ const hasAnyGroup = initialItems.some((item) => typeof item.group !== 'undefined');
41
+ const items = sortBy(initialItems, 'group');
42
+ const itemsWithKeys = items.map((item, index) => ({
43
+ ...item,
44
+ key: item.key ?? (index + 1).toString(),
45
+ }));
48
46
  const defaultValueIndex = defaultValue ? items.findIndex((item) => item.value === defaultValue.value) : -1;
49
47
  const initialIndex = defaultValueIndex === -1 ? 0 : defaultValueIndex;
48
+ const hasLimit = typeof limit !== 'undefined' && items.length > limit;
50
49
  const inputStack = useRef(null);
51
50
  const [selectedIndex, setSelectedIndex] = useState(initialIndex);
52
- const [groupedItems, ungroupedItems] = groupItems(items);
53
- const groupedItemsValues = [...Object.values(groupedItems).flat(), ...ungroupedItems];
54
- const keys = groupedItemsValues.map((item) => item.key);
55
- const groupTitles = Object.keys(groupedItems);
56
- const previousItems = useRef(items);
57
- const changeSelection = useCallback(({ index, usedShortcut = false }) => {
58
- const groupedItem = groupedItemsValues.find((item) => item.index === index);
59
- setSelectedIndex(index);
51
+ const [rotateIndex, setRotateIndex] = useState(0);
52
+ const slicedItemsWithKeys = hasLimit ? rotateArray(itemsWithKeys, rotateIndex).slice(0, limit) : itemsWithKeys;
53
+ const previousItems = useRef(undefined);
54
+ const changeSelection = useCallback(({ newSelectedIndex, newRotateIndex, usedShortcut = false, }) => {
55
+ setSelectedIndex(newSelectedIndex);
56
+ if (typeof newRotateIndex !== 'undefined')
57
+ setRotateIndex(newRotateIndex);
58
+ const rotatedItems = hasLimit ? rotateArray(items, newRotateIndex).slice(0, limit) : items;
60
59
  onChange({
61
- item: items.find((item) => item.value === groupedItem.value),
60
+ item: rotatedItems[newSelectedIndex],
62
61
  usedShortcut,
63
62
  });
64
- }, [items]);
63
+ }, [hasLimit, items, limit, onChange]);
65
64
  useEffect(() => {
66
65
  if (items.length === 0) {
67
66
  // reset selection when items are empty
@@ -69,35 +68,50 @@ function SelectInput({ items, onChange, enableShortcuts = true, focus = true, em
69
68
  item: undefined,
70
69
  usedShortcut: false,
71
70
  });
71
+ // reset index when items change or the first time
72
+ }
73
+ else if (!previousItems.current) {
74
+ changeSelection({ newSelectedIndex: initialIndex, newRotateIndex: 0 });
72
75
  }
73
- else if (
74
- // reset index when items change
75
- !isEqual(previousItems.current.map((item) => item.value), items.map((item) => item.value))) {
76
- changeSelection({ index: 0 });
76
+ else if (!isEqual(previousItems.current.map((item) => item.value), items.map((item) => item.value))) {
77
+ changeSelection({ newSelectedIndex: 0, newRotateIndex: 0 });
77
78
  }
78
79
  previousItems.current = items;
79
- }, [items]);
80
- const handleArrows = useCallback((key) => {
81
- const lastIndex = items.length - 1;
80
+ }, [changeSelection, initialIndex, items, onChange]);
81
+ const handleArrows = (key) => {
82
82
  if (key.upArrow) {
83
- changeSelection({ index: selectedIndex === 0 ? lastIndex : selectedIndex - 1 });
83
+ const lastIndex = items.length - 1;
84
+ const atFirstIndex = selectedIndex === 0;
85
+ const nextIndex = hasLimit ? selectedIndex : lastIndex;
86
+ const nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex;
87
+ const nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1;
88
+ changeSelection({ newSelectedIndex: nextSelectedIndex, newRotateIndex: nextRotateIndex });
84
89
  }
85
90
  else if (key.downArrow) {
86
- changeSelection({ index: selectedIndex === lastIndex ? 0 : selectedIndex + 1 });
91
+ const atLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;
92
+ const nextIndex = hasLimit ? selectedIndex : 0;
93
+ const nextRotateIndex = atLastIndex ? rotateIndex - 1 : rotateIndex;
94
+ const nextSelectedIndex = atLastIndex ? nextIndex : selectedIndex + 1;
95
+ changeSelection({ newSelectedIndex: nextSelectedIndex, newRotateIndex: nextRotateIndex });
87
96
  }
88
- }, [selectedIndex, items]);
97
+ };
89
98
  const handleShortcuts = useCallback((input) => {
90
- if (keys.includes(input)) {
91
- const groupedItem = groupedItemsValues.find((item) => item.key === input);
92
- if (groupedItem !== undefined) {
93
- changeSelection({ index: groupedItem.index, usedShortcut: true });
99
+ if (slicedItemsWithKeys.map((item) => item.key).includes(input)) {
100
+ const itemWithKey = slicedItemsWithKeys.find((item) => item.key === input);
101
+ if (itemWithKey !== undefined) {
102
+ changeSelection({
103
+ newSelectedIndex: items.findIndex((item) => item.value === itemWithKey.value),
104
+ usedShortcut: true,
105
+ });
94
106
  }
95
107
  }
96
- }, [items]);
108
+ }, [changeSelection, items, slicedItemsWithKeys]);
109
+ // disable exhaustive-deps because we want to memoize the debounce function itself
110
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
111
  const debounceHandleShortcuts = useCallback(debounce((newInputStack) => {
98
112
  handleShortcuts(newInputStack);
99
113
  inputStack.current = null;
100
- }, 300), []);
114
+ }, 300), [handleShortcuts]);
101
115
  useInput((input, key) => {
102
116
  // check that no special modifier (shift, control, etc.) is being pressed
103
117
  if (enableShortcuts && input.length > 0 && Object.values(key).every((value) => value === false)) {
@@ -111,7 +125,6 @@ function SelectInput({ items, onChange, enableShortcuts = true, focus = true, em
111
125
  handleArrows(key);
112
126
  }
113
127
  }, { isActive: focus });
114
- const ungroupedItemsTitle = groupTitles.length > 0 ? 'Other' : undefined;
115
128
  if (loading) {
116
129
  return (React.createElement(Box, { marginLeft: 3 },
117
130
  React.createElement(Text, { dimColor: true }, "Loading...")));
@@ -125,9 +138,8 @@ function SelectInput({ items, onChange, enableShortcuts = true, focus = true, em
125
138
  React.createElement(Text, { dimColor: true }, emptyMessage)));
126
139
  }
127
140
  else {
128
- return (React.createElement(Box, { flexDirection: "column" },
129
- groupTitles.map((title, index) => (React.createElement(SelectItemsGroup, { title: title, selectedIndex: selectedIndex, items: groupedItems[title], key: title, hasMarginTop: index !== 0, enableShortcuts: enableShortcuts, highlightedTerm: highlightedTerm }))),
130
- ungroupedItems.length > 0 && (React.createElement(SelectItemsGroup, { title: ungroupedItemsTitle, selectedIndex: selectedIndex, items: ungroupedItems, hasMarginTop: groupTitles.length > 0, enableShortcuts: enableShortcuts, highlightedTerm: highlightedTerm })),
141
+ return (React.createElement(Box, { flexDirection: "column", ref: ref },
142
+ slicedItemsWithKeys.map((item, index) => (React.createElement(Item, { key: item.key, item: item, previousItem: slicedItemsWithKeys[index - 1], highlightedTerm: highlightedTerm, selectedIndex: selectedIndex, items: slicedItemsWithKeys, enableShortcuts: enableShortcuts, hasAnyGroup: hasAnyGroup }))),
131
143
  React.createElement(Box, { marginTop: 1, marginLeft: 3, flexDirection: "column" },
132
144
  hasMorePages ? (React.createElement(Text, null,
133
145
  React.createElement(Text, { bold: true },
@@ -135,10 +147,11 @@ function SelectInput({ items, onChange, enableShortcuts = true, focus = true, em
135
147
  items.length,
136
148
  " of many"),
137
149
  morePagesMessage ? ` ${morePagesMessage}` : null)) : null,
150
+ hasLimit ? React.createElement(Text, { dimColor: true }, `Showing ${limit} of ${items.length} items.`) : null,
138
151
  React.createElement(Text, { dimColor: true }, infoMessage
139
152
  ? infoMessage
140
153
  : `Press ${figures.arrowUp}${figures.arrowDown} arrows to select, enter to confirm`))));
141
154
  }
142
155
  }
143
- export { SelectInput };
156
+ export const SelectInput = forwardRef(SelectInputInner);
144
157
  //# sourceMappingURL=SelectInput.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SelectInput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SelectInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {isEqual} from '../../../../public/common/lang.js'\nimport {groupBy, partition} from '../../../../public/common/collection.js'\nimport {mapValues} from '../../../../public/common/object.js'\nimport React, {useState, useEffect, useRef, useCallback} from 'react'\nimport {Box, Key, useInput, Text} from 'ink'\nimport {debounce} from '@shopify/cli-kit/common/function'\nimport chalk from 'chalk'\nimport figures from 'figures'\n\ninterface OnChangeOptions<T> {\n item: Item<T> | undefined\n usedShortcut: boolean\n}\nexport interface SelectInputProps<T> {\n items: Item<T>[]\n onChange: ({item, usedShortcut}: OnChangeOptions<T>) => void\n enableShortcuts?: boolean\n focus?: boolean\n emptyMessage?: string\n defaultValue?: Item<T>\n highlightedTerm?: string\n loading?: boolean\n errorMessage?: string\n hasMorePages?: boolean\n morePagesMessage?: string\n infoMessage?: string\n}\n\nexport interface Item<T> {\n label: string\n value: T\n key?: string\n group?: string\n}\n\ninterface ItemWithIndex<T> extends Item<T> {\n key: string\n index: number\n}\n\nfunction highlightedLabel(label: string, term: string | undefined) {\n if (!term) {\n return label\n }\n\n const regex = new RegExp(term, 'i')\n return label.replace(regex, (match) => {\n return chalk.bold(match)\n })\n}\n\nfunction groupItems<T>(items: Item<T>[]): [{[key: string]: ItemWithIndex<T>[]}, ItemWithIndex<T>[]] {\n let index = 0\n\n const [withGroup, withoutGroup] = partition(items, 'group')\n\n const withGroupMapped = mapValues(groupBy(withGroup, 'group'), (groupItems) =>\n groupItems.map((groupItem) => {\n const item = {...groupItem, key: groupItem.key ?? (index + 1).toString(), index}\n index += 1\n return item\n }),\n )\n const withoutGroupMapped = withoutGroup.map((item) => {\n const newItem = {...item, key: item.key ?? (index + 1).toString(), index}\n index += 1\n return newItem\n })\n\n return [withGroupMapped, withoutGroupMapped]\n}\n\ninterface SelectItemsGroupProps<T> {\n title: string | undefined\n items: ItemWithIndex<T>[]\n selectedIndex: number\n hasMarginTop: boolean\n enableShortcuts: boolean\n highlightedTerm?: string\n}\n\n// eslint-disable-next-line react/function-component-definition\nfunction SelectItemsGroup<T>({\n title,\n items,\n selectedIndex,\n hasMarginTop,\n enableShortcuts,\n highlightedTerm,\n}: SelectItemsGroupProps<T>): JSX.Element {\n return (\n <Box key={title} flexDirection=\"column\" marginTop={hasMarginTop ? 1 : 0}>\n {title ? (\n <Box marginLeft={3}>\n <Text bold>{title}</Text>\n </Box>\n ) : null}\n\n {items.map((item) => {\n const isSelected = item.index === selectedIndex\n const label = highlightedLabel(item.label, highlightedTerm)\n\n return (\n <Box key={item.key}>\n <Box marginRight={2}>{isSelected ? <Text color=\"cyan\">{`>`}</Text> : <Text> </Text>}</Box>\n\n <Text color={isSelected ? 'cyan' : undefined}>{enableShortcuts ? `(${item.key}) ${label}` : label}</Text>\n </Box>\n )\n })}\n </Box>\n )\n}\n\n// eslint-disable-next-line react/function-component-definition\nfunction SelectInput<T>({\n items,\n onChange,\n enableShortcuts = true,\n focus = true,\n emptyMessage = 'No items to select.',\n defaultValue,\n highlightedTerm,\n loading = false,\n errorMessage,\n hasMorePages = false,\n morePagesMessage,\n infoMessage,\n}: React.PropsWithChildren<SelectInputProps<T>>): JSX.Element | null {\n const defaultValueIndex = defaultValue ? items.findIndex((item) => item.value === defaultValue.value) : -1\n const initialIndex = defaultValueIndex === -1 ? 0 : defaultValueIndex\n const inputStack = useRef<string | null>(null)\n const [selectedIndex, setSelectedIndex] = useState(initialIndex)\n const [groupedItems, ungroupedItems] = groupItems(items)\n const groupedItemsValues = [...Object.values(groupedItems).flat(), ...ungroupedItems]\n const keys = groupedItemsValues.map((item) => item.key)\n const groupTitles = Object.keys(groupedItems)\n const previousItems = useRef<Item<T>[]>(items)\n\n const changeSelection = useCallback(\n ({index, usedShortcut = false}: {index: number; usedShortcut?: boolean}) => {\n const groupedItem = groupedItemsValues.find((item) => item.index === index)!\n setSelectedIndex(index)\n onChange({\n item: items.find((item) => item.value === groupedItem.value),\n usedShortcut,\n })\n },\n [items],\n )\n\n useEffect(() => {\n if (items.length === 0) {\n // reset selection when items are empty\n onChange({\n item: undefined,\n usedShortcut: false,\n })\n } else if (\n // reset index when items change\n !isEqual(\n previousItems.current.map((item) => item.value),\n items.map((item) => item.value),\n )\n ) {\n changeSelection({index: 0})\n }\n\n previousItems.current = items\n }, [items])\n\n const handleArrows = useCallback(\n (key: Key) => {\n const lastIndex = items.length - 1\n\n if (key.upArrow) {\n changeSelection({index: selectedIndex === 0 ? lastIndex : selectedIndex - 1})\n } else if (key.downArrow) {\n changeSelection({index: selectedIndex === lastIndex ? 0 : selectedIndex + 1})\n }\n },\n [selectedIndex, items],\n )\n\n const handleShortcuts = useCallback(\n (input: string) => {\n if (keys.includes(input)) {\n const groupedItem = groupedItemsValues.find((item) => item.key === input)\n if (groupedItem !== undefined) {\n changeSelection({index: groupedItem.index, usedShortcut: true})\n }\n }\n },\n [items],\n )\n\n const debounceHandleShortcuts = useCallback(\n debounce((newInputStack) => {\n handleShortcuts(newInputStack)\n inputStack.current = null\n }, 300),\n [],\n )\n\n useInput(\n (input, key) => {\n // check that no special modifier (shift, control, etc.) is being pressed\n if (enableShortcuts && input.length > 0 && Object.values(key).every((value) => value === false)) {\n const newInputStack = inputStack.current === null ? input : inputStack.current + input\n\n inputStack.current = newInputStack\n debounceHandleShortcuts(newInputStack)\n } else {\n debounceHandleShortcuts.cancel()\n inputStack.current = null\n handleArrows(key)\n }\n },\n {isActive: focus},\n )\n\n const ungroupedItemsTitle = groupTitles.length > 0 ? 'Other' : undefined\n\n if (loading) {\n return (\n <Box marginLeft={3}>\n <Text dimColor>Loading...</Text>\n </Box>\n )\n } else if (errorMessage && errorMessage.length > 0) {\n return (\n <Box marginLeft={3}>\n <Text color=\"red\">{errorMessage}</Text>\n </Box>\n )\n } else if (items.length === 0) {\n return (\n <Box marginLeft={3}>\n <Text dimColor>{emptyMessage}</Text>\n </Box>\n )\n } else {\n return (\n <Box flexDirection=\"column\">\n {groupTitles.map((title, index) => (\n <SelectItemsGroup\n title={title}\n selectedIndex={selectedIndex}\n items={groupedItems[title]!}\n key={title}\n hasMarginTop={index !== 0}\n enableShortcuts={enableShortcuts}\n highlightedTerm={highlightedTerm}\n />\n ))}\n\n {ungroupedItems.length > 0 && (\n <SelectItemsGroup\n title={ungroupedItemsTitle}\n selectedIndex={selectedIndex}\n items={ungroupedItems}\n hasMarginTop={groupTitles.length > 0}\n enableShortcuts={enableShortcuts}\n highlightedTerm={highlightedTerm}\n />\n )}\n\n <Box marginTop={1} marginLeft={3} flexDirection=\"column\">\n {hasMorePages ? (\n <Text>\n <Text bold>1-{items.length} of many</Text>\n {morePagesMessage ? ` ${morePagesMessage}` : null}\n </Text>\n ) : null}\n <Text dimColor>\n {infoMessage\n ? infoMessage\n : `Press ${figures.arrowUp}${figures.arrowDown} arrows to select, enter to confirm`}\n </Text>\n </Box>\n </Box>\n )\n }\n}\n\nexport {SelectInput}\n"]}
1
+ {"version":3,"file":"SelectInput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SelectInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {isEqual} from '../../../../public/common/lang.js'\nimport {debounce} from '../../../../public/common/function.js'\nimport React, {useState, useEffect, useRef, useCallback, forwardRef} from 'react'\nimport {Box, Key, useInput, Text, DOMElement} from 'ink'\nimport chalk from 'chalk'\nimport figures from 'figures'\nimport {createRequire} from 'module'\n\nconst require = createRequire(import.meta.url)\n\ndeclare module 'react' {\n function forwardRef<T, P>(\n render: (props: P, ref: React.Ref<T>) => JSX.Element | null,\n ): (props: P & React.RefAttributes<T>) => JSX.Element | null\n}\n\nfunction rotateArray<T>(array: T[], rotation?: number) {\n const arrayCopy = array.slice()\n return arrayCopy.splice(-(rotation ?? 0) % arrayCopy.length).concat(arrayCopy)\n}\n\ninterface OnChangeOptions<T> {\n item: Item<T> | undefined\n usedShortcut: boolean\n}\nexport interface SelectInputProps<T> {\n items: Item<T>[]\n onChange: ({item, usedShortcut}: OnChangeOptions<T>) => void\n enableShortcuts?: boolean\n focus?: boolean\n emptyMessage?: string\n defaultValue?: Item<T>\n highlightedTerm?: string\n loading?: boolean\n errorMessage?: string\n hasMorePages?: boolean\n morePagesMessage?: string\n infoMessage?: string\n limit?: number\n}\n\nexport interface Item<T> {\n label: string\n value: T\n key?: string\n group?: string\n}\n\ninterface ItemWithKey<T> extends Item<T> {\n key: string\n}\n\nfunction highlightedLabel(label: string, term: string | undefined) {\n if (!term) {\n return label\n }\n\n const regex = new RegExp(term, 'i')\n return label.replace(regex, (match) => {\n return chalk.bold(match)\n })\n}\n\ninterface ItemProps<T> {\n item: ItemWithKey<T>\n previousItem: ItemWithKey<T> | undefined\n items: ItemWithKey<T>[]\n selectedIndex: number\n highlightedTerm?: string\n enableShortcuts: boolean\n hasAnyGroup: boolean\n}\n\n// eslint-disable-next-line react/function-component-definition\nfunction Item<T>({\n item,\n previousItem,\n selectedIndex,\n highlightedTerm,\n enableShortcuts,\n items,\n hasAnyGroup,\n}: ItemProps<T>): JSX.Element {\n const isSelected = items.indexOf(item) === selectedIndex\n const label = highlightedLabel(item.label, highlightedTerm)\n let title: string | undefined\n\n if (typeof previousItem === 'undefined' || item.group !== previousItem.group) {\n title = item.group ?? (hasAnyGroup ? 'Other' : undefined)\n }\n\n return (\n <Box key={item.key} flexDirection=\"column\" marginTop={items.indexOf(item) !== 0 && title ? 1 : 0}>\n {title ? (\n <Box marginLeft={3}>\n <Text bold>{title}</Text>\n </Box>\n ) : null}\n\n <Box key={item.key}>\n <Box marginRight={2}>{isSelected ? <Text color=\"cyan\">{`>`}</Text> : <Text> </Text>}</Box>\n <Text color={isSelected ? 'cyan' : undefined}>{enableShortcuts ? `(${item.key}) ${label}` : label}</Text>\n </Box>\n </Box>\n )\n}\n\n// eslint-disable-next-line react/function-component-definition\nfunction SelectInputInner<T>(\n {\n items: initialItems,\n onChange,\n enableShortcuts = true,\n focus = true,\n emptyMessage = 'No items to select.',\n defaultValue,\n highlightedTerm,\n loading = false,\n errorMessage,\n hasMorePages = false,\n morePagesMessage,\n infoMessage,\n limit,\n }: SelectInputProps<T>,\n ref: React.ForwardedRef<DOMElement>,\n): JSX.Element | null {\n const sortBy = require('lodash/sortBy')\n const hasAnyGroup = initialItems.some((item) => typeof item.group !== 'undefined')\n const items = sortBy(initialItems, 'group') as Item<T>[]\n const itemsWithKeys = items.map((item, index) => ({\n ...item,\n key: item.key ?? (index + 1).toString(),\n })) as ItemWithKey<T>[]\n const defaultValueIndex = defaultValue ? items.findIndex((item) => item.value === defaultValue.value) : -1\n const initialIndex = defaultValueIndex === -1 ? 0 : defaultValueIndex\n const hasLimit = typeof limit !== 'undefined' && items.length > limit\n const inputStack = useRef<string | null>(null)\n const [selectedIndex, setSelectedIndex] = useState(initialIndex)\n const [rotateIndex, setRotateIndex] = useState(0)\n const slicedItemsWithKeys = hasLimit ? rotateArray(itemsWithKeys, rotateIndex).slice(0, limit) : itemsWithKeys\n const previousItems = useRef<Item<T>[] | undefined>(undefined)\n\n const changeSelection = useCallback(\n ({\n newSelectedIndex,\n newRotateIndex,\n usedShortcut = false,\n }: {\n newSelectedIndex: number\n usedShortcut?: boolean\n newRotateIndex?: number\n }) => {\n setSelectedIndex(newSelectedIndex)\n if (typeof newRotateIndex !== 'undefined') setRotateIndex(newRotateIndex)\n\n const rotatedItems = hasLimit ? rotateArray(items, newRotateIndex).slice(0, limit) : items\n\n onChange({\n item: rotatedItems[newSelectedIndex],\n usedShortcut,\n })\n },\n [hasLimit, items, limit, onChange],\n )\n\n useEffect(() => {\n if (items.length === 0) {\n // reset selection when items are empty\n onChange({\n item: undefined,\n usedShortcut: false,\n })\n // reset index when items change or the first time\n } else if (!previousItems.current) {\n changeSelection({newSelectedIndex: initialIndex, newRotateIndex: 0})\n } else if (\n !isEqual(\n previousItems.current.map((item) => item.value),\n items.map((item) => item.value),\n )\n ) {\n changeSelection({newSelectedIndex: 0, newRotateIndex: 0})\n }\n\n previousItems.current = items\n }, [changeSelection, initialIndex, items, onChange])\n\n const handleArrows = (key: Key) => {\n if (key.upArrow) {\n const lastIndex = items.length - 1\n const atFirstIndex = selectedIndex === 0\n const nextIndex = hasLimit ? selectedIndex : lastIndex\n const nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex\n const nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1\n\n changeSelection({newSelectedIndex: nextSelectedIndex, newRotateIndex: nextRotateIndex})\n } else if (key.downArrow) {\n const atLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1\n const nextIndex = hasLimit ? selectedIndex : 0\n const nextRotateIndex = atLastIndex ? rotateIndex - 1 : rotateIndex\n const nextSelectedIndex = atLastIndex ? nextIndex : selectedIndex + 1\n\n changeSelection({newSelectedIndex: nextSelectedIndex, newRotateIndex: nextRotateIndex})\n }\n }\n\n const handleShortcuts = useCallback(\n (input: string) => {\n if (slicedItemsWithKeys.map((item) => item.key).includes(input)) {\n const itemWithKey = slicedItemsWithKeys.find((item) => item.key === input)\n if (itemWithKey !== undefined) {\n changeSelection({\n newSelectedIndex: items.findIndex((item) => item.value === itemWithKey.value),\n usedShortcut: true,\n })\n }\n }\n },\n [changeSelection, items, slicedItemsWithKeys],\n )\n\n // disable exhaustive-deps because we want to memoize the debounce function itself\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const debounceHandleShortcuts = useCallback(\n debounce((newInputStack) => {\n handleShortcuts(newInputStack)\n inputStack.current = null\n }, 300),\n [handleShortcuts],\n )\n\n useInput(\n (input, key) => {\n // check that no special modifier (shift, control, etc.) is being pressed\n if (enableShortcuts && input.length > 0 && Object.values(key).every((value) => value === false)) {\n const newInputStack = inputStack.current === null ? input : inputStack.current + input\n\n inputStack.current = newInputStack\n debounceHandleShortcuts(newInputStack)\n } else {\n debounceHandleShortcuts.cancel()\n inputStack.current = null\n handleArrows(key)\n }\n },\n {isActive: focus},\n )\n\n if (loading) {\n return (\n <Box marginLeft={3}>\n <Text dimColor>Loading...</Text>\n </Box>\n )\n } else if (errorMessage && errorMessage.length > 0) {\n return (\n <Box marginLeft={3}>\n <Text color=\"red\">{errorMessage}</Text>\n </Box>\n )\n } else if (items.length === 0) {\n return (\n <Box marginLeft={3}>\n <Text dimColor>{emptyMessage}</Text>\n </Box>\n )\n } else {\n return (\n <Box flexDirection=\"column\" ref={ref}>\n {slicedItemsWithKeys.map((item, index) => (\n <Item\n key={item.key}\n item={item}\n previousItem={slicedItemsWithKeys[index - 1]}\n highlightedTerm={highlightedTerm}\n selectedIndex={selectedIndex}\n items={slicedItemsWithKeys}\n enableShortcuts={enableShortcuts}\n hasAnyGroup={hasAnyGroup}\n />\n ))}\n\n <Box marginTop={1} marginLeft={3} flexDirection=\"column\">\n {hasMorePages ? (\n <Text>\n <Text bold>1-{items.length} of many</Text>\n {morePagesMessage ? ` ${morePagesMessage}` : null}\n </Text>\n ) : null}\n {hasLimit ? <Text dimColor>{`Showing ${limit} of ${items.length} items.`}</Text> : null}\n <Text dimColor>\n {infoMessage\n ? infoMessage\n : `Press ${figures.arrowUp}${figures.arrowDown} arrows to select, enter to confirm`}\n </Text>\n </Box>\n </Box>\n )\n }\n}\n\nexport const SelectInput = forwardRef(SelectInputInner)\n"]}
@@ -146,7 +146,8 @@ describe('SelectInput', async () => {
146
146
 
147
147
  Press ↑↓ arrows to select, enter to confirm"
148
148
  `);
149
- expect(onChange).not.toHaveBeenCalled();
149
+ expect(onChange).toHaveBeenCalledOnce();
150
+ expect(onChange).toHaveBeenCalledWith({ item: items[0], usedShortcut: false });
150
151
  });
151
152
  test('handles custom keys', async () => {
152
153
  const onChange = vi.fn();
@@ -313,7 +314,8 @@ describe('SelectInput', async () => {
313
314
 
314
315
  Press ↑↓ arrows to select, enter to confirm"
315
316
  `);
316
- expect(onChange).not.toHaveBeenCalled();
317
+ expect(onChange).toHaveBeenCalledOnce();
318
+ expect(onChange).toHaveBeenCalledWith({ item: items[0], usedShortcut: false });
317
319
  });
318
320
  test('accepts a default value', async () => {
319
321
  const items = [
@@ -366,5 +368,73 @@ describe('SelectInput', async () => {
366
368
  Press ↑↓ arrows to select, enter to confirm"
367
369
  `);
368
370
  });
371
+ test('supports a limit of items to show', async () => {
372
+ const items = [
373
+ { label: 'first', value: 'first', key: 'f' },
374
+ { label: 'second', value: 'second', key: 's' },
375
+ { label: 'third', value: 'third' },
376
+ { label: 'fourth', value: 'fourth' },
377
+ { label: 'fifth', value: 'fifth', group: 'Automations', key: 'a' },
378
+ { label: 'sixth', value: 'sixth', group: 'Automations' },
379
+ { label: 'seventh', value: 'seventh' },
380
+ { label: 'eighth', value: 'eighth', group: 'Merchant Admin' },
381
+ { label: 'ninth', value: 'ninth', group: 'Merchant Admin' },
382
+ { label: 'tenth', value: 'tenth' },
383
+ ];
384
+ const renderInstance = render(React.createElement(SelectInput, { items: items, onChange: () => { }, limit: 5 }));
385
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
386
+ " Automations
387
+ > (a) fifth
388
+ (2) sixth
389
+
390
+ Merchant Admin
391
+ (3) eighth
392
+ (4) ninth
393
+
394
+ Other
395
+ (f) first
396
+
397
+ Showing 5 of 10 items.
398
+ Press ↑↓ arrows to select, enter to confirm"
399
+ `);
400
+ await waitForInputsToBeReady();
401
+ await sendInputAndWaitForChange(renderInstance, ARROW_UP);
402
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
403
+ " Other
404
+ > (10) tenth
405
+
406
+ Automations
407
+ (a) fifth
408
+ (2) sixth
409
+
410
+ Merchant Admin
411
+ (3) eighth
412
+ (4) ninth
413
+
414
+ Showing 5 of 10 items.
415
+ Press ↑↓ arrows to select, enter to confirm"
416
+ `);
417
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
418
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
419
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
420
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
421
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
422
+ await sendInputAndWaitForChange(renderInstance, ARROW_DOWN);
423
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
424
+ " Automations
425
+ (2) sixth
426
+
427
+ Merchant Admin
428
+ (3) eighth
429
+ (4) ninth
430
+
431
+ Other
432
+ (f) first
433
+ > (s) second
434
+
435
+ Showing 5 of 10 items.
436
+ Press ↑↓ arrows to select, enter to confirm"
437
+ `);
438
+ });
369
439
  });
370
440
  //# sourceMappingURL=SelectInput.test.js.map