@shopify/cli-kit 3.47.4 → 3.48.0

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 (138) hide show
  1. package/assets/cli-ruby/lib/project_types/extension/messages/messages.rb +18 -1
  2. package/assets/cli-ruby/lib/shopify_cli/constants.rb +1 -0
  3. package/assets/cli-ruby/lib/shopify_cli/environment.rb +7 -0
  4. package/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +8 -2
  5. package/dist/private/node/ui/alert.js +3 -1
  6. package/dist/private/node/ui/alert.js.map +1 -1
  7. package/dist/private/node/ui/components/Alert.d.ts +1 -1
  8. package/dist/private/node/ui/components/Alert.js.map +1 -1
  9. package/dist/private/node/ui/components/AutocompletePrompt.d.ts +7 -2
  10. package/dist/private/node/ui/components/AutocompletePrompt.js +28 -91
  11. package/dist/private/node/ui/components/AutocompletePrompt.js.map +1 -1
  12. package/dist/private/node/ui/components/AutocompletePrompt.test.js +317 -257
  13. package/dist/private/node/ui/components/AutocompletePrompt.test.js.map +1 -1
  14. package/dist/private/node/ui/components/ConcurrentOutput.d.ts +1 -0
  15. package/dist/private/node/ui/components/ConcurrentOutput.js +59 -32
  16. package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
  17. package/dist/private/node/ui/components/ConcurrentOutput.test.js +62 -22
  18. package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -1
  19. package/dist/private/node/ui/components/DangerousConfirmationPrompt.d.ts +12 -0
  20. package/dist/private/node/ui/components/DangerousConfirmationPrompt.js +77 -0
  21. package/dist/private/node/ui/components/DangerousConfirmationPrompt.js.map +1 -0
  22. package/dist/private/node/ui/components/DangerousConfirmationPrompt.test.js +101 -0
  23. package/dist/private/node/ui/components/DangerousConfirmationPrompt.test.js.map +1 -0
  24. package/dist/private/node/ui/components/List.d.ts +1 -0
  25. package/dist/private/node/ui/components/List.js +2 -2
  26. package/dist/private/node/ui/components/List.js.map +1 -1
  27. package/dist/private/node/ui/components/{GitDiff.d.ts → Prompts/GitDiff.d.ts} +4 -2
  28. package/dist/private/node/ui/components/{GitDiff.js → Prompts/GitDiff.js} +3 -2
  29. package/dist/private/node/ui/components/Prompts/GitDiff.js.map +1 -0
  30. package/dist/private/node/ui/components/Prompts/GitDiff.test.d.ts +1 -0
  31. package/dist/private/node/ui/components/{GitDiff.test.js → Prompts/GitDiff.test.js} +50 -28
  32. package/dist/private/node/ui/components/Prompts/GitDiff.test.js.map +1 -0
  33. package/dist/private/node/ui/components/Prompts/InfoMessage.d.ts +14 -0
  34. package/dist/private/node/ui/components/Prompts/InfoMessage.js +11 -0
  35. package/dist/private/node/ui/components/Prompts/InfoMessage.js.map +1 -0
  36. package/dist/private/node/ui/components/Prompts/InfoMessage.test.d.ts +1 -0
  37. package/dist/private/node/ui/components/Prompts/InfoMessage.test.js +21 -0
  38. package/dist/private/node/ui/components/Prompts/InfoMessage.test.js.map +1 -0
  39. package/dist/private/node/ui/components/Prompts/InfoTable.d.ts +1 -0
  40. package/dist/private/node/ui/components/Prompts/InfoTable.js +11 -7
  41. package/dist/private/node/ui/components/Prompts/InfoTable.js.map +1 -1
  42. package/dist/private/node/ui/components/Prompts/InfoTable.test.js +6 -4
  43. package/dist/private/node/ui/components/Prompts/InfoTable.test.js.map +1 -1
  44. package/dist/private/node/ui/components/Prompts/PromptLayout.d.ts +21 -0
  45. package/dist/private/node/ui/components/Prompts/PromptLayout.js +73 -0
  46. package/dist/private/node/ui/components/Prompts/PromptLayout.js.map +1 -0
  47. package/dist/private/node/ui/components/Prompts/PromptLayout.test.d.ts +1 -0
  48. package/dist/private/node/ui/components/Prompts/PromptLayout.test.js +129 -0
  49. package/dist/private/node/ui/components/Prompts/PromptLayout.test.js.map +1 -0
  50. package/dist/private/node/ui/components/Scrollbar.d.ts +10 -0
  51. package/dist/private/node/ui/components/Scrollbar.js +44 -0
  52. package/dist/private/node/ui/components/Scrollbar.js.map +1 -0
  53. package/dist/private/node/ui/components/Scrollbar.test.d.ts +1 -0
  54. package/dist/private/node/ui/components/Scrollbar.test.js +96 -0
  55. package/dist/private/node/ui/components/Scrollbar.test.js.map +1 -0
  56. package/dist/private/node/ui/components/SelectInput.d.ts +3 -6
  57. package/dist/private/node/ui/components/SelectInput.js +57 -41
  58. package/dist/private/node/ui/components/SelectInput.js.map +1 -1
  59. package/dist/private/node/ui/components/SelectInput.test.js +120 -192
  60. package/dist/private/node/ui/components/SelectInput.test.js.map +1 -1
  61. package/dist/private/node/ui/components/SelectPrompt.d.ts +7 -6
  62. package/dist/private/node/ui/components/SelectPrompt.js +11 -68
  63. package/dist/private/node/ui/components/SelectPrompt.js.map +1 -1
  64. package/dist/private/node/ui/components/SelectPrompt.test.js +135 -65
  65. package/dist/private/node/ui/components/SelectPrompt.test.js.map +1 -1
  66. package/dist/private/node/ui/components/Table/Row.js +2 -1
  67. package/dist/private/node/ui/components/Table/Row.js.map +1 -1
  68. package/dist/private/node/ui/components/Table/Table.js +2 -1
  69. package/dist/private/node/ui/components/Table/Table.js.map +1 -1
  70. package/dist/private/node/ui/components/Tasks.js +1 -8
  71. package/dist/private/node/ui/components/Tasks.js.map +1 -1
  72. package/dist/private/node/ui/components/TextInput.d.ts +1 -0
  73. package/dist/private/node/ui/components/TextInput.js +10 -4
  74. package/dist/private/node/ui/components/TextInput.js.map +1 -1
  75. package/dist/private/node/ui/components/TextInput.test.js +27 -18
  76. package/dist/private/node/ui/components/TextInput.test.js.map +1 -1
  77. package/dist/private/node/ui/components/TextPrompt.d.ts +2 -3
  78. package/dist/private/node/ui/components/TextPrompt.js +18 -16
  79. package/dist/private/node/ui/components/TextPrompt.js.map +1 -1
  80. package/dist/private/node/ui/components/TextPrompt.test.js +25 -11
  81. package/dist/private/node/ui/components/TextPrompt.test.js.map +1 -1
  82. package/dist/private/node/ui/hooks/use-prompt.d.ts +18 -0
  83. package/dist/private/node/ui/hooks/use-prompt.js +20 -0
  84. package/dist/private/node/ui/hooks/use-prompt.js.map +1 -0
  85. package/dist/private/node/ui/hooks/use-select-state.d.ts +4 -4
  86. package/dist/private/node/ui/hooks/use-select-state.js +9 -9
  87. package/dist/private/node/ui/hooks/use-select-state.js.map +1 -1
  88. package/dist/public/common/object.d.ts +16 -0
  89. package/dist/public/common/object.js +26 -0
  90. package/dist/public/common/object.js.map +1 -1
  91. package/dist/public/common/string.d.ts +16 -0
  92. package/dist/public/common/string.js +30 -0
  93. package/dist/public/common/string.js.map +1 -1
  94. package/dist/public/common/version.d.ts +1 -1
  95. package/dist/public/common/version.js +1 -1
  96. package/dist/public/common/version.js.map +1 -1
  97. package/dist/public/node/analytics.js +3 -1
  98. package/dist/public/node/analytics.js.map +1 -1
  99. package/dist/public/node/base-command.d.ts +1 -0
  100. package/dist/public/node/base-command.js +21 -1
  101. package/dist/public/node/base-command.js.map +1 -1
  102. package/dist/public/node/figures.d.ts +2 -0
  103. package/dist/public/node/figures.js +3 -0
  104. package/dist/public/node/figures.js.map +1 -0
  105. package/dist/public/node/metadata.d.ts +2 -1
  106. package/dist/public/node/metadata.js +5 -2
  107. package/dist/public/node/metadata.js.map +1 -1
  108. package/dist/public/node/monorail.d.ts +22 -1
  109. package/dist/public/node/monorail.js +1 -1
  110. package/dist/public/node/monorail.js.map +1 -1
  111. package/dist/public/node/output.d.ts +1 -1
  112. package/dist/public/node/output.js +1 -1
  113. package/dist/public/node/output.js.map +1 -1
  114. package/dist/public/node/ruby.d.ts +1 -0
  115. package/dist/public/node/ruby.js +1 -0
  116. package/dist/public/node/ruby.js.map +1 -1
  117. package/dist/public/node/system.js +2 -2
  118. package/dist/public/node/system.js.map +1 -1
  119. package/dist/public/node/themes/models/theme.d.ts +2 -1
  120. package/dist/public/node/themes/models/theme.js +2 -1
  121. package/dist/public/node/themes/models/theme.js.map +1 -1
  122. package/dist/public/node/themes/theme-urls.d.ts +1 -0
  123. package/dist/public/node/themes/theme-urls.js +4 -0
  124. package/dist/public/node/themes/theme-urls.js.map +1 -1
  125. package/dist/public/node/themes/themes-api.d.ts +9 -1
  126. package/dist/public/node/themes/themes-api.js +14 -3
  127. package/dist/public/node/themes/themes-api.js.map +1 -1
  128. package/dist/public/node/toml.d.ts +3 -2
  129. package/dist/public/node/toml.js +5 -2
  130. package/dist/public/node/toml.js.map +1 -1
  131. package/dist/public/node/ui.d.ts +82 -27
  132. package/dist/public/node/ui.js +97 -32
  133. package/dist/public/node/ui.js.map +1 -1
  134. package/dist/tsconfig.tsbuildinfo +1 -1
  135. package/package.json +14 -14
  136. package/dist/private/node/ui/components/GitDiff.js.map +0 -1
  137. package/dist/private/node/ui/components/GitDiff.test.js.map +0 -1
  138. /package/dist/private/node/ui/components/{GitDiff.test.d.ts → DangerousConfirmationPrompt.test.d.ts} +0 -0
@@ -0,0 +1,21 @@
1
+ import { InfoMessage } from './InfoMessage.js';
2
+ import { render } from '../../../testing/ui.js';
3
+ import { describe, expect, test } from 'vitest';
4
+ import React from 'react';
5
+ describe('InfoMessage', async () => {
6
+ test('renders a message with title and body', async () => {
7
+ const { lastFrame } = render(React.createElement(InfoMessage, { message: {
8
+ title: {
9
+ color: 'red',
10
+ text: "This can't be undone.",
11
+ },
12
+ body: "Once you upgrade this app, you can't go back to the old way of deploying extensions",
13
+ } }));
14
+ expect(lastFrame()).toMatchInlineSnapshot(`
15
+ "This can't be undone.
16
+
17
+ Once you upgrade this app, you can't go back to the old way of deploying extensions"
18
+ `);
19
+ });
20
+ });
21
+ //# sourceMappingURL=InfoMessage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfoMessage.test.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/InfoMessage.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAA;AAC7C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,QAAQ,CAAC,aAAa,EAAE,KAAK,IAAI,EAAE;IACjC,IAAI,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,WAAW,IACV,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,KAAK,EAAE,KAAK;oBACZ,IAAI,EAAE,uBAAuB;iBAC9B;gBACD,IAAI,EAAE,qFAAqF;aAC5F,GACD,CACH,CAAA;QAED,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,qBAAqB,CAAC;;;;KAIzC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {InfoMessage} from './InfoMessage.js'\nimport {render} from '../../../testing/ui.js'\nimport {describe, expect, test} from 'vitest'\nimport React from 'react'\n\ndescribe('InfoMessage', async () => {\n test('renders a message with title and body', async () => {\n const {lastFrame} = render(\n <InfoMessage\n message={{\n title: {\n color: 'red',\n text: \"This can't be undone.\",\n },\n body: \"Once you upgrade this app, you can't go back to the old way of deploying extensions\",\n }}\n />,\n )\n\n expect(lastFrame()).toMatchInlineSnapshot(`\n \"\u001b[31mThis can't be undone.\u001b[39m\n\n Once you upgrade this app, you can't go back to the old way of deploying extensions\"\n `)\n })\n})\n"]}
@@ -5,6 +5,7 @@ type Items = TokenItem<InlineToken>[];
5
5
  export interface InfoTableSection {
6
6
  color?: TextProps['color'];
7
7
  header: string;
8
+ bullet?: string;
8
9
  helperText?: string;
9
10
  items: Items;
10
11
  }
@@ -5,19 +5,23 @@ import React from 'react';
5
5
  const InfoTable = ({ table }) => {
6
6
  const sections = Array.isArray(table)
7
7
  ? table
8
- : Object.keys(table).map((header) => ({ header, items: table[header], color: undefined, helperText: undefined }));
8
+ : Object.keys(table).map((header) => ({
9
+ header,
10
+ items: table[header],
11
+ color: undefined,
12
+ helperText: undefined,
13
+ bullet: undefined,
14
+ }));
9
15
  const headerColumnWidth = Math.max(...sections.map((section) => {
10
16
  return Math.max(...section.header.split('\n').map((line) => {
11
17
  return line.length;
12
18
  }));
13
19
  }));
14
- return (React.createElement(Box, { flexDirection: "column" }, sections.map((section, index) => (React.createElement(Box, { key: index, marginBottom: index === sections.length - 1 ? 0 : 1 },
20
+ return (React.createElement(Box, { flexDirection: "column" }, sections.map((section, index) => (React.createElement(Box, { key: index, marginBottom: index === sections.length - 1 ? 0 : 1, flexDirection: "column" },
15
21
  section.header.length > 0 && (React.createElement(Box, { width: headerColumnWidth + 1 },
16
- React.createElement(Text, { color: section.color },
17
- capitalize(section.header),
18
- ":"))),
19
- React.createElement(Box, { marginLeft: section.header.length > 0 ? 2 : 0, flexGrow: 1, flexDirection: "column", gap: 1 },
20
- React.createElement(List, { margin: false, items: section.items, color: section.color }),
22
+ React.createElement(Text, { color: section.color, bold: true }, capitalize(section.header)))),
23
+ React.createElement(Box, { flexGrow: 1, flexDirection: "column", gap: 1 },
24
+ React.createElement(List, { margin: false, items: section.items, color: section.color, bullet: section.bullet }),
21
25
  section.helperText ? React.createElement(Text, { color: section.color }, section.helperText) : null))))));
22
26
  };
23
27
  export { InfoTable };
@@ -1 +1 @@
1
- {"version":3,"file":"InfoTable.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/InfoTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,YAAY,CAAA;AAC/B,OAAO,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAA;AAEjE,OAAO,EAAC,GAAG,EAAE,IAAI,EAAY,MAAM,KAAK,CAAA;AACxC,OAAO,KAA0B,MAAM,OAAO,CAAA;AAmB9C,MAAM,SAAS,GAAsC,CAAC,EAAC,KAAK,EAAC,EAAE,EAAE;IAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACnC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAC,CAAC,CAAC,CAAA;IAElH,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAChC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1B,OAAO,IAAI,CAAC,GAAG,CACb,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACzC,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC,CAAC,CACH,CAAA;IACH,CAAC,CAAC,CACH,CAAA;IAED,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,IACxB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAC5B,oBAAC,GAAG,IAAC,KAAK,EAAE,iBAAiB,GAAG,CAAC;YAC/B,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,KAAK;gBAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;oBAAS,CAC5D,CACP;QACD,oBAAC,GAAG,IAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC;YAC5F,oBAAC,IAAI,IAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,GAAI;YAClE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAG,OAAO,CAAC,UAAU,CAAQ,CAAC,CAAC,CAAC,IAAI,CAChF,CACF,CACP,CAAC,CACE,CACP,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,SAAS,EAAC,CAAA","sourcesContent":["import {List} from '../List.js'\nimport {capitalize} from '../../../../../public/common/string.js'\nimport {InlineToken, TokenItem} from '../TokenizedText.js'\nimport {Box, Text, TextProps} from 'ink'\nimport React, {FunctionComponent} from 'react'\n\ntype Items = TokenItem<InlineToken>[]\n\nexport interface InfoTableSection {\n color?: TextProps['color']\n header: string\n helperText?: string\n items: Items\n}\n\nexport interface InfoTableProps {\n table:\n | {\n [header: string]: Items\n }\n | InfoTableSection[]\n}\n\nconst InfoTable: FunctionComponent<InfoTableProps> = ({table}) => {\n const sections = Array.isArray(table)\n ? table\n : Object.keys(table).map((header) => ({header, items: table[header]!, color: undefined, helperText: undefined}))\n\n const headerColumnWidth = Math.max(\n ...sections.map((section) => {\n return Math.max(\n ...section.header.split('\\n').map((line) => {\n return line.length\n }),\n )\n }),\n )\n\n return (\n <Box flexDirection=\"column\">\n {sections.map((section, index) => (\n <Box key={index} marginBottom={index === sections.length - 1 ? 0 : 1}>\n {section.header.length > 0 && (\n <Box width={headerColumnWidth + 1}>\n <Text color={section.color}>{capitalize(section.header)}:</Text>\n </Box>\n )}\n <Box marginLeft={section.header.length > 0 ? 2 : 0} flexGrow={1} flexDirection=\"column\" gap={1}>\n <List margin={false} items={section.items} color={section.color} />\n {section.helperText ? <Text color={section.color}>{section.helperText}</Text> : null}\n </Box>\n </Box>\n ))}\n </Box>\n )\n}\n\nexport {InfoTable}\n"]}
1
+ {"version":3,"file":"InfoTable.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/InfoTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,YAAY,CAAA;AAC/B,OAAO,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAA;AAEjE,OAAO,EAAC,GAAG,EAAE,IAAI,EAAY,MAAM,KAAK,CAAA;AACxC,OAAO,KAA0B,MAAM,OAAO,CAAA;AAoB9C,MAAM,SAAS,GAAsC,CAAC,EAAC,KAAK,EAAC,EAAE,EAAE;IAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACnC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAClC,MAAM;YACN,KAAK,EAAE,KAAK,CAAC,MAAM,CAAE;YACrB,KAAK,EAAE,SAAS;YAChB,UAAU,EAAE,SAAS;YACrB,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC,CAAA;IAEP,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAChC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1B,OAAO,IAAI,CAAC,GAAG,CACb,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACzC,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC,CAAC,CACH,CAAA;IACH,CAAC,CAAC,CACH,CAAA;IAED,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,IACxB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAC,QAAQ;QACzF,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAC5B,oBAAC,GAAG,IAAC,KAAK,EAAE,iBAAiB,GAAG,CAAC;YAC/B,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,UAC7B,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CACtB,CACH,CACP;QACD,oBAAC,GAAG,IAAC,QAAQ,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC;YAC7C,oBAAC,IAAI,IAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,GAAI;YAC1F,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAG,OAAO,CAAC,UAAU,CAAQ,CAAC,CAAC,CAAC,IAAI,CAChF,CACF,CACP,CAAC,CACE,CACP,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,SAAS,EAAC,CAAA","sourcesContent":["import {List} from '../List.js'\nimport {capitalize} from '../../../../../public/common/string.js'\nimport {InlineToken, TokenItem} from '../TokenizedText.js'\nimport {Box, Text, TextProps} from 'ink'\nimport React, {FunctionComponent} from 'react'\n\ntype Items = TokenItem<InlineToken>[]\n\nexport interface InfoTableSection {\n color?: TextProps['color']\n header: string\n bullet?: string\n helperText?: string\n items: Items\n}\n\nexport interface InfoTableProps {\n table:\n | {\n [header: string]: Items\n }\n | InfoTableSection[]\n}\n\nconst InfoTable: FunctionComponent<InfoTableProps> = ({table}) => {\n const sections = Array.isArray(table)\n ? table\n : Object.keys(table).map((header) => ({\n header,\n items: table[header]!,\n color: undefined,\n helperText: undefined,\n bullet: undefined,\n }))\n\n const headerColumnWidth = Math.max(\n ...sections.map((section) => {\n return Math.max(\n ...section.header.split('\\n').map((line) => {\n return line.length\n }),\n )\n }),\n )\n\n return (\n <Box flexDirection=\"column\">\n {sections.map((section, index) => (\n <Box key={index} marginBottom={index === sections.length - 1 ? 0 : 1} flexDirection=\"column\">\n {section.header.length > 0 && (\n <Box width={headerColumnWidth + 1}>\n <Text color={section.color} bold>\n {capitalize(section.header)}\n </Text>\n </Box>\n )}\n <Box flexGrow={1} flexDirection=\"column\" gap={1}>\n <List margin={false} items={section.items} color={section.color} bullet={section.bullet} />\n {section.helperText ? <Text color={section.color}>{section.helperText}</Text> : null}\n </Box>\n </Box>\n ))}\n </Box>\n )\n}\n\nexport {InfoTable}\n"]}
@@ -10,11 +10,13 @@ describe('InfoTable', async () => {
10
10
  'header 2\nlonger text here': [['one item', { link: { label: 'Shopify', url: 'https://shopify.com' } }]],
11
11
  } }));
12
12
  expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
13
- "Header 1: • some
14
- items
13
+ "Header 1
14
+ some
15
+ • items
15
16
 
16
- Header 2 • one item Shopify ( https://shopify.com )
17
- longer text here:"
17
+ Header 2
18
+ longer text here
19
+ • one item Shopify ( https://shopify.com )"
18
20
  `);
19
21
  });
20
22
  test('supports an empty header value', async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"InfoTable.test.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/InfoTable.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAC7D,OAAO,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAA;AAC7C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;IAC/B,IAAI,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,SAAS,IACR,KAAK,EAAE;gBACL,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;gBAC7B,4BAA4B,EAAE,CAAC,CAAC,UAAU,EAAE,EAAC,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,qBAAqB,EAAC,EAAC,CAAC,CAAC;aACrG,GACD,CACH,CAAA;QAED,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;KAMpD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,SAAS,IACR,KAAK,EAAE;gBACL,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;aACtB,GACD,CACH,CAAA;QAED,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;KAGpD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {InfoTable} from './InfoTable.js'\nimport {unstyled} from '../../../../../public/node/output.js'\nimport {render} from '../../../testing/ui.js'\nimport {describe, expect, test} from 'vitest'\nimport React from 'react'\n\ndescribe('InfoTable', async () => {\n test('renders a horizontal table with bullet points', async () => {\n const {lastFrame} = render(\n <InfoTable\n table={{\n 'header 1': ['some', 'items'],\n 'header 2\\nlonger text here': [['one item', {link: {label: 'Shopify', url: 'https://shopify.com'}}]],\n }}\n />,\n )\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"Header 1: • some\n • items\n\n Header 2 • one item Shopify ( https://shopify.com )\n longer text here:\"\n `)\n })\n\n test('supports an empty header value', async () => {\n const {lastFrame} = render(\n <InfoTable\n table={{\n '': ['some', 'items'],\n }}\n />,\n )\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"• some\n • items\"\n `)\n })\n})\n"]}
1
+ {"version":3,"file":"InfoTable.test.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/InfoTable.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAC7D,OAAO,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAA;AAC7C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;IAC/B,IAAI,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,SAAS,IACR,KAAK,EAAE;gBACL,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;gBAC7B,4BAA4B,EAAE,CAAC,CAAC,UAAU,EAAE,EAAC,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,qBAAqB,EAAC,EAAC,CAAC,CAAC;aACrG,GACD,CACH,CAAA;QAED,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;KAQpD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,SAAS,IACR,KAAK,EAAE;gBACL,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;aACtB,GACD,CACH,CAAA;QAED,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;KAGpD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {InfoTable} from './InfoTable.js'\nimport {unstyled} from '../../../../../public/node/output.js'\nimport {render} from '../../../testing/ui.js'\nimport {describe, expect, test} from 'vitest'\nimport React from 'react'\n\ndescribe('InfoTable', async () => {\n test('renders a horizontal table with bullet points', async () => {\n const {lastFrame} = render(\n <InfoTable\n table={{\n 'header 1': ['some', 'items'],\n 'header 2\\nlonger text here': [['one item', {link: {label: 'Shopify', url: 'https://shopify.com'}}]],\n }}\n />,\n )\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"Header 1\n • some\n • items\n\n Header 2\n longer text here\n • one item Shopify ( https://shopify.com )\"\n `)\n })\n\n test('supports an empty header value', async () => {\n const {lastFrame} = render(\n <InfoTable\n table={{\n '': ['some', 'items'],\n }}\n />,\n )\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"• some\n • items\"\n `)\n })\n})\n"]}
@@ -0,0 +1,21 @@
1
+ import { GitDiffProps } from './GitDiff.js';
2
+ import { InfoMessageProps } from './InfoMessage.js';
3
+ import { InfoTableProps } from './InfoTable.js';
4
+ import { InlineToken, LinkToken, TokenItem } from '../TokenizedText.js';
5
+ import { AbortSignal } from '../../../../../public/node/abort.js';
6
+ import { PromptState } from '../../hooks/use-prompt.js';
7
+ import { ReactElement } from 'react';
8
+ export type Message = TokenItem<Exclude<InlineToken, LinkToken>>;
9
+ export interface PromptLayoutProps {
10
+ message: Message;
11
+ infoTable?: InfoTableProps['table'];
12
+ abortSignal?: AbortSignal;
13
+ infoMessage?: InfoMessageProps['message'];
14
+ gitDiff?: GitDiffProps['gitDiff'];
15
+ header?: ReactElement | null;
16
+ state: PromptState;
17
+ submittedAnswerLabel?: string;
18
+ input: ReactElement;
19
+ }
20
+ declare const PromptLayout: ({ message, infoTable, abortSignal, infoMessage, gitDiff, header, state, input, submittedAnswerLabel, }: PromptLayoutProps) => ReactElement | null;
21
+ export { PromptLayout };
@@ -0,0 +1,73 @@
1
+ import { GitDiff } from './GitDiff.js';
2
+ import { InfoMessage } from './InfoMessage.js';
3
+ import { InfoTable } from './InfoTable.js';
4
+ import { TokenizedText } from '../TokenizedText.js';
5
+ import { messageWithPunctuation } from '../../utilities.js';
6
+ import useAbortSignal from '../../hooks/use-abort-signal.js';
7
+ import { PromptState } from '../../hooks/use-prompt.js';
8
+ import React, { cloneElement, useCallback, useLayoutEffect, useState } from 'react';
9
+ import { Box, measureElement, Text, useStdout } from 'ink';
10
+ import figures from 'figures';
11
+ const PromptLayout = ({ message, infoTable, abortSignal, infoMessage, gitDiff, header, state, input, submittedAnswerLabel, }) => {
12
+ const { stdout } = useStdout();
13
+ const [wrapperHeight, setWrapperHeight] = useState(0);
14
+ const [promptAreaHeight, setPromptAreaHeight] = useState(0);
15
+ const [inputFixedAreaHeight, setInputFixedAreaHeight] = useState(0);
16
+ const currentAvailableLines = stdout.rows - promptAreaHeight - inputFixedAreaHeight;
17
+ const [availableLines, setAvailableLines] = useState(currentAvailableLines);
18
+ const wrapperRef = useCallback((node) => {
19
+ if (node !== null) {
20
+ const { height } = measureElement(node);
21
+ if (wrapperHeight !== height) {
22
+ setWrapperHeight(height);
23
+ }
24
+ }
25
+ }, [wrapperHeight]);
26
+ const promptAreaRef = useCallback((node) => {
27
+ if (node !== null) {
28
+ const { height } = measureElement(node);
29
+ setPromptAreaHeight(height);
30
+ }
31
+ }, []);
32
+ const inputFixedAreaRef = useCallback((node) => {
33
+ if (node !== null) {
34
+ const { height } = measureElement(node);
35
+ // + 3 accounts for the margins inside the input elements and the last empty line of the terminal
36
+ setInputFixedAreaHeight(height + 3);
37
+ }
38
+ }, []);
39
+ const inputComponent = cloneElement(input, { availableLines, inputFixedAreaRef });
40
+ useLayoutEffect(() => {
41
+ function onResize() {
42
+ const newAvailableLines = stdout.rows - promptAreaHeight - inputFixedAreaHeight;
43
+ if (newAvailableLines !== availableLines) {
44
+ setAvailableLines(newAvailableLines);
45
+ }
46
+ }
47
+ onResize();
48
+ stdout.on('resize', onResize);
49
+ return () => {
50
+ stdout.off('resize', onResize);
51
+ };
52
+ }, [wrapperHeight, promptAreaHeight, stdout, availableLines, inputFixedAreaHeight]);
53
+ const { isAborted } = useAbortSignal(abortSignal);
54
+ // Object.keys on an array returns the indices as strings
55
+ const showInfoTable = infoTable && Object.keys(infoTable).length > 0;
56
+ return isAborted ? null : (React.createElement(Box, { flexDirection: "column", marginBottom: 1, ref: wrapperRef },
57
+ React.createElement(Box, { ref: promptAreaRef, flexDirection: "column" },
58
+ React.createElement(Box, null,
59
+ React.createElement(Box, { marginRight: 2 },
60
+ React.createElement(Text, null, "?")),
61
+ React.createElement(TokenizedText, { item: messageWithPunctuation(message) }),
62
+ header),
63
+ (showInfoTable || infoMessage || gitDiff) && state !== PromptState.Submitted ? (React.createElement(Box, { marginTop: 1, marginLeft: 3, paddingLeft: 2, borderStyle: "bold", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, flexDirection: "column", gap: 1 },
64
+ infoMessage ? React.createElement(InfoMessage, { message: infoMessage }) : null,
65
+ showInfoTable ? React.createElement(InfoTable, { table: infoTable }) : null,
66
+ gitDiff ? React.createElement(GitDiff, { gitDiff: gitDiff }) : null)) : null),
67
+ state === PromptState.Submitted && submittedAnswerLabel ? (React.createElement(Box, null,
68
+ React.createElement(Box, { marginRight: 2 },
69
+ React.createElement(Text, { color: "cyan" }, figures.tick)),
70
+ React.createElement(Text, { color: "cyan" }, submittedAnswerLabel))) : (React.createElement(Box, { marginTop: 1 }, inputComponent))));
71
+ };
72
+ export { PromptLayout };
73
+ //# sourceMappingURL=PromptLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptLayout.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/PromptLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["import {GitDiff, GitDiffProps} from './GitDiff.js'\nimport {InfoMessage, InfoMessageProps} from './InfoMessage.js'\nimport {InfoTable, InfoTableProps} from './InfoTable.js'\nimport {InlineToken, LinkToken, TokenItem, TokenizedText} from '../TokenizedText.js'\nimport {messageWithPunctuation} from '../../utilities.js'\nimport {AbortSignal} from '../../../../../public/node/abort.js'\nimport useAbortSignal from '../../hooks/use-abort-signal.js'\nimport {PromptState} from '../../hooks/use-prompt.js'\nimport React, {ReactElement, cloneElement, useCallback, useLayoutEffect, useState} from 'react'\nimport {Box, measureElement, Text, useStdout} from 'ink'\nimport figures from 'figures'\n\nexport type Message = TokenItem<Exclude<InlineToken, LinkToken>>\n\nexport interface PromptLayoutProps {\n message: Message\n infoTable?: InfoTableProps['table']\n abortSignal?: AbortSignal\n infoMessage?: InfoMessageProps['message']\n gitDiff?: GitDiffProps['gitDiff']\n header?: ReactElement | null\n state: PromptState\n submittedAnswerLabel?: string\n input: ReactElement\n}\n\nconst PromptLayout = ({\n message,\n infoTable,\n abortSignal,\n infoMessage,\n gitDiff,\n header,\n state,\n input,\n submittedAnswerLabel,\n}: PromptLayoutProps): ReactElement | null => {\n const {stdout} = useStdout()\n const [wrapperHeight, setWrapperHeight] = useState(0)\n const [promptAreaHeight, setPromptAreaHeight] = useState(0)\n const [inputFixedAreaHeight, setInputFixedAreaHeight] = useState(0)\n const currentAvailableLines = stdout.rows - promptAreaHeight - inputFixedAreaHeight\n const [availableLines, setAvailableLines] = useState(currentAvailableLines)\n\n const wrapperRef = useCallback(\n (node) => {\n if (node !== null) {\n const {height} = measureElement(node)\n if (wrapperHeight !== height) {\n setWrapperHeight(height)\n }\n }\n },\n [wrapperHeight],\n )\n\n const promptAreaRef = useCallback((node) => {\n if (node !== null) {\n const {height} = measureElement(node)\n setPromptAreaHeight(height)\n }\n }, [])\n\n const inputFixedAreaRef = useCallback((node) => {\n if (node !== null) {\n const {height} = measureElement(node)\n // + 3 accounts for the margins inside the input elements and the last empty line of the terminal\n setInputFixedAreaHeight(height + 3)\n }\n }, [])\n\n const inputComponent = cloneElement(input, {availableLines, inputFixedAreaRef})\n\n useLayoutEffect(() => {\n function onResize() {\n const newAvailableLines = stdout.rows - promptAreaHeight - inputFixedAreaHeight\n if (newAvailableLines !== availableLines) {\n setAvailableLines(newAvailableLines)\n }\n }\n\n onResize()\n\n stdout.on('resize', onResize)\n return () => {\n stdout.off('resize', onResize)\n }\n }, [wrapperHeight, promptAreaHeight, stdout, availableLines, inputFixedAreaHeight])\n\n const {isAborted} = useAbortSignal(abortSignal)\n // Object.keys on an array returns the indices as strings\n const showInfoTable = infoTable && Object.keys(infoTable).length > 0\n\n return isAborted ? null : (\n <Box flexDirection=\"column\" marginBottom={1} ref={wrapperRef}>\n <Box ref={promptAreaRef} flexDirection=\"column\">\n <Box>\n <Box marginRight={2}>\n <Text>?</Text>\n </Box>\n <TokenizedText item={messageWithPunctuation(message)} />\n {header}\n </Box>\n\n {(showInfoTable || infoMessage || gitDiff) && state !== PromptState.Submitted ? (\n <Box\n marginTop={1}\n marginLeft={3}\n paddingLeft={2}\n borderStyle=\"bold\"\n borderLeft\n borderRight={false}\n borderTop={false}\n borderBottom={false}\n flexDirection=\"column\"\n gap={1}\n >\n {infoMessage ? <InfoMessage message={infoMessage} /> : null}\n {showInfoTable ? <InfoTable table={infoTable} /> : null}\n {gitDiff ? <GitDiff gitDiff={gitDiff} /> : null}\n </Box>\n ) : null}\n </Box>\n\n {state === PromptState.Submitted && submittedAnswerLabel ? (\n <Box>\n <Box marginRight={2}>\n <Text color=\"cyan\">{figures.tick}</Text>\n </Box>\n\n <Text color=\"cyan\">{submittedAnswerLabel}</Text>\n </Box>\n ) : (\n <Box marginTop={1}>{inputComponent}</Box>\n )}\n </Box>\n )\n}\n\nexport {PromptLayout}\n"]}
@@ -0,0 +1,129 @@
1
+ import { PromptLayout } from './PromptLayout.js';
2
+ import { render } from '../../../testing/ui.js';
3
+ import { PromptState } from '../../hooks/use-prompt.js';
4
+ import { describe, expect, test } from 'vitest';
5
+ import React from 'react';
6
+ import { Box, Text } from 'ink';
7
+ describe('PromptLayout', async () => {
8
+ test("doesn't add unnecessary margins when infoTable is an empty array", async () => {
9
+ const items = [
10
+ { label: 'first', value: 'first' },
11
+ { label: 'second', value: 'second' },
12
+ { label: 'third', value: 'third' },
13
+ { label: 'fourth', value: 'fourth' },
14
+ ];
15
+ const renderInstance = render(React.createElement(PromptLayout, { message: "Associate your project with the org Castile Ventures?", infoTable: [], state: PromptState.Idle, input: React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Text, { key: item.value }, item.label)))) }));
16
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
17
+ "? Associate your project with the org Castile Ventures?
18
+
19
+ first
20
+ second
21
+ third
22
+ fourth
23
+ "
24
+ `);
25
+ });
26
+ test("doesn't add unnecessary margins when infoTable is an empty object", async () => {
27
+ const items = [
28
+ { label: 'first', value: 'first' },
29
+ { label: 'second', value: 'second' },
30
+ { label: 'third', value: 'third' },
31
+ { label: 'fourth', value: 'fourth' },
32
+ ];
33
+ const renderInstance = render(React.createElement(PromptLayout, { message: "Associate your project with the org Castile Ventures?", infoTable: {}, state: PromptState.Idle, input: React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Text, { key: item.value }, item.label)))) }));
34
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
35
+ "? Associate your project with the org Castile Ventures?
36
+
37
+ first
38
+ second
39
+ third
40
+ fourth
41
+ "
42
+ `);
43
+ });
44
+ test("doesn't add unnecessary margins when infoTable is empty and there are other elements in the header", async () => {
45
+ const items = [
46
+ { label: 'first', value: 'first' },
47
+ { label: 'second', value: 'second' },
48
+ { label: 'third', value: 'third' },
49
+ { label: 'fourth', value: 'fourth' },
50
+ ];
51
+ const gitDiff = {
52
+ baselineContent: 'hello',
53
+ updatedContent: 'hello',
54
+ };
55
+ const infoMessage = {
56
+ title: { text: 'some title' },
57
+ body: 'some body',
58
+ };
59
+ const renderInstance = render(React.createElement(PromptLayout, { message: "Associate your project with the org Castile Ventures?", infoTable: [], infoMessage: infoMessage, gitDiff: gitDiff, state: PromptState.Idle, input: React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Text, { key: item.value }, item.label)))) }));
60
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
61
+ "? Associate your project with the org Castile Ventures?
62
+
63
+ ┃ some title
64
+
65
+ ┃ some body
66
+
67
+ ┃ No changes.
68
+
69
+ first
70
+ second
71
+ third
72
+ fourth
73
+ "
74
+ `);
75
+ });
76
+ test('can have all elements visible in the header at the same time', async () => {
77
+ const items = [
78
+ { label: 'first', value: 'first' },
79
+ { label: 'second', value: 'second' },
80
+ { label: 'third', value: 'third' },
81
+ { label: 'fourth', value: 'fourth' },
82
+ ];
83
+ const gitDiff = {
84
+ baselineContent: 'hello',
85
+ updatedContent: 'hello',
86
+ };
87
+ const infoMessage = {
88
+ title: { text: 'some title' },
89
+ body: 'some body',
90
+ };
91
+ const infoTable = {
92
+ header1: ['item 1', 'item 2', 'item 3'],
93
+ header2: ['item 4', 'item 5', 'item 6'],
94
+ header3: ['item 7', 'item 8', 'item 9'],
95
+ };
96
+ const renderInstance = render(React.createElement(PromptLayout, { message: "Associate your project with the org Castile Ventures?", infoTable: infoTable, infoMessage: infoMessage, gitDiff: gitDiff, state: PromptState.Idle, input: React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Text, { key: item.value }, item.label)))) }));
97
+ expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
98
+ "? Associate your project with the org Castile Ventures?
99
+
100
+ ┃ some title
101
+
102
+ ┃ some body
103
+
104
+ ┃ Header1
105
+ ┃ • item 1
106
+ ┃ • item 2
107
+ ┃ • item 3
108
+
109
+ ┃ Header2
110
+ ┃ • item 4
111
+ ┃ • item 5
112
+ ┃ • item 6
113
+
114
+ ┃ Header3
115
+ ┃ • item 7
116
+ ┃ • item 8
117
+ ┃ • item 9
118
+
119
+ ┃ No changes.
120
+
121
+ first
122
+ second
123
+ third
124
+ fourth
125
+ "
126
+ `);
127
+ });
128
+ });
129
+ //# sourceMappingURL=PromptLayout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptLayout.test.js","sourceRoot":"","sources":["../../../../../../src/private/node/ui/components/Prompts/PromptLayout.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {PromptLayout} from './PromptLayout.js'\nimport {render} from '../../../testing/ui.js'\nimport {PromptState} from '../../hooks/use-prompt.js'\nimport {describe, expect, test} from 'vitest'\nimport React from 'react'\nimport {Box, Text} from 'ink'\n\ndescribe('PromptLayout', async () => {\n test(\"doesn't add unnecessary margins when infoTable is an empty array\", async () => {\n const items = [\n {label: 'first', value: 'first'},\n {label: 'second', value: 'second'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n ]\n\n const renderInstance = render(\n <PromptLayout\n message=\"Associate your project with the org Castile Ventures?\"\n infoTable={[]}\n state={PromptState.Idle}\n input={\n <Box flexDirection=\"column\">\n {items.map((item) => (\n <Text key={item.value}>{item.label}</Text>\n ))}\n </Box>\n }\n />,\n )\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n\n first\n second\n third\n fourth\n \"\n `)\n })\n\n test(\"doesn't add unnecessary margins when infoTable is an empty object\", async () => {\n const items = [\n {label: 'first', value: 'first'},\n {label: 'second', value: 'second'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n ]\n\n const renderInstance = render(\n <PromptLayout\n message=\"Associate your project with the org Castile Ventures?\"\n infoTable={{}}\n state={PromptState.Idle}\n input={\n <Box flexDirection=\"column\">\n {items.map((item) => (\n <Text key={item.value}>{item.label}</Text>\n ))}\n </Box>\n }\n />,\n )\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n\n first\n second\n third\n fourth\n \"\n `)\n })\n\n test(\"doesn't add unnecessary margins when infoTable is empty and there are other elements in the header\", async () => {\n const items = [\n {label: 'first', value: 'first'},\n {label: 'second', value: 'second'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n ]\n\n const gitDiff = {\n baselineContent: 'hello',\n updatedContent: 'hello',\n }\n\n const infoMessage = {\n title: {text: 'some title'},\n body: 'some body',\n }\n\n const renderInstance = render(\n <PromptLayout\n message=\"Associate your project with the org Castile Ventures?\"\n infoTable={[]}\n infoMessage={infoMessage}\n gitDiff={gitDiff}\n state={PromptState.Idle}\n input={\n <Box flexDirection=\"column\">\n {items.map((item) => (\n <Text key={item.value}>{item.label}</Text>\n ))}\n </Box>\n }\n />,\n )\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n\n ┃ some title\n ┃\n ┃ some body\n ┃\n ┃ No changes.\n\n first\n second\n third\n fourth\n \"\n `)\n })\n\n test('can have all elements visible in the header at the same time', async () => {\n const items = [\n {label: 'first', value: 'first'},\n {label: 'second', value: 'second'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n ]\n\n const gitDiff = {\n baselineContent: 'hello',\n updatedContent: 'hello',\n }\n\n const infoMessage = {\n title: {text: 'some title'},\n body: 'some body',\n }\n\n const infoTable = {\n header1: ['item 1', 'item 2', 'item 3'],\n header2: ['item 4', 'item 5', 'item 6'],\n header3: ['item 7', 'item 8', 'item 9'],\n }\n\n const renderInstance = render(\n <PromptLayout\n message=\"Associate your project with the org Castile Ventures?\"\n infoTable={infoTable}\n infoMessage={infoMessage}\n gitDiff={gitDiff}\n state={PromptState.Idle}\n input={\n <Box flexDirection=\"column\">\n {items.map((item) => (\n <Text key={item.value}>{item.label}</Text>\n ))}\n </Box>\n }\n />,\n )\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n\n ┃ some title\n ┃\n ┃ some body\n ┃\n ┃ \u001b[1mHeader1\u001b[22m\n ┃ • item 1\n ┃ • item 2\n ┃ • item 3\n ┃\n ┃ \u001b[1mHeader2\u001b[22m\n ┃ • item 4\n ┃ • item 5\n ┃ • item 6\n ┃\n ┃ \u001b[1mHeader3\u001b[22m\n ┃ • item 7\n ┃ • item 8\n ┃ • item 9\n ┃\n ┃ No changes.\n\n first\n second\n third\n fourth\n \"\n `)\n })\n})\n"]}
@@ -0,0 +1,10 @@
1
+ import { FunctionComponent } from 'react';
2
+ export interface ScrollbarProps {
3
+ containerHeight: number;
4
+ visibleListSectionLength: number;
5
+ fullListLength: number;
6
+ visibleFromIndex: number;
7
+ noColor?: boolean;
8
+ }
9
+ declare const Scrollbar: FunctionComponent<ScrollbarProps>;
10
+ export { Scrollbar };
@@ -0,0 +1,44 @@
1
+ import { shouldDisplayColors } from '../../../../public/node/output.js';
2
+ import { Box, Text } from 'ink';
3
+ import React from 'react';
4
+ const BACKGROUND_CHAR = '│';
5
+ const SCROLLBOX_CHAR = '║';
6
+ const Scrollbar = ({ containerHeight, visibleListSectionLength, fullListLength, visibleFromIndex, noColor = !shouldDisplayColors(), }) => {
7
+ const displayArrows = containerHeight >= 4 && noColor;
8
+ const visibleToIndex = visibleFromIndex + visibleListSectionLength - 1;
9
+ // Leave 2 rows for top/bottom arrows when there is vertical room for them.
10
+ const fullHeight = displayArrows ? containerHeight - 2 : containerHeight;
11
+ const scrollboxHeight = Math.min(fullHeight - 1, Math.ceil(Math.min(1, visibleListSectionLength / fullListLength) * fullHeight));
12
+ let topBuffer;
13
+ // Ensure it scrolls all the way to the bottom when we hit the bottom
14
+ if (visibleToIndex >= fullListLength - 1) {
15
+ topBuffer = fullHeight - scrollboxHeight;
16
+ }
17
+ else {
18
+ // This is the actual number of rows available for the scrollbar to go up and down
19
+ const scrollingLength = fullHeight - scrollboxHeight;
20
+ // This is the number of times the screen itself can scroll down
21
+ const scrollableIncrements = fullListLength - visibleListSectionLength;
22
+ topBuffer = Math.max(
23
+ // Never go negative, that causes errors!
24
+ 0, Math.min(
25
+ // Never have more buffer than filling in all spaces above the scrollbox
26
+ fullHeight - scrollboxHeight, Math.round((visibleFromIndex / scrollableIncrements) * scrollingLength)));
27
+ }
28
+ const bottomBuffer = fullHeight - scrollboxHeight - topBuffer;
29
+ const backgroundChar = noColor ? BACKGROUND_CHAR : ' ';
30
+ const scrollboxChar = noColor ? SCROLLBOX_CHAR : ' ';
31
+ const bgColor = noColor ? undefined : 'gray';
32
+ const scrollboxColor = noColor ? undefined : 'cyan';
33
+ return (React.createElement(Box, { flexDirection: "column" },
34
+ displayArrows ? React.createElement(Text, null, "\u25B3") : null,
35
+ React.createElement(Box, { width: 1 },
36
+ React.createElement(Text, { backgroundColor: bgColor }, backgroundChar.repeat(topBuffer))),
37
+ React.createElement(Box, { width: 1 },
38
+ React.createElement(Text, { backgroundColor: scrollboxColor }, scrollboxChar.repeat(scrollboxHeight))),
39
+ React.createElement(Box, { width: 1 },
40
+ React.createElement(Text, { backgroundColor: bgColor }, backgroundChar.repeat(bottomBuffer))),
41
+ displayArrows ? React.createElement(Text, null, "\u25BD") : null));
42
+ };
43
+ export { Scrollbar };
44
+ //# sourceMappingURL=Scrollbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scrollbar.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Scrollbar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {shouldDisplayColors} from '../../../../public/node/output.js'\nimport {Box, Text} from 'ink'\nimport React, {FunctionComponent} from 'react'\n\nexport interface ScrollbarProps {\n containerHeight: number\n visibleListSectionLength: number\n fullListLength: number\n visibleFromIndex: number\n noColor?: boolean\n}\n\nconst BACKGROUND_CHAR = '│'\nconst SCROLLBOX_CHAR = '║'\n\nconst Scrollbar: FunctionComponent<ScrollbarProps> = ({\n containerHeight,\n visibleListSectionLength,\n fullListLength,\n visibleFromIndex,\n noColor = !shouldDisplayColors(),\n}) => {\n const displayArrows = containerHeight >= 4 && noColor\n const visibleToIndex = visibleFromIndex + visibleListSectionLength - 1\n\n // Leave 2 rows for top/bottom arrows when there is vertical room for them.\n const fullHeight = displayArrows ? containerHeight - 2 : containerHeight\n const scrollboxHeight = Math.min(\n fullHeight - 1,\n Math.ceil(Math.min(1, visibleListSectionLength / fullListLength) * fullHeight),\n )\n\n let topBuffer: number\n // Ensure it scrolls all the way to the bottom when we hit the bottom\n if (visibleToIndex >= fullListLength - 1) {\n topBuffer = fullHeight - scrollboxHeight\n } else {\n // This is the actual number of rows available for the scrollbar to go up and down\n const scrollingLength = fullHeight - scrollboxHeight\n // This is the number of times the screen itself can scroll down\n const scrollableIncrements = fullListLength - visibleListSectionLength\n\n topBuffer = Math.max(\n // Never go negative, that causes errors!\n 0,\n Math.min(\n // Never have more buffer than filling in all spaces above the scrollbox\n fullHeight - scrollboxHeight,\n Math.round((visibleFromIndex / scrollableIncrements) * scrollingLength),\n ),\n )\n }\n const bottomBuffer = fullHeight - scrollboxHeight - topBuffer\n\n const backgroundChar = noColor ? BACKGROUND_CHAR : ' '\n const scrollboxChar = noColor ? SCROLLBOX_CHAR : ' '\n const bgColor = noColor ? undefined : 'gray'\n const scrollboxColor = noColor ? undefined : 'cyan'\n\n return (\n <Box flexDirection=\"column\">\n {displayArrows ? <Text>△</Text> : null}\n\n <Box width={1}>\n <Text backgroundColor={bgColor}>{backgroundChar.repeat(topBuffer)}</Text>\n </Box>\n <Box width={1}>\n <Text backgroundColor={scrollboxColor}>{scrollboxChar.repeat(scrollboxHeight)}</Text>\n </Box>\n <Box width={1}>\n <Text backgroundColor={bgColor}>{backgroundChar.repeat(bottomBuffer)}</Text>\n </Box>\n\n {displayArrows ? <Text>▽</Text> : null}\n </Box>\n )\n}\n\nexport {Scrollbar}\n"]}
@@ -0,0 +1,96 @@
1
+ import { Scrollbar } from './Scrollbar.js';
2
+ import { render } from '../../testing/ui.js';
3
+ import { describe, expect, test } from 'vitest';
4
+ import React from 'react';
5
+ describe('Scrollbar', async () => {
6
+ test('renders correctly when at the top of the list', async () => {
7
+ const options = {
8
+ containerHeight: 10,
9
+ visibleListSectionLength: 10,
10
+ fullListLength: 50,
11
+ visibleFromIndex: 0,
12
+ };
13
+ const { lastFrame } = render(React.createElement(Scrollbar, { ...options }));
14
+ // First 2 are colored in
15
+ expect(lastFrame()).toMatchInlineSnapshot(`
16
+ "\u001b[46m \u001b[49m
17
+ \u001b[46m \u001b[49m
18
+ \u001b[100m \u001b[49m
19
+ \u001b[100m \u001b[49m
20
+ \u001b[100m \u001b[49m
21
+ \u001b[100m \u001b[49m
22
+ \u001b[100m \u001b[49m
23
+ \u001b[100m \u001b[49m
24
+ \u001b[100m \u001b[49m
25
+ \u001b[100m \u001b[49m"
26
+ `);
27
+ });
28
+ test('renders correctly when in the middle of the list', async () => {
29
+ const options = {
30
+ containerHeight: 10,
31
+ visibleListSectionLength: 10,
32
+ fullListLength: 50,
33
+ visibleFromIndex: 20,
34
+ };
35
+ const { lastFrame } = render(React.createElement(Scrollbar, { ...options }));
36
+ // Scrollbar is in the middle
37
+ expect(lastFrame()).toMatchInlineSnapshot(`
38
+ "\u001b[100m \u001b[49m
39
+ \u001b[100m \u001b[49m
40
+ \u001b[100m \u001b[49m
41
+ \u001b[100m \u001b[49m
42
+ \u001b[46m \u001b[49m
43
+ \u001b[46m \u001b[49m
44
+ \u001b[100m \u001b[49m
45
+ \u001b[100m \u001b[49m
46
+ \u001b[100m \u001b[49m
47
+ \u001b[100m \u001b[49m"
48
+ `);
49
+ });
50
+ test('renders correctly when at the bottom of the list', async () => {
51
+ const options = {
52
+ containerHeight: 10,
53
+ visibleListSectionLength: 10,
54
+ fullListLength: 50,
55
+ visibleFromIndex: 40,
56
+ };
57
+ const { lastFrame } = render(React.createElement(Scrollbar, { ...options }));
58
+ // Last 2 are colored in
59
+ expect(lastFrame()).toMatchInlineSnapshot(`
60
+ "\u001b[100m \u001b[49m
61
+ \u001b[100m \u001b[49m
62
+ \u001b[100m \u001b[49m
63
+ \u001b[100m \u001b[49m
64
+ \u001b[100m \u001b[49m
65
+ \u001b[100m \u001b[49m
66
+ \u001b[100m \u001b[49m
67
+ \u001b[100m \u001b[49m
68
+ \u001b[46m \u001b[49m
69
+ \u001b[46m \u001b[49m"
70
+ `);
71
+ });
72
+ test('renders correctly in the middle of the list in no-color mode', async () => {
73
+ const options = {
74
+ containerHeight: 10,
75
+ visibleListSectionLength: 10,
76
+ fullListLength: 50,
77
+ visibleFromIndex: 20,
78
+ noColor: true,
79
+ };
80
+ const { lastFrame } = render(React.createElement(Scrollbar, { ...options }));
81
+ // Scrollbar is in the middle
82
+ expect(lastFrame()).toMatchInlineSnapshot(`
83
+ "△
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+ ▽"
93
+ `);
94
+ });
95
+ });
96
+ //# sourceMappingURL=Scrollbar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scrollbar.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Scrollbar.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,MAAM,EAAC,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;IAC/B,IAAI,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,OAAO,GAAG;YACd,eAAe,EAAE,EAAE;YACnB,wBAAwB,EAAE,EAAE;YAC5B,cAAc,EAAE,EAAE;YAClB,gBAAgB,EAAE,CAAC;SACpB,CAAA;QAED,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CAAC,oBAAC,SAAS,OAAK,OAAO,GAAI,CAAC,CAAA;QAEtD,yBAAyB;QACzB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;KAWzC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,OAAO,GAAG;YACd,eAAe,EAAE,EAAE;YACnB,wBAAwB,EAAE,EAAE;YAC5B,cAAc,EAAE,EAAE;YAClB,gBAAgB,EAAE,EAAE;SACrB,CAAA;QAED,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CAAC,oBAAC,SAAS,OAAK,OAAO,GAAI,CAAC,CAAA;QAEtD,6BAA6B;QAC7B,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;KAWzC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,OAAO,GAAG;YACd,eAAe,EAAE,EAAE;YACnB,wBAAwB,EAAE,EAAE;YAC5B,cAAc,EAAE,EAAE;YAClB,gBAAgB,EAAE,EAAE;SACrB,CAAA;QAED,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CAAC,oBAAC,SAAS,OAAK,OAAO,GAAI,CAAC,CAAA;QAEtD,wBAAwB;QACxB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;KAWzC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,OAAO,GAAG;YACd,eAAe,EAAE,EAAE;YACnB,wBAAwB,EAAE,EAAE;YAC5B,cAAc,EAAE,EAAE;YAClB,gBAAgB,EAAE,EAAE;YACpB,OAAO,EAAE,IAAI;SACd,CAAA;QAED,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CAAC,oBAAC,SAAS,OAAK,OAAO,GAAI,CAAC,CAAA;QACtD,6BAA6B;QAC7B,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;KAWzC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {Scrollbar} from './Scrollbar.js'\nimport {render} from '../../testing/ui.js'\nimport {describe, expect, test} from 'vitest'\nimport React from 'react'\n\ndescribe('Scrollbar', async () => {\n test('renders correctly when at the top of the list', async () => {\n const options = {\n containerHeight: 10,\n visibleListSectionLength: 10,\n fullListLength: 50,\n visibleFromIndex: 0,\n }\n\n const {lastFrame} = render(<Scrollbar {...options} />)\n\n // First 2 are colored in\n expect(lastFrame()).toMatchInlineSnapshot(`\n \"\\u001b[46m \\u001b[49m\n \\u001b[46m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\"\n `)\n })\n\n test('renders correctly when in the middle of the list', async () => {\n const options = {\n containerHeight: 10,\n visibleListSectionLength: 10,\n fullListLength: 50,\n visibleFromIndex: 20,\n }\n\n const {lastFrame} = render(<Scrollbar {...options} />)\n\n // Scrollbar is in the middle\n expect(lastFrame()).toMatchInlineSnapshot(`\n \"\\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[46m \\u001b[49m\n \\u001b[46m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\"\n `)\n })\n\n test('renders correctly when at the bottom of the list', async () => {\n const options = {\n containerHeight: 10,\n visibleListSectionLength: 10,\n fullListLength: 50,\n visibleFromIndex: 40,\n }\n\n const {lastFrame} = render(<Scrollbar {...options} />)\n\n // Last 2 are colored in\n expect(lastFrame()).toMatchInlineSnapshot(`\n \"\\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[100m \\u001b[49m\n \\u001b[46m \\u001b[49m\n \\u001b[46m \\u001b[49m\"\n `)\n })\n\n test('renders correctly in the middle of the list in no-color mode', async () => {\n const options = {\n containerHeight: 10,\n visibleListSectionLength: 10,\n fullListLength: 50,\n visibleFromIndex: 20,\n noColor: true,\n }\n\n const {lastFrame} = render(<Scrollbar {...options} />)\n // Scrollbar is in the middle\n expect(lastFrame()).toMatchInlineSnapshot(`\n \"△\n │\n │\n │\n ║\n ║\n │\n │\n │\n ▽\"\n `)\n })\n})\n"]}
@@ -5,6 +5,7 @@ declare module 'react' {
5
5
  }
6
6
  export interface SelectInputProps<T> {
7
7
  items: Item<T>[];
8
+ initialItems?: Item<T>[];
8
9
  onChange?: (item: Item<T> | undefined) => void;
9
10
  enableShortcuts?: boolean;
10
11
  focus?: boolean;
@@ -15,10 +16,9 @@ export interface SelectInputProps<T> {
15
16
  errorMessage?: string;
16
17
  hasMorePages?: boolean;
17
18
  morePagesMessage?: string;
18
- infoMessage?: string;
19
- limit?: number;
20
- submitWithShortcuts?: boolean;
19
+ availableLines?: number;
21
20
  onSubmit?: (item: Item<T>) => void;
21
+ inputFixedAreaRef?: React.RefObject<DOMElement>;
22
22
  }
23
23
  export interface Item<T> {
24
24
  label: string;
@@ -28,7 +28,4 @@ export interface Item<T> {
28
28
  helperText?: string;
29
29
  disabled?: boolean;
30
30
  }
31
- export interface ItemWithKey<T> extends Item<T> {
32
- key: string;
33
- }
34
31
  export declare const SelectInput: <T>(props: SelectInputProps<T> & React.RefAttributes<DOMElement>) => JSX.Element | null;