inputdialog.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Widget } from '@phosphor/widgets';
  4. import { Dialog } from './dialog';
  5. import { Styling } from './styling';
  6. const INPUT_DIALOG_CLASS = 'jp-Input-Dialog';
  7. /**
  8. * Create and show a input dialog for a number.
  9. *
  10. * @param options - The dialog setup options.
  11. *
  12. * @returns A promise that resolves with whether the dialog was accepted
  13. */
  14. export function getNumber(
  15. options: InputDialog.INumberOptions
  16. ): Promise<Dialog.IResult<number>> {
  17. let dialog = new Dialog({
  18. ...options,
  19. body: new InputNumberDialog(options)
  20. });
  21. return dialog.launch();
  22. }
  23. /**
  24. * Create and show a input dialog for a choice.
  25. *
  26. * @param options - The dialog setup options.
  27. *
  28. * @returns A promise that resolves with whether the dialog was accepted
  29. */
  30. export function getItem(
  31. options: InputDialog.IItemOptions
  32. ): Promise<Dialog.IResult<string>> {
  33. let dialog = new Dialog({
  34. ...options,
  35. body: new InputItemsDialog(options)
  36. });
  37. return dialog.launch();
  38. }
  39. /**
  40. * Create and show a input dialog for a text.
  41. *
  42. * @param options - The dialog setup options.
  43. *
  44. * @returns A promise that resolves with whether the dialog was accepted
  45. */
  46. export function getText(
  47. options: InputDialog.ITextOptions
  48. ): Promise<Dialog.IResult<string>> {
  49. let dialog = new Dialog({
  50. ...options,
  51. body: new InputTextDialog(options)
  52. });
  53. return dialog.launch();
  54. }
  55. export namespace InputDialog {
  56. export interface IOptions<T>
  57. extends Partial<
  58. Pick<
  59. Dialog.IOptions<T>,
  60. Exclude<keyof Dialog.IOptions<T>, 'body' | 'buttons' | 'defaultButton'>
  61. >
  62. > {
  63. /**
  64. * Label of the requested input
  65. */
  66. label: string;
  67. }
  68. export interface INumberOptions extends IOptions<Number> {
  69. /**
  70. * Default value
  71. */
  72. value?: number;
  73. }
  74. export interface IItemOptions extends IOptions<string> {
  75. /**
  76. * List of choices
  77. */
  78. items: Array<string>;
  79. /**
  80. * Default choice
  81. *
  82. * If the list is editable a string with a default value can be provided
  83. * otherwise the index of the default choice should be given.
  84. */
  85. current?: number | string;
  86. /**
  87. * Is the item editable?
  88. */
  89. editable?: boolean;
  90. /**
  91. * Placeholder text for editable input
  92. */
  93. placeholder?: string;
  94. }
  95. export interface ITextOptions extends IOptions<string> {
  96. /**
  97. * Default input text
  98. */
  99. text?: string;
  100. /**
  101. * Placeholder text
  102. */
  103. placeholder?: string;
  104. }
  105. }
  106. /**
  107. * Base widget for input dialog body
  108. */
  109. class InputDialog<T> extends Widget implements Dialog.IBodyWidget<T> {
  110. /**
  111. * InputDialog constructor
  112. *
  113. * @param label Input field label
  114. */
  115. constructor(label: string) {
  116. super();
  117. this.addClass(INPUT_DIALOG_CLASS);
  118. let labelElement = document.createElement('label');
  119. labelElement.textContent = label;
  120. // Initialize the node
  121. this.node.appendChild(labelElement);
  122. }
  123. /** Input HTML node */
  124. protected _input: HTMLInputElement;
  125. }
  126. /**
  127. * Widget body for input number dialog
  128. */
  129. class InputNumberDialog extends InputDialog<number> {
  130. /**
  131. * InputNumberDialog constructor
  132. *
  133. * @param options Constructor options
  134. */
  135. constructor(options: InputDialog.INumberOptions) {
  136. super(options.label);
  137. this._input = document.createElement('input', {});
  138. this._input.classList.add('jp-mod-styled');
  139. this._input.type = 'number';
  140. this._input.value = options.value ? options.value.toString() : '0';
  141. // Initialize the node
  142. this.node.appendChild(this._input);
  143. }
  144. /**
  145. * Get the number specified by the user.
  146. */
  147. getValue(): number {
  148. if (this._input.value) {
  149. return Number(this._input.value);
  150. } else {
  151. return Number.NaN;
  152. }
  153. }
  154. }
  155. /**
  156. * Widget body for input text dialog
  157. */
  158. class InputTextDialog extends InputDialog<string> {
  159. /**
  160. * InputTextDialog constructor
  161. *
  162. * @param options Constructor options
  163. */
  164. constructor(options: InputDialog.ITextOptions) {
  165. super(options.label);
  166. this._input = document.createElement('input', {});
  167. this._input.classList.add('jp-mod-styled');
  168. this._input.type = 'text';
  169. this._input.value = options.text ? options.text : '';
  170. if (options.placeholder) {
  171. this._input.placeholder = options.placeholder;
  172. }
  173. // Initialize the node
  174. this.node.appendChild(this._input);
  175. }
  176. /**
  177. * Get the text specified by the user
  178. */
  179. getValue(): string {
  180. return this._input.value;
  181. }
  182. }
  183. /**
  184. * Widget body for input list dialog
  185. */
  186. class InputItemsDialog extends InputDialog<string> {
  187. /**
  188. * InputItemsDialog constructor
  189. *
  190. * @param options Constructor options
  191. */
  192. constructor(options: InputDialog.IItemOptions) {
  193. super(options.label);
  194. this._editable = options.editable || false;
  195. let current = options.current || 0;
  196. let defaultIndex: number;
  197. if (typeof current === 'number') {
  198. defaultIndex = Math.max(0, Math.min(current, options.items.length - 1));
  199. current = '';
  200. }
  201. this._list = document.createElement('select');
  202. options.items.forEach((item, index) => {
  203. let option = document.createElement('option');
  204. if (index === defaultIndex) {
  205. option.selected = true;
  206. current = item;
  207. }
  208. option.value = item;
  209. option.textContent = item;
  210. this._list.appendChild(option);
  211. });
  212. if (options.editable) {
  213. /* Use of list and datalist */
  214. let data = document.createElement('datalist');
  215. data.id = 'input-dialog-items';
  216. data.appendChild(this._list);
  217. this._input = document.createElement('input', {});
  218. this._input.classList.add('jp-mod-styled');
  219. this._input.type = 'list';
  220. this._input.value = current;
  221. this._input.setAttribute('list', data.id);
  222. if (options.placeholder) {
  223. this._input.placeholder = options.placeholder;
  224. }
  225. this.node.appendChild(this._input);
  226. this.node.appendChild(data);
  227. } else {
  228. /* Use select directly */
  229. this.node.appendChild(Styling.wrapSelect(this._list));
  230. }
  231. }
  232. /**
  233. * Get the user choice
  234. */
  235. getValue(): string {
  236. if (this._editable) {
  237. return this._input.value;
  238. } else {
  239. return this._list.value;
  240. }
  241. }
  242. private _list: HTMLSelectElement;
  243. private _editable: boolean;
  244. }