blob: 17e018141123a21f111f0bd355cd367e1da79d4e [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
Paul Lewis9950e182019-12-16 16:06:074
Paul Lewis17e384e2020-01-08 15:46:515import * as Common from '../common/common.js';
Tim van der Lippeaa76aa22020-02-14 14:38:246
7import * as ARIAUtils from './ARIAUtils.js';
Paul Lewis9950e182019-12-16 16:06:078import {Toolbar, ToolbarButton} from './Toolbar.js';
9import {createInput, createTextButton, ElementFocusRestorer} from './UIUtils.js';
10import {VBox} from './Widget.js';
11
Blink Reformat4c46d092018-04-07 15:32:3712/**
13 * @template T
14 */
Paul Lewis9950e182019-12-16 16:06:0715export class ListWidget extends VBox {
Blink Reformat4c46d092018-04-07 15:32:3716 /**
Tim van der Lippe0830b3d2019-10-03 13:20:0717 * @param {!Delegate<T>} delegate
Alex Rudenkocd7a2f92020-04-27 12:02:3718 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:3719 */
Alex Rudenkocd7a2f92020-04-27 12:02:3720 constructor(delegate, delegatesFocus = true) {
21 super(true, delegatesFocus);
Jack Franklin71519f82020-11-03 12:08:5922 this.registerRequiredCSS('ui/listWidget.css', {enableLegacyPatching: true});
Blink Reformat4c46d092018-04-07 15:32:3723 this._delegate = delegate;
24
25 this._list = this.contentElement.createChild('div', 'list');
Blink Reformat4c46d092018-04-07 15:32:3726
27 this._lastSeparator = false;
Paul Lewis9950e182019-12-16 16:06:0728 /** @type {?ElementFocusRestorer} */
Blink Reformat4c46d092018-04-07 15:32:3729 this._focusRestorer = null;
30 /** @type {!Array<T>} */
31 this._items = [];
32 /** @type {!Array<boolean>} */
33 this._editable = [];
34 /** @type {!Array<!Element>} */
35 this._elements = [];
Tim van der Lippe0830b3d2019-10-03 13:20:0736 /** @type {?Editor<T>} */
Blink Reformat4c46d092018-04-07 15:32:3737 this._editor = null;
38 /** @type {?T} */
39 this._editItem = null;
40 /** @type {?Element} */
41 this._editElement = null;
42
43 /** @type {?Element} */
44 this._emptyPlaceholder = null;
45
46 this._updatePlaceholder();
47 }
48
49 clear() {
50 this._items = [];
51 this._editable = [];
52 this._elements = [];
53 this._lastSeparator = false;
54 this._list.removeChildren();
55 this._updatePlaceholder();
56 this._stopEditing();
57 }
58
59 /**
60 * @param {!T} item
61 * @param {boolean} editable
62 */
63 appendItem(item, editable) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3464 if (this._lastSeparator && this._items.length) {
Tim van der Lippe47d01242020-05-01 16:06:4665 const element = document.createElement('div');
66 element.classList.add('list-separator');
67 this._list.appendChild(element);
Tim van der Lippe1d6e57a2019-09-30 11:55:3468 }
Blink Reformat4c46d092018-04-07 15:32:3769 this._lastSeparator = false;
70
71 this._items.push(item);
72 this._editable.push(editable);
73
74 const element = this._list.createChild('div', 'list-item');
75 element.appendChild(this._delegate.renderItem(item, editable));
76 if (editable) {
77 element.classList.add('editable');
Alex Rudenkoee06e412020-04-22 08:58:5278 element.tabIndex = 0;
Blink Reformat4c46d092018-04-07 15:32:3779 element.appendChild(this._createControls(item, element));
80 }
81 this._elements.push(element);
82 this._updatePlaceholder();
83 }
84
85 appendSeparator() {
86 this._lastSeparator = true;
87 }
88
89 /**
90 * @param {number} index
91 */
92 removeItem(index) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3493 if (this._editItem === this._items[index]) {
Blink Reformat4c46d092018-04-07 15:32:3794 this._stopEditing();
Tim van der Lippe1d6e57a2019-09-30 11:55:3495 }
Blink Reformat4c46d092018-04-07 15:32:3796
97 const element = this._elements[index];
98
99 const previous = element.previousElementSibling;
100 const previousIsSeparator = previous && previous.classList.contains('list-separator');
101
102 const next = element.nextElementSibling;
103 const nextIsSeparator = next && next.classList.contains('list-separator');
104
Tim van der Lippe1d6e57a2019-09-30 11:55:34105 if (previousIsSeparator && (nextIsSeparator || !next)) {
Simon Zünd3460a8d2020-08-26 07:07:35106 /** @type {!Element} */ (previous).remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:34107 }
108 if (nextIsSeparator && !previous) {
Simon Zünd3460a8d2020-08-26 07:07:35109 /** @type {!Element} */ (next).remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:34110 }
Blink Reformat4c46d092018-04-07 15:32:37111 element.remove();
112
113 this._elements.splice(index, 1);
114 this._items.splice(index, 1);
115 this._editable.splice(index, 1);
116 this._updatePlaceholder();
117 }
118
119 /**
120 * @param {number} index
121 * @param {!T} item
122 */
123 addNewItem(index, item) {
124 this._startEditing(item, null, this._elements[index] || null);
125 }
126
127 /**
128 * @param {?Element} element
129 */
130 setEmptyPlaceholder(element) {
131 this._emptyPlaceholder = element;
132 this._updatePlaceholder();
133 }
134
135 /**
136 * @param {!T} item
137 * @param {!Element} element
138 * @return {!Element}
139 */
140 _createControls(item, element) {
Tim van der Lippee7f27052020-05-01 15:15:28141 const controls = document.createElement('div');
142 controls.classList.add('controls-container');
143 controls.classList.add('fill');
Blink Reformat4c46d092018-04-07 15:32:37144 controls.createChild('div', 'controls-gradient');
145
146 const buttons = controls.createChild('div', 'controls-buttons');
147
Paul Lewis9950e182019-12-16 16:06:07148 const toolbar = new Toolbar('', buttons);
Blink Reformat4c46d092018-04-07 15:32:37149
Paul Lewis17e384e2020-01-08 15:46:51150 const editButton = new ToolbarButton(Common.UIString.UIString('Edit'), 'largeicon-edit');
Paul Lewis9950e182019-12-16 16:06:07151 editButton.addEventListener(ToolbarButton.Events.Click, onEditClicked.bind(this));
Blink Reformat4c46d092018-04-07 15:32:37152 toolbar.appendToolbarItem(editButton);
153
Paul Lewis17e384e2020-01-08 15:46:51154 const removeButton = new ToolbarButton(Common.UIString.UIString('Remove'), 'largeicon-trash-bin');
Paul Lewis9950e182019-12-16 16:06:07155 removeButton.addEventListener(ToolbarButton.Events.Click, onRemoveClicked.bind(this));
Blink Reformat4c46d092018-04-07 15:32:37156 toolbar.appendToolbarItem(removeButton);
157
158 return controls;
159
160 /**
Simon Zünd3460a8d2020-08-26 07:07:35161 * @this {!ListWidget<?>}
Blink Reformat4c46d092018-04-07 15:32:37162 */
163 function onEditClicked() {
164 const index = this._elements.indexOf(element);
165 const insertionPoint = this._elements[index + 1] || null;
166 this._startEditing(item, element, insertionPoint);
167 }
168
169 /**
Simon Zünd3460a8d2020-08-26 07:07:35170 * @this {!ListWidget<?>}
Blink Reformat4c46d092018-04-07 15:32:37171 */
172 function onRemoveClicked() {
173 const index = this._elements.indexOf(element);
174 this.element.focus();
175 this._delegate.removeItemRequested(this._items[index], index);
176 }
177 }
178
179 /**
180 * @override
181 */
182 wasShown() {
183 super.wasShown();
184 this._stopEditing();
185 }
186
187 _updatePlaceholder() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34188 if (!this._emptyPlaceholder) {
Blink Reformat4c46d092018-04-07 15:32:37189 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34190 }
Blink Reformat4c46d092018-04-07 15:32:37191
Tim van der Lippe1d6e57a2019-09-30 11:55:34192 if (!this._elements.length && !this._editor) {
Blink Reformat4c46d092018-04-07 15:32:37193 this._list.appendChild(this._emptyPlaceholder);
Tim van der Lippe1d6e57a2019-09-30 11:55:34194 } else {
Blink Reformat4c46d092018-04-07 15:32:37195 this._emptyPlaceholder.remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:34196 }
Blink Reformat4c46d092018-04-07 15:32:37197 }
198
199 /**
200 * @param {!T} item
201 * @param {?Element} element
202 * @param {?Element} insertionPoint
203 */
204 _startEditing(item, element, insertionPoint) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34205 if (element && this._editElement === element) {
Blink Reformat4c46d092018-04-07 15:32:37206 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34207 }
Blink Reformat4c46d092018-04-07 15:32:37208
209 this._stopEditing();
Paul Lewis9950e182019-12-16 16:06:07210 this._focusRestorer = new ElementFocusRestorer(this.element);
Blink Reformat4c46d092018-04-07 15:32:37211
212 this._list.classList.add('list-editing');
213 this._editItem = item;
214 this._editElement = element;
Tim van der Lippe1d6e57a2019-09-30 11:55:34215 if (element) {
Blink Reformat4c46d092018-04-07 15:32:37216 element.classList.add('hidden');
Tim van der Lippe1d6e57a2019-09-30 11:55:34217 }
Blink Reformat4c46d092018-04-07 15:32:37218
219 const index = element ? this._elements.indexOf(element) : -1;
220 this._editor = this._delegate.beginEdit(item);
221 this._updatePlaceholder();
222 this._list.insertBefore(this._editor.element, insertionPoint);
223 this._editor.beginEdit(
Paul Lewis17e384e2020-01-08 15:46:51224 item, index, element ? Common.UIString.UIString('Save') : Common.UIString.UIString('Add'),
225 this._commitEditing.bind(this), this._stopEditing.bind(this));
Blink Reformat4c46d092018-04-07 15:32:37226 }
227
228 _commitEditing() {
229 const editItem = this._editItem;
230 const isNew = !this._editElement;
Tim van der Lippe0830b3d2019-10-03 13:20:07231 const editor = /** @type {!Editor<T>} */ (this._editor);
Blink Reformat4c46d092018-04-07 15:32:37232 this._stopEditing();
Simon Zünd3460a8d2020-08-26 07:07:35233 if (editItem) {
234 this._delegate.commitEdit(editItem, editor, isNew);
235 }
Blink Reformat4c46d092018-04-07 15:32:37236 }
237
238 _stopEditing() {
239 this._list.classList.remove('list-editing');
Tim van der Lippe1d6e57a2019-09-30 11:55:34240 if (this._focusRestorer) {
Blink Reformat4c46d092018-04-07 15:32:37241 this._focusRestorer.restore();
Tim van der Lippe1d6e57a2019-09-30 11:55:34242 }
243 if (this._editElement) {
Blink Reformat4c46d092018-04-07 15:32:37244 this._editElement.classList.remove('hidden');
Tim van der Lippe1d6e57a2019-09-30 11:55:34245 }
246 if (this._editor && this._editor.element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37247 this._editor.element.remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:34248 }
Blink Reformat4c46d092018-04-07 15:32:37249
250 this._editor = null;
251 this._editItem = null;
252 this._editElement = null;
253 this._updatePlaceholder();
254 }
Tim van der Lippe0830b3d2019-10-03 13:20:07255}
Blink Reformat4c46d092018-04-07 15:32:37256
257/**
258 * @template T
259 * @interface
260 */
Tim van der Lippe0830b3d2019-10-03 13:20:07261export class Delegate {
Blink Reformat4c46d092018-04-07 15:32:37262 /**
263 * @param {!T} item
264 * @param {boolean} editable
265 * @return {!Element}
266 */
Tim van der Lippe0830b3d2019-10-03 13:20:07267 renderItem(item, editable) {
Simon Zünd3460a8d2020-08-26 07:07:35268 throw new Error('not implemented yet');
Tim van der Lippe0830b3d2019-10-03 13:20:07269 }
Blink Reformat4c46d092018-04-07 15:32:37270
271 /**
272 * @param {!T} item
273 * @param {number} index
274 */
Tim van der Lippe0830b3d2019-10-03 13:20:07275 removeItemRequested(item, index) {
276 }
Blink Reformat4c46d092018-04-07 15:32:37277
278 /**
279 * @param {!T} item
Tim van der Lippe0830b3d2019-10-03 13:20:07280 * @return {!Editor<T>}
Blink Reformat4c46d092018-04-07 15:32:37281 */
Tim van der Lippe0830b3d2019-10-03 13:20:07282 beginEdit(item) {
Simon Zünd3460a8d2020-08-26 07:07:35283 throw new Error('not implemented yet');
Tim van der Lippe0830b3d2019-10-03 13:20:07284 }
Blink Reformat4c46d092018-04-07 15:32:37285
286 /**
287 * @param {!T} item
Tim van der Lippe0830b3d2019-10-03 13:20:07288 * @param {!Editor<T>} editor
Blink Reformat4c46d092018-04-07 15:32:37289 * @param {boolean} isNew
290 */
291 commitEdit(item, editor, isNew) {}
Tim van der Lippe0830b3d2019-10-03 13:20:07292}
Blink Reformat4c46d092018-04-07 15:32:37293
294/**
295 * @template T
296 */
Tim van der Lippe0830b3d2019-10-03 13:20:07297export class Editor {
Blink Reformat4c46d092018-04-07 15:32:37298 constructor() {
Tim van der Lippef49e2322020-05-01 15:03:09299 this.element = document.createElement('div');
300 this.element.classList.add('editor-container');
Blink Reformat4c46d092018-04-07 15:32:37301 this.element.addEventListener('keydown', onKeyDown.bind(null, isEscKey, this._cancelClicked.bind(this)), false);
302 this.element.addEventListener('keydown', onKeyDown.bind(null, isEnterKey, this._commitClicked.bind(this)), false);
303
304 this._contentElement = this.element.createChild('div', 'editor-content');
305
306 const buttonsRow = this.element.createChild('div', 'editor-buttons');
Paul Lewis9950e182019-12-16 16:06:07307 this._commitButton = createTextButton('', this._commitClicked.bind(this), '', true /* primary */);
Blink Reformat4c46d092018-04-07 15:32:37308 buttonsRow.appendChild(this._commitButton);
Paul Lewis17e384e2020-01-08 15:46:51309 this._cancelButton = createTextButton(Common.UIString.UIString('Cancel'), this._cancelClicked.bind(this));
Blink Reformat4c46d092018-04-07 15:32:37310 this._cancelButton.addEventListener(
311 'keydown', onKeyDown.bind(null, isEnterKey, this._cancelClicked.bind(this)), false);
312 buttonsRow.appendChild(this._cancelButton);
313
Rob Pavezac671db32019-09-20 01:54:50314 this._errorMessageContainer = this.element.createChild('div', 'list-widget-input-validation-error');
Tim van der Lippeaa76aa22020-02-14 14:38:24315 ARIAUtils.markAsAlert(this._errorMessageContainer);
Rob Pavezac671db32019-09-20 01:54:50316
Blink Reformat4c46d092018-04-07 15:32:37317 /**
318 * @param {function(!Event):boolean} predicate
Tim van der Lippe403a88d2020-05-13 11:51:32319 * @param {function():void} callback
Blink Reformat4c46d092018-04-07 15:32:37320 * @param {!Event} event
321 */
322 function onKeyDown(predicate, callback, event) {
323 if (predicate(event)) {
324 event.consume(true);
325 callback();
326 }
327 }
328
329 /** @type {!Array<!HTMLInputElement|!HTMLSelectElement>} */
330 this._controls = [];
331 /** @type {!Map<string, !HTMLInputElement|!HTMLSelectElement>} */
332 this._controlByName = new Map();
Tim van der Lippeaa76aa22020-02-14 14:38:24333 /** @type {!Array<function(!T, number, (!HTMLInputElement|!HTMLSelectElement)): !ValidatorResult>} */
Blink Reformat4c46d092018-04-07 15:32:37334 this._validators = [];
335
Tim van der Lippeee97fa32020-04-23 15:20:56336 /** @type {?function():void} */
Blink Reformat4c46d092018-04-07 15:32:37337 this._commit = null;
Tim van der Lippeee97fa32020-04-23 15:20:56338 /** @type {?function():void} */
Blink Reformat4c46d092018-04-07 15:32:37339 this._cancel = null;
340 /** @type {?T} */
341 this._item = null;
342 /** @type {number} */
343 this._index = -1;
344 }
345
346 /**
347 * @return {!Element}
348 */
349 contentElement() {
350 return this._contentElement;
351 }
352
353 /**
354 * @param {string} name
355 * @param {string} type
356 * @param {string} title
Tim van der Lippeaa76aa22020-02-14 14:38:24357 * @param {function(!T, number, (!HTMLInputElement|!HTMLSelectElement)): !ValidatorResult} validator
Blink Reformat4c46d092018-04-07 15:32:37358 * @return {!HTMLInputElement}
359 */
360 createInput(name, type, title, validator) {
Paul Lewis9950e182019-12-16 16:06:07361 const input = /** @type {!HTMLInputElement} */ (createInput('', type));
Blink Reformat4c46d092018-04-07 15:32:37362 input.placeholder = title;
363 input.addEventListener('input', this._validateControls.bind(this, false), false);
364 input.addEventListener('blur', this._validateControls.bind(this, false), false);
Tim van der Lippeaa76aa22020-02-14 14:38:24365 ARIAUtils.setAccessibleName(input, title);
Blink Reformat4c46d092018-04-07 15:32:37366 this._controlByName.set(name, input);
367 this._controls.push(input);
368 this._validators.push(validator);
369 return input;
370 }
371
372 /**
373 * @param {string} name
374 * @param {!Array<string>} options
Tim van der Lippeaa76aa22020-02-14 14:38:24375 * @param {function(!T, number, (!HTMLInputElement|!HTMLSelectElement)): !ValidatorResult} validator
Amanda Bakerca502822019-07-02 00:01:28376 * @param {string=} title
Blink Reformat4c46d092018-04-07 15:32:37377 * @return {!HTMLSelectElement}
378 */
Amanda Bakerca502822019-07-02 00:01:28379 createSelect(name, options, validator, title) {
Tim van der Lippe47d01242020-05-01 16:06:46380 const select = /** @type {!HTMLSelectElement} */ (document.createElement('select'));
381 select.classList.add('chrome-select');
Blink Reformat4c46d092018-04-07 15:32:37382 for (let index = 0; index < options.length; ++index) {
Simon Zünd3460a8d2020-08-26 07:07:35383 const option = /** @type {!HTMLOptionElement} */ (select.createChild('option'));
Blink Reformat4c46d092018-04-07 15:32:37384 option.value = options[index];
385 option.textContent = options[index];
386 }
Amanda Bakerca502822019-07-02 00:01:28387 if (title) {
388 select.title = title;
Tim van der Lippeaa76aa22020-02-14 14:38:24389 ARIAUtils.setAccessibleName(select, title);
Amanda Bakerca502822019-07-02 00:01:28390 }
Blink Reformat4c46d092018-04-07 15:32:37391 select.addEventListener('input', this._validateControls.bind(this, false), false);
392 select.addEventListener('blur', this._validateControls.bind(this, false), false);
393 this._controlByName.set(name, select);
394 this._controls.push(select);
395 this._validators.push(validator);
396 return select;
397 }
398
399 /**
400 * @param {string} name
401 * @return {!HTMLInputElement|!HTMLSelectElement}
402 */
403 control(name) {
404 return /** @type {!HTMLInputElement|!HTMLSelectElement} */ (this._controlByName.get(name));
405 }
406
407 /**
408 * @param {boolean} forceValid
409 */
410 _validateControls(forceValid) {
411 let allValid = true;
Amanda Bakerca502822019-07-02 00:01:28412 this._errorMessageContainer.textContent = '';
Blink Reformat4c46d092018-04-07 15:32:37413 for (let index = 0; index < this._controls.length; ++index) {
414 const input = this._controls[index];
Simon Zünd3460a8d2020-08-26 07:07:35415 const {valid, errorMessage} =
416 this._validators[index].call(null, /** @type {!T} */ (this._item), this._index, input);
Amanda Bakerca502822019-07-02 00:01:28417
Blink Reformat4c46d092018-04-07 15:32:37418 input.classList.toggle('error-input', !valid && !forceValid);
Tim van der Lippe1d6e57a2019-09-30 11:55:34419 if (valid || forceValid) {
Tim van der Lippeaa76aa22020-02-14 14:38:24420 ARIAUtils.setInvalid(input, false);
Tim van der Lippe1d6e57a2019-09-30 11:55:34421 } else {
Tim van der Lippeaa76aa22020-02-14 14:38:24422 ARIAUtils.setInvalid(input, true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34423 }
Amanda Bakerca502822019-07-02 00:01:28424
Tim van der Lippe1d6e57a2019-09-30 11:55:34425 if (!forceValid && errorMessage && !this._errorMessageContainer.textContent) {
Amanda Bakerca502822019-07-02 00:01:28426 this._errorMessageContainer.textContent = errorMessage;
Tim van der Lippe1d6e57a2019-09-30 11:55:34427 }
Amanda Bakerca502822019-07-02 00:01:28428
Simon Zünd3460a8d2020-08-26 07:07:35429 allValid = allValid && valid;
Blink Reformat4c46d092018-04-07 15:32:37430 }
431 this._commitButton.disabled = !allValid;
432 }
433
434 /**
435 * @param {!T} item
436 * @param {number} index
437 * @param {string} commitButtonTitle
Tim van der Lippe403a88d2020-05-13 11:51:32438 * @param {function():void} commit
439 * @param {function():void} cancel
Blink Reformat4c46d092018-04-07 15:32:37440 */
441 beginEdit(item, index, commitButtonTitle, commit, cancel) {
442 this._commit = commit;
443 this._cancel = cancel;
444 this._item = item;
445 this._index = index;
446
447 this._commitButton.textContent = commitButtonTitle;
448 this.element.scrollIntoViewIfNeeded(false);
Tim van der Lippe1d6e57a2019-09-30 11:55:34449 if (this._controls.length) {
Blink Reformat4c46d092018-04-07 15:32:37450 this._controls[0].focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34451 }
Blink Reformat4c46d092018-04-07 15:32:37452 this._validateControls(true);
453 }
454
455 _commitClicked() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34456 if (this._commitButton.disabled) {
Blink Reformat4c46d092018-04-07 15:32:37457 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34458 }
Blink Reformat4c46d092018-04-07 15:32:37459
460 const commit = this._commit;
461 this._commit = null;
462 this._cancel = null;
463 this._item = null;
464 this._index = -1;
Simon Zünd3460a8d2020-08-26 07:07:35465 if (commit) {
466 commit();
467 }
Blink Reformat4c46d092018-04-07 15:32:37468 }
469
470 _cancelClicked() {
471 const cancel = this._cancel;
472 this._commit = null;
473 this._cancel = null;
474 this._item = null;
475 this._index = -1;
Simon Zünd3460a8d2020-08-26 07:07:35476 if (cancel) {
477 cancel();
478 }
Blink Reformat4c46d092018-04-07 15:32:37479 }
Tim van der Lippe0830b3d2019-10-03 13:20:07480}
Tim van der Lippe96f16602020-01-24 12:45:35481
482/** @typedef {{valid: boolean, errorMessage: (string|undefined)}} */
Simon Zünd3460a8d2020-08-26 07:07:35483// @ts-ignore typedef
Tim van der Lippe96f16602020-01-24 12:45:35484export let ValidatorResult;