UNPKG

81.4 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { Cell, CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells';
4import { TableOfContentsUtils } from '@jupyterlab/toc';
5import { nullTranslator } from '@jupyterlab/translation';
6import { WindowedList } from '@jupyterlab/ui-components';
7import { ArrayExt, findIndex } from '@lumino/algorithm';
8import { MimeData } from '@lumino/coreutils';
9import { ElementExt } from '@lumino/domutils';
10import { Drag } from '@lumino/dragdrop';
11import { AttachedProperty } from '@lumino/properties';
12import { Signal } from '@lumino/signaling';
13import { h, VirtualDOM } from '@lumino/virtualdom';
14import { PanelLayout, Widget } from '@lumino/widgets';
15import { NotebookActions } from './actions';
16import { DROP_SOURCE_CLASS, DROP_TARGET_CLASS } from './constants';
17import { NotebookViewModel, NotebookWindowedLayout } from './windowing';
18import { NotebookFooter } from './notebookfooter';
19/**
20 * The data attribute added to a widget that has an active kernel.
21 */
22const KERNEL_USER = 'jpKernelUser';
23/**
24 * The data attribute added to a widget that can run code.
25 */
26const CODE_RUNNER = 'jpCodeRunner';
27/**
28 * The data attribute added to a widget that can undo.
29 */
30const UNDOER = 'jpUndoer';
31/**
32 * The class name added to notebook widgets.
33 */
34const NB_CLASS = 'jp-Notebook';
35/**
36 * The class name added to notebook widget cells.
37 */
38const NB_CELL_CLASS = 'jp-Notebook-cell';
39/**
40 * The class name added to a notebook in edit mode.
41 */
42const EDIT_CLASS = 'jp-mod-editMode';
43/**
44 * The class name added to a notebook in command mode.
45 */
46const COMMAND_CLASS = 'jp-mod-commandMode';
47/**
48 * The class name added to the active cell.
49 */
50const ACTIVE_CLASS = 'jp-mod-active';
51/**
52 * The class name added to selected cells.
53 */
54const SELECTED_CLASS = 'jp-mod-selected';
55/**
56 * The class name added to an active cell when there are other selected cells.
57 */
58const OTHER_SELECTED_CLASS = 'jp-mod-multiSelected';
59/**
60 * The class name added to unconfined images.
61 */
62const UNCONFINED_CLASS = 'jp-mod-unconfined';
63/**
64 * The class name added to drag images.
65 */
66const DRAG_IMAGE_CLASS = 'jp-dragImage';
67/**
68 * The class name added to singular drag images
69 */
70const SINGLE_DRAG_IMAGE_CLASS = 'jp-dragImage-singlePrompt';
71/**
72 * The class name added to the drag image cell content.
73 */
74const CELL_DRAG_CONTENT_CLASS = 'jp-dragImage-content';
75/**
76 * The class name added to the drag image cell content.
77 */
78const CELL_DRAG_PROMPT_CLASS = 'jp-dragImage-prompt';
79/**
80 * The class name added to the drag image cell content.
81 */
82const CELL_DRAG_MULTIPLE_BACK = 'jp-dragImage-multipleBack';
83/**
84 * The mimetype used for Jupyter cell data.
85 */
86const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
87/**
88 * The threshold in pixels to start a drag event.
89 */
90const DRAG_THRESHOLD = 5;
91/**
92 * Maximal remaining time for idle callback
93 *
94 * Ref: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#getting_the_most_out_of_idle_callbacks
95 */
96const MAXIMUM_TIME_REMAINING = 50;
97/**
98 * The class attached to the heading collapser button
99 */
100const HEADING_COLLAPSER_CLASS = 'jp-collapseHeadingButton';
101/**
102 * The class that controls the visibility of "heading collapser" and "show hidden cells" buttons.
103 */
104const HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS = 'jp-mod-showHiddenCellsButton';
105const SIDE_BY_SIDE_CLASS = 'jp-mod-sideBySide';
106if (window.requestIdleCallback === undefined) {
107 // On Safari, requestIdleCallback is not available, so we use replacement functions for `idleCallbacks`
108 // See: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#falling_back_to_settimeout
109 // eslint-disable-next-line @typescript-eslint/ban-types
110 window.requestIdleCallback = function (handler) {
111 let startTime = Date.now();
112 return setTimeout(function () {
113 handler({
114 didTimeout: false,
115 timeRemaining: function () {
116 return Math.max(0, 50.0 - (Date.now() - startTime));
117 }
118 });
119 }, 1);
120 };
121 window.cancelIdleCallback = function (id) {
122 clearTimeout(id);
123 };
124}
125/**
126 * A widget which renders static non-interactive notebooks.
127 *
128 * #### Notes
129 * The widget model must be set separately and can be changed
130 * at any time. Consumers of the widget must account for a
131 * `null` model, and may want to listen to the `modelChanged`
132 * signal.
133 */
134export class StaticNotebook extends WindowedList {
135 /**
136 * Construct a notebook widget.
137 */
138 constructor(options) {
139 var _a, _b, _c, _d, _e;
140 const cells = new Array();
141 super({
142 model: new NotebookViewModel(cells, {
143 overscanCount: (_b = (_a = options.notebookConfig) === null || _a === void 0 ? void 0 : _a.overscanCount) !== null && _b !== void 0 ? _b : StaticNotebook.defaultNotebookConfig.overscanCount,
144 windowingActive: ((_d = (_c = options.notebookConfig) === null || _c === void 0 ? void 0 : _c.windowingMode) !== null && _d !== void 0 ? _d : StaticNotebook.defaultNotebookConfig.windowingMode) === 'full'
145 }),
146 layout: new NotebookWindowedLayout()
147 });
148 this._cellCollapsed = new Signal(this);
149 this._cellInViewportChanged = new Signal(this);
150 this._renderingLayoutChanged = new Signal(this);
151 this.addClass(NB_CLASS);
152 this.cellsArray = cells;
153 this._idleCallBack = null;
154 this._editorConfig = StaticNotebook.defaultEditorConfig;
155 this._notebookConfig = StaticNotebook.defaultNotebookConfig;
156 this._mimetype = 'text/plain';
157 this._notebookModel = null;
158 this._modelChanged = new Signal(this);
159 this._modelContentChanged = new Signal(this);
160 this.node.dataset[KERNEL_USER] = 'true';
161 this.node.dataset[UNDOER] = 'true';
162 this.node.dataset[CODE_RUNNER] = 'true';
163 this.rendermime = options.rendermime;
164 this.translator = options.translator || nullTranslator;
165 this.contentFactory = options.contentFactory;
166 this.editorConfig =
167 options.editorConfig || StaticNotebook.defaultEditorConfig;
168 this.notebookConfig =
169 options.notebookConfig || StaticNotebook.defaultNotebookConfig;
170 this._updateNotebookConfig();
171 this._mimetypeService = options.mimeTypeService;
172 this.renderingLayout = (_e = options.notebookConfig) === null || _e === void 0 ? void 0 : _e.renderingLayout;
173 }
174 get cellCollapsed() {
175 return this._cellCollapsed;
176 }
177 get cellInViewportChanged() {
178 return this._cellInViewportChanged;
179 }
180 /**
181 * A signal emitted when the model of the notebook changes.
182 */
183 get modelChanged() {
184 return this._modelChanged;
185 }
186 /**
187 * A signal emitted when the model content changes.
188 *
189 * #### Notes
190 * This is a convenience signal that follows the current model.
191 */
192 get modelContentChanged() {
193 return this._modelContentChanged;
194 }
195 /**
196 * A signal emitted when the rendering layout of the notebook changes.
197 */
198 get renderingLayoutChanged() {
199 return this._renderingLayoutChanged;
200 }
201 /**
202 * The model for the widget.
203 */
204 get model() {
205 return this._notebookModel;
206 }
207 set model(newValue) {
208 var _a;
209 newValue = newValue || null;
210 if (this._notebookModel === newValue) {
211 return;
212 }
213 const oldValue = this._notebookModel;
214 this._notebookModel = newValue;
215 // Trigger private, protected, and public changes.
216 this._onModelChanged(oldValue, newValue);
217 this.onModelChanged(oldValue, newValue);
218 this._modelChanged.emit(void 0);
219 // Trigger state change
220 this.viewModel.itemsList = (_a = newValue === null || newValue === void 0 ? void 0 : newValue.cells) !== null && _a !== void 0 ? _a : null;
221 }
222 /**
223 * Get the mimetype for code cells.
224 */
225 get codeMimetype() {
226 return this._mimetype;
227 }
228 /**
229 * A read-only sequence of the widgets in the notebook.
230 */
231 get widgets() {
232 return this.cellsArray;
233 }
234 /**
235 * A configuration object for cell editor settings.
236 */
237 get editorConfig() {
238 return this._editorConfig;
239 }
240 set editorConfig(value) {
241 this._editorConfig = value;
242 this._updateEditorConfig();
243 }
244 /**
245 * A configuration object for notebook settings.
246 */
247 get notebookConfig() {
248 return this._notebookConfig;
249 }
250 set notebookConfig(value) {
251 this._notebookConfig = value;
252 this._updateNotebookConfig();
253 }
254 get renderingLayout() {
255 return this._renderingLayout;
256 }
257 set renderingLayout(value) {
258 var _a;
259 this._renderingLayout = value;
260 if (this._renderingLayout === 'side-by-side') {
261 this.node.classList.add(SIDE_BY_SIDE_CLASS);
262 }
263 else {
264 this.node.classList.remove(SIDE_BY_SIDE_CLASS);
265 }
266 this._renderingLayoutChanged.emit((_a = this._renderingLayout) !== null && _a !== void 0 ? _a : 'default');
267 }
268 /**
269 * Dispose of the resources held by the widget.
270 */
271 dispose() {
272 var _a;
273 // Do nothing if already disposed.
274 if (this.isDisposed) {
275 return;
276 }
277 this._notebookModel = null;
278 (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
279 super.dispose();
280 }
281 /**
282 * Move cells preserving widget view state.
283 *
284 * #### Notes
285 * This is required because at the model level a move is a deletion
286 * followed by an insertion. Hence the view state is not preserved.
287 *
288 * @param from The index of the cell to move
289 * @param to The new index of the cell
290 * @param n Number of cells to move
291 */
292 moveCell(from, to, n = 1) {
293 if (!this.model) {
294 return;
295 }
296 const boundedTo = Math.min(this.model.cells.length - 1, Math.max(0, to));
297 if (boundedTo === from) {
298 return;
299 }
300 const viewModel = new Array(n);
301 for (let i = 0; i < n; i++) {
302 viewModel[i] = {};
303 const oldCell = this.widgets[from + i];
304 if (oldCell.model.type === 'markdown') {
305 for (const k of ['rendered', 'headingCollapsed']) {
306 // @ts-expect-error Cell has no index signature
307 viewModel[i][k] = oldCell[k];
308 }
309 }
310 }
311 this.model.sharedModel.moveCells(from, boundedTo, n);
312 for (let i = 0; i < n; i++) {
313 const newCell = this.widgets[to + i];
314 const view = viewModel[i];
315 for (const state in view) {
316 // @ts-expect-error Cell has no index signature
317 newCell[state] = view[state];
318 }
319 }
320 }
321 /**
322 * Force rendering the cell outputs of a given cell if it is still a placeholder.
323 *
324 * #### Notes
325 * The goal of this method is to allow search on cell outputs (that is based
326 * on DOM tree introspection).
327 *
328 * @param index The cell index
329 */
330 renderCellOutputs(index) {
331 const cell = this.viewModel.widgetRenderer(index);
332 if (cell instanceof CodeCell && cell.isPlaceholder()) {
333 cell.dataset.windowedListIndex = `${index}`;
334 this.layout.insertWidget(index, cell);
335 if (this.notebookConfig.windowingMode === 'full') {
336 // We need to delay slightly the removal to let codemirror properly initialize
337 requestAnimationFrame(() => {
338 this.layout.removeWidget(cell);
339 });
340 }
341 }
342 }
343 /**
344 * Adds a message to the notebook as a header.
345 */
346 addHeader() {
347 const trans = this.translator.load('jupyterlab');
348 const info = new Widget();
349 info.node.textContent = trans.__('The notebook is empty. Click the + button on the toolbar to add a new cell.');
350 this.layout.header = info;
351 }
352 /**
353 * Removes the header.
354 */
355 removeHeader() {
356 var _a;
357 (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
358 this.layout.header = null;
359 }
360 /**
361 * Handle a new model.
362 *
363 * #### Notes
364 * This method is called after the model change has been handled
365 * internally and before the `modelChanged` signal is emitted.
366 * The default implementation is a no-op.
367 */
368 onModelChanged(oldValue, newValue) {
369 // No-op.
370 }
371 /**
372 * Handle changes to the notebook model content.
373 *
374 * #### Notes
375 * The default implementation emits the `modelContentChanged` signal.
376 */
377 onModelContentChanged(model, args) {
378 this._modelContentChanged.emit(void 0);
379 }
380 /**
381 * Handle changes to the notebook model metadata.
382 *
383 * #### Notes
384 * The default implementation updates the mimetypes of the code cells
385 * when the `language_info` metadata changes.
386 */
387 onMetadataChanged(sender, args) {
388 switch (args.key) {
389 case 'language_info':
390 this._updateMimetype();
391 break;
392 default:
393 break;
394 }
395 }
396 /**
397 * Handle a cell being inserted.
398 *
399 * The default implementation is a no-op
400 */
401 onCellInserted(index, cell) {
402 // This is a no-op.
403 }
404 /**
405 * Handle a cell being removed.
406 *
407 * The default implementation is a no-op
408 */
409 onCellRemoved(index, cell) {
410 // This is a no-op.
411 }
412 /**
413 * A message handler invoked on an `'update-request'` message.
414 *
415 * #### Notes
416 * The default implementation of this handler is a no-op.
417 */
418 onUpdateRequest(msg) {
419 if (this.notebookConfig.windowingMode === 'defer') {
420 void this._runOnIdleTime();
421 }
422 else {
423 super.onUpdateRequest(msg);
424 }
425 }
426 /**
427 * Handle a new model on the widget.
428 */
429 _onModelChanged(oldValue, newValue) {
430 var _a;
431 if (oldValue) {
432 oldValue.contentChanged.disconnect(this.onModelContentChanged, this);
433 oldValue.metadataChanged.disconnect(this.onMetadataChanged, this);
434 oldValue.cells.changed.disconnect(this._onCellsChanged, this);
435 while (this.cellsArray.length) {
436 this._removeCell(0);
437 }
438 }
439 if (!newValue) {
440 this._mimetype = 'text/plain';
441 return;
442 }
443 this._updateMimetype();
444 const cells = newValue.cells;
445 const collab = (_a = newValue.collaborative) !== null && _a !== void 0 ? _a : false;
446 if (!collab && !cells.length) {
447 newValue.sharedModel.insertCell(0, {
448 cell_type: this.notebookConfig.defaultCell,
449 metadata: this.notebookConfig.defaultCell === 'code'
450 ? {
451 // This is an empty cell created in empty notebook, thus is trusted
452 trusted: true
453 }
454 : {}
455 });
456 }
457 let index = -1;
458 for (const cell of cells) {
459 this._insertCell(++index, cell);
460 }
461 newValue.cells.changed.connect(this._onCellsChanged, this);
462 newValue.metadataChanged.connect(this.onMetadataChanged, this);
463 newValue.contentChanged.connect(this.onModelContentChanged, this);
464 }
465 /**
466 * Handle a change cells event.
467 */
468 _onCellsChanged(sender, args) {
469 this.removeHeader();
470 switch (args.type) {
471 case 'add': {
472 let index = 0;
473 index = args.newIndex;
474 for (const value of args.newValues) {
475 this._insertCell(index++, value);
476 }
477 this._updateDataWindowedListIndex(args.newIndex, this.model.cells.length, args.newValues.length);
478 break;
479 }
480 case 'remove':
481 for (let length = args.oldValues.length; length > 0; length--) {
482 this._removeCell(args.oldIndex);
483 }
484 this._updateDataWindowedListIndex(args.oldIndex, this.model.cells.length + args.oldValues.length, -1 * args.oldValues.length);
485 // Add default cell if there are no cells remaining.
486 if (!sender.length) {
487 const model = this.model;
488 // Add the cell in a new context to avoid triggering another
489 // cell changed event during the handling of this signal.
490 requestAnimationFrame(() => {
491 if (model && !model.isDisposed && !model.sharedModel.cells.length) {
492 model.sharedModel.insertCell(0, {
493 cell_type: this.notebookConfig.defaultCell,
494 metadata: this.notebookConfig.defaultCell === 'code'
495 ? {
496 // This is an empty cell created in empty notebook, thus is trusted
497 trusted: true
498 }
499 : {}
500 });
501 }
502 });
503 }
504 break;
505 default:
506 return;
507 }
508 if (!this.model.sharedModel.cells.length) {
509 this.addHeader();
510 }
511 this.update();
512 }
513 /**
514 * Create a cell widget and insert into the notebook.
515 */
516 _insertCell(index, cell) {
517 let widget;
518 switch (cell.type) {
519 case 'code':
520 widget = this._createCodeCell(cell);
521 widget.model.mimeType = this._mimetype;
522 break;
523 case 'markdown':
524 widget = this._createMarkdownCell(cell);
525 if (cell.sharedModel.getSource() === '') {
526 widget.rendered = false;
527 }
528 break;
529 default:
530 widget = this._createRawCell(cell);
531 }
532 widget.inViewportChanged.connect(this._onCellInViewportChanged, this);
533 widget.addClass(NB_CELL_CLASS);
534 ArrayExt.insert(this.cellsArray, index, widget);
535 this.onCellInserted(index, widget);
536 this._scheduleCellRenderOnIdle();
537 }
538 /**
539 * Create a code cell widget from a code cell model.
540 */
541 _createCodeCell(model) {
542 const rendermime = this.rendermime;
543 const contentFactory = this.contentFactory;
544 const editorConfig = this.editorConfig.code;
545 const options = {
546 contentFactory,
547 editorConfig,
548 inputHistoryScope: this.notebookConfig.inputHistoryScope,
549 maxNumberOutputs: this.notebookConfig.maxNumberOutputs,
550 model,
551 placeholder: this._notebookConfig.windowingMode !== 'none',
552 rendermime,
553 translator: this.translator
554 };
555 const cell = this.contentFactory.createCodeCell(options);
556 cell.syncCollapse = true;
557 cell.syncEditable = true;
558 cell.syncScrolled = true;
559 cell.outputArea.inputRequested.connect(() => {
560 this._onInputRequested(cell).catch(reason => {
561 console.error('Failed to scroll to cell requesting input.', reason);
562 });
563 });
564 return cell;
565 }
566 /**
567 * Create a markdown cell widget from a markdown cell model.
568 */
569 _createMarkdownCell(model) {
570 const rendermime = this.rendermime;
571 const contentFactory = this.contentFactory;
572 const editorConfig = this.editorConfig.markdown;
573 const options = {
574 contentFactory,
575 editorConfig,
576 model,
577 placeholder: this._notebookConfig.windowingMode !== 'none',
578 rendermime,
579 showEditorForReadOnlyMarkdown: this._notebookConfig.showEditorForReadOnlyMarkdown
580 };
581 const cell = this.contentFactory.createMarkdownCell(options);
582 cell.syncCollapse = true;
583 cell.syncEditable = true;
584 // Connect collapsed signal for each markdown cell widget
585 cell.headingCollapsedChanged.connect(this._onCellCollapsed, this);
586 return cell;
587 }
588 /**
589 * Create a raw cell widget from a raw cell model.
590 */
591 _createRawCell(model) {
592 const contentFactory = this.contentFactory;
593 const editorConfig = this.editorConfig.raw;
594 const options = {
595 editorConfig,
596 model,
597 contentFactory,
598 placeholder: this._notebookConfig.windowingMode !== 'none'
599 };
600 const cell = this.contentFactory.createRawCell(options);
601 cell.syncCollapse = true;
602 cell.syncEditable = true;
603 return cell;
604 }
605 /**
606 * Remove a cell widget.
607 */
608 _removeCell(index) {
609 const widget = this.cellsArray[index];
610 widget.parent = null;
611 ArrayExt.removeAt(this.cellsArray, index);
612 this.onCellRemoved(index, widget);
613 widget.dispose();
614 }
615 /**
616 * Update the mimetype of the notebook.
617 */
618 _updateMimetype() {
619 var _a;
620 const info = (_a = this._notebookModel) === null || _a === void 0 ? void 0 : _a.getMetadata('language_info');
621 if (!info) {
622 return;
623 }
624 this._mimetype = this._mimetypeService.getMimeTypeByLanguage(info);
625 for (const widget of this.widgets) {
626 if (widget.model.type === 'code') {
627 widget.model.mimeType = this._mimetype;
628 }
629 }
630 }
631 /**
632 * Callback when a cell collapsed status changes.
633 *
634 * @param cell Cell changed
635 * @param collapsed New collapsed status
636 */
637 _onCellCollapsed(cell, collapsed) {
638 NotebookActions.setHeadingCollapse(cell, collapsed, this);
639 this._cellCollapsed.emit(cell);
640 }
641 /**
642 * Callback when a cell viewport status changes.
643 *
644 * @param cell Cell changed
645 */
646 _onCellInViewportChanged(cell) {
647 this._cellInViewportChanged.emit(cell);
648 }
649 /**
650 * Ensure to load in the DOM a cell requesting an user input
651 *
652 * @param cell Cell requesting an input
653 */
654 async _onInputRequested(cell) {
655 if (!cell.inViewport) {
656 const cellIndex = this.widgets.findIndex(c => c === cell);
657 if (cellIndex >= 0) {
658 await this.scrollToItem(cellIndex);
659 const inputEl = cell.node.querySelector('.jp-Stdin');
660 if (inputEl) {
661 ElementExt.scrollIntoViewIfNeeded(this.node, inputEl);
662 inputEl.focus();
663 }
664 }
665 }
666 }
667 _scheduleCellRenderOnIdle() {
668 if (this.notebookConfig.windowingMode !== 'none' && !this.isDisposed) {
669 if (!this._idleCallBack) {
670 this._idleCallBack = requestIdleCallback((deadline) => {
671 this._idleCallBack = null;
672 // In case of timeout, render for some time even if it means freezing the UI
673 // This avoids the cells to never be loaded.
674 void this._runOnIdleTime(deadline.didTimeout
675 ? MAXIMUM_TIME_REMAINING
676 : deadline.timeRemaining());
677 }, {
678 timeout: 3000
679 });
680 }
681 }
682 }
683 _updateDataWindowedListIndex(start, end, delta) {
684 for (let cellIdx = 0; cellIdx < this.viewportNode.childElementCount; cellIdx++) {
685 const cell = this.viewportNode.children[cellIdx];
686 const globalIndex = parseInt(cell.dataset.windowedListIndex, 10);
687 if (globalIndex >= start && globalIndex < end) {
688 cell.dataset.windowedListIndex = `${globalIndex + delta}`;
689 }
690 }
691 }
692 /**
693 * Update editor settings for notebook cells.
694 */
695 _updateEditorConfig() {
696 for (let i = 0; i < this.widgets.length; i++) {
697 const cell = this.widgets[i];
698 let config = {};
699 switch (cell.model.type) {
700 case 'code':
701 config = this._editorConfig.code;
702 break;
703 case 'markdown':
704 config = this._editorConfig.markdown;
705 break;
706 default:
707 config = this._editorConfig.raw;
708 break;
709 }
710 cell.updateEditorConfig({ ...config });
711 }
712 }
713 async _runOnIdleTime(remainingTime = MAXIMUM_TIME_REMAINING) {
714 const startTime = Date.now();
715 let cellIdx = 0;
716 while (Date.now() - startTime < remainingTime &&
717 cellIdx < this.cellsArray.length) {
718 const cell = this.cellsArray[cellIdx];
719 if (cell.isPlaceholder()) {
720 switch (this.notebookConfig.windowingMode) {
721 case 'defer':
722 await this._updateForDeferMode(cell, cellIdx);
723 break;
724 case 'full':
725 this._renderCSSAndJSOutputs(cell, cellIdx);
726 break;
727 }
728 }
729 cellIdx++;
730 }
731 if (cellIdx < this.cellsArray.length) {
732 this._scheduleCellRenderOnIdle();
733 }
734 else {
735 if (this._idleCallBack) {
736 window.cancelIdleCallback(this._idleCallBack);
737 this._idleCallBack = null;
738 }
739 }
740 }
741 async _updateForDeferMode(cell, cellIdx) {
742 cell.dataset.windowedListIndex = `${cellIdx}`;
743 this.layout.insertWidget(cellIdx, cell);
744 await cell.ready;
745 }
746 _renderCSSAndJSOutputs(cell, cellIdx) {
747 var _a, _b, _c;
748 // Only render cell with text/html outputs containing scripts or/and styles
749 // Note:
750 // We don't need to render JavaScript mimetype outputs because they get
751 // directly evaluate without adding DOM elements (see @jupyterlab/javascript-extension)
752 if (cell instanceof CodeCell) {
753 for (let outputIdx = 0; outputIdx < ((_b = (_a = cell.model.outputs) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); outputIdx++) {
754 const output = cell.model.outputs.get(outputIdx);
755 const html = (_c = output.data['text/html']) !== null && _c !== void 0 ? _c : '';
756 if (html.match(/(<style[^>]*>[^<]*<\/style[^>]*>|<script[^>]*>.*?<\/script[^>]*>)/gims)) {
757 this.renderCellOutputs(cellIdx);
758 break;
759 }
760 }
761 }
762 }
763 /**
764 * Apply updated notebook settings.
765 */
766 _updateNotebookConfig() {
767 // Apply scrollPastEnd setting.
768 this.toggleClass('jp-mod-scrollPastEnd', this._notebookConfig.scrollPastEnd);
769 // Control visibility of heading collapser UI
770 this.toggleClass(HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS, this._notebookConfig.showHiddenCellsButton);
771 // Control editor visibility for read-only Markdown cells
772 const showEditorForReadOnlyMarkdown = this._notebookConfig.showEditorForReadOnlyMarkdown;
773 if (showEditorForReadOnlyMarkdown !== undefined) {
774 for (const cell of this.cellsArray) {
775 if (cell.model.type === 'markdown') {
776 cell.showEditorForReadOnly =
777 showEditorForReadOnlyMarkdown;
778 }
779 }
780 }
781 this.viewModel.windowingActive =
782 this._notebookConfig.windowingMode === 'full';
783 }
784}
785/**
786 * The namespace for the `StaticNotebook` class statics.
787 */
788(function (StaticNotebook) {
789 /**
790 * Default configuration options for cell editors.
791 */
792 StaticNotebook.defaultEditorConfig = {
793 code: {
794 lineNumbers: false,
795 lineWrap: false,
796 matchBrackets: true
797 },
798 markdown: {
799 lineNumbers: false,
800 lineWrap: true,
801 matchBrackets: false
802 },
803 raw: {
804 lineNumbers: false,
805 lineWrap: true,
806 matchBrackets: false
807 }
808 };
809 /**
810 * Default configuration options for notebooks.
811 */
812 StaticNotebook.defaultNotebookConfig = {
813 showHiddenCellsButton: true,
814 scrollPastEnd: true,
815 defaultCell: 'code',
816 recordTiming: false,
817 inputHistoryScope: 'global',
818 maxNumberOutputs: 50,
819 showEditorForReadOnlyMarkdown: true,
820 disableDocumentWideUndoRedo: true,
821 renderingLayout: 'default',
822 sideBySideLeftMarginOverride: '10px',
823 sideBySideRightMarginOverride: '10px',
824 sideBySideOutputRatio: 1,
825 overscanCount: 1,
826 windowingMode: 'full'
827 };
828 /**
829 * The default implementation of an `IContentFactory`.
830 */
831 class ContentFactory extends Cell.ContentFactory {
832 /**
833 * Create a new code cell widget.
834 *
835 * #### Notes
836 * If no cell content factory is passed in with the options, the one on the
837 * notebook content factory is used.
838 */
839 createCodeCell(options) {
840 return new CodeCell(options).initializeState();
841 }
842 /**
843 * Create a new markdown cell widget.
844 *
845 * #### Notes
846 * If no cell content factory is passed in with the options, the one on the
847 * notebook content factory is used.
848 */
849 createMarkdownCell(options) {
850 return new MarkdownCell(options).initializeState();
851 }
852 /**
853 * Create a new raw cell widget.
854 *
855 * #### Notes
856 * If no cell content factory is passed in with the options, the one on the
857 * notebook content factory is used.
858 */
859 createRawCell(options) {
860 return new RawCell(options).initializeState();
861 }
862 }
863 StaticNotebook.ContentFactory = ContentFactory;
864})(StaticNotebook || (StaticNotebook = {}));
865/**
866 * A notebook widget that supports interactivity.
867 */
868export class Notebook extends StaticNotebook {
869 /**
870 * Construct a notebook widget.
871 */
872 constructor(options) {
873 super(options);
874 this._activeCellIndex = -1;
875 this._activeCell = null;
876 this._mode = 'command';
877 this._drag = null;
878 this._dragData = null;
879 this._mouseMode = null;
880 this._activeCellChanged = new Signal(this);
881 this._stateChanged = new Signal(this);
882 this._selectionChanged = new Signal(this);
883 this._checkCacheOnNextResize = false;
884 this._lastClipboardInteraction = null;
885 this._selectedCells = [];
886 this.node.tabIndex = 0; // Allow the widget to take focus.
887 // Allow the node to scroll while dragging items.
888 this.node.setAttribute('data-lm-dragscroll', 'true');
889 this.activeCellChanged.connect(this._updateSelectedCells, this);
890 this.selectionChanged.connect(this._updateSelectedCells, this);
891 this.addFooter();
892 }
893 /**
894 * List of selected and active cells
895 */
896 get selectedCells() {
897 return this._selectedCells;
898 }
899 /**
900 * Adds a footer to the notebook.
901 */
902 addFooter() {
903 const info = new NotebookFooter(this);
904 this.layout.footer = info;
905 }
906 /**
907 * Handle a change cells event.
908 */
909 _onCellsChanged(sender, args) {
910 var _a, _b;
911 const activeCellId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
912 super._onCellsChanged(sender, args);
913 if (activeCellId) {
914 const newActiveCellIndex = (_b = this.model) === null || _b === void 0 ? void 0 : _b.sharedModel.cells.findIndex(cell => cell.getId() === activeCellId);
915 if (newActiveCellIndex != null) {
916 this.activeCellIndex = newActiveCellIndex;
917 }
918 }
919 }
920 /**
921 * A signal emitted when the active cell changes.
922 *
923 * #### Notes
924 * This can be due to the active index changing or the
925 * cell at the active index changing.
926 */
927 get activeCellChanged() {
928 return this._activeCellChanged;
929 }
930 /**
931 * A signal emitted when the state of the notebook changes.
932 */
933 get stateChanged() {
934 return this._stateChanged;
935 }
936 /**
937 * A signal emitted when the selection state of the notebook changes.
938 */
939 get selectionChanged() {
940 return this._selectionChanged;
941 }
942 /**
943 * The interactivity mode of the notebook.
944 */
945 get mode() {
946 return this._mode;
947 }
948 set mode(newValue) {
949 const activeCell = this.activeCell;
950 if (!activeCell) {
951 newValue = 'command';
952 }
953 if (newValue === this._mode) {
954 this._ensureFocus();
955 return;
956 }
957 // Post an update request.
958 this.update();
959 const oldValue = this._mode;
960 this._mode = newValue;
961 if (newValue === 'edit') {
962 // Edit mode deselects all cells.
963 for (const widget of this.widgets) {
964 this.deselect(widget);
965 }
966 // Edit mode unrenders an active markdown widget.
967 if (activeCell instanceof MarkdownCell) {
968 activeCell.rendered = false;
969 }
970 activeCell.inputHidden = false;
971 }
972 else {
973 // Focus on the notebook document, which blurs the active cell.
974 this.node.focus();
975 }
976 this._stateChanged.emit({ name: 'mode', oldValue, newValue });
977 this._ensureFocus();
978 }
979 /**
980 * The active cell index of the notebook.
981 *
982 * #### Notes
983 * The index will be clamped to the bounds of the notebook cells.
984 */
985 get activeCellIndex() {
986 if (!this.model) {
987 return -1;
988 }
989 return this.widgets.length ? this._activeCellIndex : -1;
990 }
991 set activeCellIndex(newValue) {
992 var _a;
993 const oldValue = this._activeCellIndex;
994 if (!this.model || !this.widgets.length) {
995 newValue = -1;
996 }
997 else {
998 newValue = Math.max(newValue, 0);
999 newValue = Math.min(newValue, this.widgets.length - 1);
1000 }
1001 this._activeCellIndex = newValue;
1002 const cell = (_a = this.widgets[newValue]) !== null && _a !== void 0 ? _a : null;
1003 const cellChanged = cell !== this._activeCell;
1004 if (cellChanged) {
1005 // Post an update request.
1006 this.update();
1007 this._activeCell = cell;
1008 }
1009 if (cellChanged || newValue != oldValue) {
1010 this._activeCellChanged.emit(cell);
1011 }
1012 if (this.mode === 'edit' && cell instanceof MarkdownCell) {
1013 cell.rendered = false;
1014 }
1015 this._ensureFocus();
1016 if (newValue === oldValue) {
1017 return;
1018 }
1019 this._trimSelections();
1020 this._stateChanged.emit({ name: 'activeCellIndex', oldValue, newValue });
1021 }
1022 /**
1023 * Get the active cell widget.
1024 *
1025 * #### Notes
1026 * This is a cell or `null` if there is no active cell.
1027 */
1028 get activeCell() {
1029 return this._activeCell;
1030 }
1031 get lastClipboardInteraction() {
1032 return this._lastClipboardInteraction;
1033 }
1034 set lastClipboardInteraction(newValue) {
1035 this._lastClipboardInteraction = newValue;
1036 }
1037 /**
1038 * Dispose of the resources held by the widget.
1039 */
1040 dispose() {
1041 if (this.isDisposed) {
1042 return;
1043 }
1044 this._activeCell = null;
1045 super.dispose();
1046 }
1047 /**
1048 * Move cells preserving widget view state.
1049 *
1050 * #### Notes
1051 * This is required because at the model level a move is a deletion
1052 * followed by an insertion. Hence the view state is not preserved.
1053 *
1054 * @param from The index of the cell to move
1055 * @param to The new index of the cell
1056 * @param n Number of cells to move
1057 */
1058 moveCell(from, to, n = 1) {
1059 // Save active cell id to be restored
1060 const newActiveCellIndex = from <= this.activeCellIndex && this.activeCellIndex < from + n
1061 ? this.activeCellIndex + to - from - (from > to ? 0 : n - 1)
1062 : -1;
1063 const isSelected = this.widgets
1064 .slice(from, from + n)
1065 .map(w => this.isSelected(w));
1066 super.moveCell(from, to, n);
1067 if (newActiveCellIndex >= 0) {
1068 this.activeCellIndex = newActiveCellIndex;
1069 }
1070 if (from > to) {
1071 isSelected.forEach((selected, idx) => {
1072 if (selected) {
1073 this.select(this.widgets[to + idx]);
1074 }
1075 });
1076 }
1077 else {
1078 isSelected.forEach((selected, idx) => {
1079 if (selected) {
1080 this.select(this.widgets[to - n + 1 + idx]);
1081 }
1082 });
1083 }
1084 }
1085 /**
1086 * Select a cell widget.
1087 *
1088 * #### Notes
1089 * It is a no-op if the value does not change.
1090 * It will emit the `selectionChanged` signal.
1091 */
1092 select(widget) {
1093 if (Private.selectedProperty.get(widget)) {
1094 return;
1095 }
1096 Private.selectedProperty.set(widget, true);
1097 this._selectionChanged.emit(void 0);
1098 this.update();
1099 }
1100 /**
1101 * Deselect a cell widget.
1102 *
1103 * #### Notes
1104 * It is a no-op if the value does not change.
1105 * It will emit the `selectionChanged` signal.
1106 */
1107 deselect(widget) {
1108 if (!Private.selectedProperty.get(widget)) {
1109 return;
1110 }
1111 Private.selectedProperty.set(widget, false);
1112 this._selectionChanged.emit(void 0);
1113 this.update();
1114 }
1115 /**
1116 * Whether a cell is selected.
1117 */
1118 isSelected(widget) {
1119 return Private.selectedProperty.get(widget);
1120 }
1121 /**
1122 * Whether a cell is selected or is the active cell.
1123 */
1124 isSelectedOrActive(widget) {
1125 if (widget === this._activeCell) {
1126 return true;
1127 }
1128 return Private.selectedProperty.get(widget);
1129 }
1130 /**
1131 * Deselect all of the cells.
1132 */
1133 deselectAll() {
1134 let changed = false;
1135 for (const widget of this.widgets) {
1136 if (Private.selectedProperty.get(widget)) {
1137 changed = true;
1138 }
1139 Private.selectedProperty.set(widget, false);
1140 }
1141 if (changed) {
1142 this._selectionChanged.emit(void 0);
1143 }
1144 // Make sure we have a valid active cell.
1145 this.activeCellIndex = this.activeCellIndex; // eslint-disable-line
1146 this.update();
1147 }
1148 /**
1149 * Move the head of an existing contiguous selection to extend the selection.
1150 *
1151 * @param index - The new head of the existing selection.
1152 *
1153 * #### Notes
1154 * If there is no existing selection, the active cell is considered an
1155 * existing one-cell selection.
1156 *
1157 * If the new selection is a single cell, that cell becomes the active cell
1158 * and all cells are deselected.
1159 *
1160 * There is no change if there are no cells (i.e., activeCellIndex is -1).
1161 */
1162 extendContiguousSelectionTo(index) {
1163 let { head, anchor } = this.getContiguousSelection();
1164 let i;
1165 // Handle the case of no current selection.
1166 if (anchor === null || head === null) {
1167 if (index === this.activeCellIndex) {
1168 // Already collapsed selection, nothing more to do.
1169 return;
1170 }
1171 // We will start a new selection below.
1172 head = this.activeCellIndex;
1173 anchor = this.activeCellIndex;
1174 }
1175 // Move the active cell. We do this before the collapsing shortcut below.
1176 this.activeCellIndex = index;
1177 // Make sure the index is valid, according to the rules for setting and clipping the
1178 // active cell index. This may change the index.
1179 index = this.activeCellIndex;
1180 // Collapse the selection if it is only the active cell.
1181 if (index === anchor) {
1182 this.deselectAll();
1183 return;
1184 }
1185 let selectionChanged = false;
1186 if (head < index) {
1187 if (head < anchor) {
1188 Private.selectedProperty.set(this.widgets[head], false);
1189 selectionChanged = true;
1190 }
1191 // Toggle everything strictly between head and index except anchor.
1192 for (i = head + 1; i < index; i++) {
1193 if (i !== anchor) {
1194 Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
1195 selectionChanged = true;
1196 }
1197 }
1198 }
1199 else if (index < head) {
1200 if (anchor < head) {
1201 Private.selectedProperty.set(this.widgets[head], false);
1202 selectionChanged = true;
1203 }
1204 // Toggle everything strictly between index and head except anchor.
1205 for (i = index + 1; i < head; i++) {
1206 if (i !== anchor) {
1207 Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
1208 selectionChanged = true;
1209 }
1210 }
1211 }
1212 // Anchor and index should *always* be selected.
1213 if (!Private.selectedProperty.get(this.widgets[anchor])) {
1214 selectionChanged = true;
1215 }
1216 Private.selectedProperty.set(this.widgets[anchor], true);
1217 if (!Private.selectedProperty.get(this.widgets[index])) {
1218 selectionChanged = true;
1219 }
1220 Private.selectedProperty.set(this.widgets[index], true);
1221 if (selectionChanged) {
1222 this._selectionChanged.emit(void 0);
1223 }
1224 }
1225 /**
1226 * Get the head and anchor of a contiguous cell selection.
1227 *
1228 * The head of a contiguous selection is always the active cell.
1229 *
1230 * If there are no cells selected, `{head: null, anchor: null}` is returned.
1231 *
1232 * Throws an error if the currently selected cells do not form a contiguous
1233 * selection.
1234 */
1235 getContiguousSelection() {
1236 const cells = this.widgets;
1237 const first = ArrayExt.findFirstIndex(cells, c => this.isSelected(c));
1238 // Return early if no cells are selected.
1239 if (first === -1) {
1240 return { head: null, anchor: null };
1241 }
1242 const last = ArrayExt.findLastIndex(cells, c => this.isSelected(c), -1, first);
1243 // Check that the selection is contiguous.
1244 for (let i = first; i <= last; i++) {
1245 if (!this.isSelected(cells[i])) {
1246 throw new Error('Selection not contiguous');
1247 }
1248 }
1249 // Check that the active cell is one of the endpoints of the selection.
1250 const activeIndex = this.activeCellIndex;
1251 if (first !== activeIndex && last !== activeIndex) {
1252 throw new Error('Active cell not at endpoint of selection');
1253 }
1254 // Determine the head and anchor of the selection.
1255 if (first === activeIndex) {
1256 return { head: first, anchor: last };
1257 }
1258 else {
1259 return { head: last, anchor: first };
1260 }
1261 }
1262 /**
1263 * Scroll so that the given cell is in view. Selects and activates cell.
1264 *
1265 * @param cell - A cell in the notebook widget.
1266 * @param align - Type of alignment.
1267 *
1268 */
1269 async scrollToCell(cell, align = 'auto') {
1270 try {
1271 await this.scrollToItem(this.widgets.findIndex(c => c === cell), align);
1272 }
1273 catch (r) {
1274 //no-op
1275 }
1276 // change selection and active cell:
1277 this.deselectAll();
1278 this.select(cell);
1279 cell.activate();
1280 }
1281 _parseFragment(fragment) {
1282 const cleanedFragment = fragment.slice(1);
1283 if (!cleanedFragment) {
1284 // Bail early
1285 return;
1286 }
1287 const parts = cleanedFragment.split('=');
1288 if (parts.length === 1) {
1289 // Default to heading if no prefix is given.
1290 return {
1291 kind: 'heading',
1292 value: cleanedFragment
1293 };
1294 }
1295 return {
1296 kind: parts[0],
1297 value: parts.slice(1).join('=')
1298 };
1299 }
1300 /**
1301 * Set URI fragment identifier.
1302 */
1303 async setFragment(fragment) {
1304 const parsedFragment = this._parseFragment(fragment);
1305 if (!parsedFragment) {
1306 // Bail early
1307 return;
1308 }
1309 let result;
1310 switch (parsedFragment.kind) {
1311 case 'heading':
1312 result = await this._findHeading(parsedFragment.value);
1313 break;
1314 case 'cell-id':
1315 result = this._findCellById(parsedFragment.value);
1316 break;
1317 default:
1318 console.warn(`Unknown target type for URI fragment ${fragment}, interpreting as a heading`);
1319 result = await this._findHeading(parsedFragment.kind + '=' + parsedFragment.value);
1320 break;
1321 }
1322 if (result == null) {
1323 return;
1324 }
1325 let { cell, element } = result;
1326 if (!cell.inViewport) {
1327 await this.scrollToCell(cell, 'center');
1328 }
1329 if (element == null) {
1330 element = cell.node;
1331 }
1332 const widgetBox = this.node.getBoundingClientRect();
1333 const elementBox = element.getBoundingClientRect();
1334 if (elementBox.top > widgetBox.bottom ||
1335 elementBox.bottom < widgetBox.top) {
1336 element.scrollIntoView({ block: 'center' });
1337 }
1338 }
1339 /**
1340 * Handle the DOM events for the widget.
1341 *
1342 * @param event - The DOM event sent to the widget.
1343 *
1344 * #### Notes
1345 * This method implements the DOM `EventListener` interface and is
1346 * called in response to events on the notebook panel's node. It should
1347 * not be called directly by user code.
1348 */
1349 handleEvent(event) {
1350 if (!this.model) {
1351 return;
1352 }
1353 switch (event.type) {
1354 case 'contextmenu':
1355 if (event.eventPhase === Event.CAPTURING_PHASE) {
1356 this._evtContextMenuCapture(event);
1357 }
1358 break;
1359 case 'mousedown':
1360 if (event.eventPhase === Event.CAPTURING_PHASE) {
1361 this._evtMouseDownCapture(event);
1362 }
1363 else {
1364 // Skip processing the event when it resulted from a toolbar button click
1365 if (!event.defaultPrevented) {
1366 this._evtMouseDown(event);
1367 }
1368 }
1369 break;
1370 case 'mouseup':
1371 if (event.currentTarget === document) {
1372 this._evtDocumentMouseup(event);
1373 }
1374 break;
1375 case 'mousemove':
1376 if (event.currentTarget === document) {
1377 this._evtDocumentMousemove(event);
1378 }
1379 break;
1380 case 'keydown':
1381 this._ensureFocus(true);
1382 break;
1383 case 'dblclick':
1384 this._evtDblClick(event);
1385 break;
1386 case 'focusin':
1387 this._evtFocusIn(event);
1388 break;
1389 case 'focusout':
1390 this._evtFocusOut(event);
1391 break;
1392 case 'lm-dragenter':
1393 this._evtDragEnter(event);
1394 break;
1395 case 'lm-dragleave':
1396 this._evtDragLeave(event);
1397 break;
1398 case 'lm-dragover':
1399 this._evtDragOver(event);
1400 break;
1401 case 'lm-drop':
1402 this._evtDrop(event);
1403 break;
1404 default:
1405 super.handleEvent(event);
1406 break;
1407 }
1408 }
1409 /**
1410 * Handle `after-attach` messages for the widget.
1411 */
1412 onAfterAttach(msg) {
1413 super.onAfterAttach(msg);
1414 const node = this.node;
1415 node.addEventListener('contextmenu', this, true);
1416 node.addEventListener('mousedown', this, true);
1417 node.addEventListener('mousedown', this);
1418 node.addEventListener('keydown', this);
1419 node.addEventListener('dblclick', this);
1420 node.addEventListener('focusin', this);
1421 node.addEventListener('focusout', this);
1422 // Capture drag events for the notebook widget
1423 // in order to preempt the drag/drop handlers in the
1424 // code editor widgets, which can take text data.
1425 node.addEventListener('lm-dragenter', this, true);
1426 node.addEventListener('lm-dragleave', this, true);
1427 node.addEventListener('lm-dragover', this, true);
1428 node.addEventListener('lm-drop', this, true);
1429 }
1430 /**
1431 * Handle `before-detach` messages for the widget.
1432 */
1433 onBeforeDetach(msg) {
1434 const node = this.node;
1435 node.removeEventListener('contextmenu', this, true);
1436 node.removeEventListener('mousedown', this, true);
1437 node.removeEventListener('mousedown', this);
1438 node.removeEventListener('keydown', this);
1439 node.removeEventListener('dblclick', this);
1440 node.removeEventListener('focusin', this);
1441 node.removeEventListener('focusout', this);
1442 node.removeEventListener('lm-dragenter', this, true);
1443 node.removeEventListener('lm-dragleave', this, true);
1444 node.removeEventListener('lm-dragover', this, true);
1445 node.removeEventListener('lm-drop', this, true);
1446 document.removeEventListener('mousemove', this, true);
1447 document.removeEventListener('mouseup', this, true);
1448 super.onBeforeAttach(msg);
1449 }
1450 /**
1451 * A message handler invoked on an `'after-show'` message.
1452 */
1453 onAfterShow(msg) {
1454 super.onAfterShow(msg);
1455 this._checkCacheOnNextResize = true;
1456 }
1457 /**
1458 * A message handler invoked on a `'resize'` message.
1459 */
1460 onResize(msg) {
1461 var _a;
1462 // TODO
1463 if (!this._checkCacheOnNextResize) {
1464 return super.onResize(msg);
1465 }
1466 super.onResize(msg);
1467 this._checkCacheOnNextResize = false;
1468 const cache = this._cellLayoutStateCache;
1469 const width = parseInt(this.node.style.width, 10);
1470 if (cache) {
1471 if (width === cache.width) {
1472 // Cache identical, do nothing
1473 return;
1474 }
1475 }
1476 // Update cache
1477 this._cellLayoutStateCache = { width };
1478 // Fallback:
1479 for (const w of this.widgets) {
1480 if (w instanceof Cell && w.inViewport) {
1481 (_a = w.editorWidget) === null || _a === void 0 ? void 0 : _a.update();
1482 }
1483 }
1484 }
1485 /**
1486 * A message handler invoked on an `'before-hide'` message.
1487 */
1488 onBeforeHide(msg) {
1489 super.onBeforeHide(msg);
1490 // Update cache
1491 const width = parseInt(this.node.style.width, 10);
1492 this._cellLayoutStateCache = { width };
1493 }
1494 /**
1495 * Handle `'activate-request'` messages.
1496 */
1497 onActivateRequest(msg) {
1498 super.onActivateRequest(msg);
1499 this._ensureFocus(true);
1500 }
1501 /**
1502 * Handle `update-request` messages sent to the widget.
1503 */
1504 onUpdateRequest(msg) {
1505 super.onUpdateRequest(msg);
1506 const activeCell = this.activeCell;
1507 // Set the appropriate classes on the cells.
1508 if (this.mode === 'edit') {
1509 this.addClass(EDIT_CLASS);
1510 this.removeClass(COMMAND_CLASS);
1511 }
1512 else {
1513 this.addClass(COMMAND_CLASS);
1514 this.removeClass(EDIT_CLASS);
1515 }
1516 if (activeCell) {
1517 activeCell.addClass(ACTIVE_CLASS);
1518 }
1519 let count = 0;
1520 for (const widget of this.widgets) {
1521 if (widget !== activeCell) {
1522 widget.removeClass(ACTIVE_CLASS);
1523 }
1524 widget.removeClass(OTHER_SELECTED_CLASS);
1525 if (this.isSelectedOrActive(widget)) {
1526 widget.addClass(SELECTED_CLASS);
1527 count++;
1528 }
1529 else {
1530 widget.removeClass(SELECTED_CLASS);
1531 }
1532 }
1533 if (count > 1) {
1534 activeCell === null || activeCell === void 0 ? void 0 : activeCell.addClass(OTHER_SELECTED_CLASS);
1535 }
1536 }
1537 /**
1538 * Handle a cell being inserted.
1539 */
1540 onCellInserted(index, cell) {
1541 void cell.ready.then(() => {
1542 if (!cell.isDisposed) {
1543 cell.editor.edgeRequested.connect(this._onEdgeRequest, this);
1544 }
1545 });
1546 // If the insertion happened above, increment the active cell
1547 // index, otherwise it stays the same.
1548 this.activeCellIndex =
1549 index <= this.activeCellIndex
1550 ? this.activeCellIndex + 1
1551 : this.activeCellIndex;
1552 }
1553 /**
1554 * Handle a cell being removed.
1555 */
1556 onCellRemoved(index, cell) {
1557 // If the removal happened above, decrement the active
1558 // cell index, otherwise it stays the same.
1559 this.activeCellIndex =
1560 index <= this.activeCellIndex
1561 ? this.activeCellIndex - 1
1562 : this.activeCellIndex;
1563 if (this.isSelected(cell)) {
1564 this._selectionChanged.emit(void 0);
1565 }
1566 }
1567 /**
1568 * Handle a new model.
1569 */
1570 onModelChanged(oldValue, newValue) {
1571 super.onModelChanged(oldValue, newValue);
1572 // Try to set the active cell index to 0.
1573 // It will be set to `-1` if there is no new model or the model is empty.
1574 this.activeCellIndex = 0;
1575 }
1576 /**
1577 * Handle edge request signals from cells.
1578 */
1579 _onEdgeRequest(editor, location) {
1580 const prev = this.activeCellIndex;
1581 if (location === 'top') {
1582 this.activeCellIndex--;
1583 // Move the cursor to the first position on the last line.
1584 if (this.activeCellIndex < prev) {
1585 const editor = this.activeCell.editor;
1586 if (editor) {
1587 const lastLine = editor.lineCount - 1;
1588 editor.setCursorPosition({ line: lastLine, column: 0 });
1589 }
1590 }
1591 }
1592 else if (location === 'bottom') {
1593 this.activeCellIndex++;
1594 // Move the cursor to the first character.
1595 if (this.activeCellIndex > prev) {
1596 const editor = this.activeCell.editor;
1597 if (editor) {
1598 editor.setCursorPosition({ line: 0, column: 0 });
1599 }
1600 }
1601 }
1602 this.mode = 'edit';
1603 }
1604 /**
1605 * Ensure that the notebook has proper focus.
1606 */
1607 _ensureFocus(force = false) {
1608 var _a, _b;
1609 const activeCell = this.activeCell;
1610 if (this.mode === 'edit' && activeCell) {
1611 // Test for !== true to cover hasFocus is false and editor is not yet rendered.
1612 if (((_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.hasFocus()) !== true) {
1613 if (activeCell.inViewport) {
1614 (_b = activeCell.editor) === null || _b === void 0 ? void 0 : _b.focus();
1615 }
1616 else {
1617 this.scrollToItem(this.activeCellIndex)
1618 .then(() => {
1619 void activeCell.ready.then(() => {
1620 var _a;
1621 (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.focus();
1622 });
1623 })
1624 .catch(reason => {
1625 // no-op
1626 });
1627 }
1628 }
1629 }
1630 if (force && !this.node.contains(document.activeElement)) {
1631 this.node.focus();
1632 }
1633 }
1634 /**
1635 * Find the cell index containing the target html element.
1636 *
1637 * #### Notes
1638 * Returns -1 if the cell is not found.
1639 */
1640 _findCell(node) {
1641 // Trace up the DOM hierarchy to find the root cell node.
1642 // Then find the corresponding child and select it.
1643 let n = node;
1644 while (n && n !== this.node) {
1645 if (n.classList.contains(NB_CELL_CLASS)) {
1646 const i = ArrayExt.findFirstIndex(this.widgets, widget => widget.node === n);
1647 if (i !== -1) {
1648 return i;
1649 }
1650 break;
1651 }
1652 n = n.parentElement;
1653 }
1654 return -1;
1655 }
1656 /**
1657 * Find the target of html mouse event and cell index containing this target.
1658 *
1659 * #### Notes
1660 * Returned index is -1 if the cell is not found.
1661 */
1662 _findEventTargetAndCell(event) {
1663 let target = event.target;
1664 let index = this._findCell(target);
1665 if (index === -1) {
1666 // `event.target` sometimes gives an orphaned node in Firefox 57, which
1667 // can have `null` anywhere in its parent line. If we fail to find a cell
1668 // using `event.target`, try again using a target reconstructed from the
1669 // position of the click event.
1670 target = document.elementFromPoint(event.clientX, event.clientY);
1671 index = this._findCell(target);
1672 }
1673 return [target, index];
1674 }
1675 /**
1676 * Find heading with given ID in any of the cells.
1677 */
1678 async _findHeading(queryId) {
1679 // Loop on cells, get headings and search for first matching id.
1680 for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
1681 const cell = this.widgets[cellIdx];
1682 if (cell.model.type === 'raw' ||
1683 (cell.model.type === 'markdown' && !cell.rendered)) {
1684 // Bail early
1685 continue;
1686 }
1687 for (const heading of cell.headings) {
1688 let id = '';
1689 switch (heading.type) {
1690 case Cell.HeadingType.HTML:
1691 id = heading.id;
1692 break;
1693 case Cell.HeadingType.Markdown:
1694 {
1695 const mdHeading = heading;
1696 id = await TableOfContentsUtils.Markdown.getHeadingId(this.rendermime.markdownParser, mdHeading.raw, mdHeading.level);
1697 }
1698 break;
1699 }
1700 if (id === queryId) {
1701 const element = this.node.querySelector(`h${heading.level}[id="${id}"]`);
1702 return {
1703 cell,
1704 element
1705 };
1706 }
1707 }
1708 }
1709 return null;
1710 }
1711 /**
1712 * Find cell by its unique ID.
1713 */
1714 _findCellById(queryId) {
1715 for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
1716 const cell = this.widgets[cellIdx];
1717 if (cell.model.id === queryId) {
1718 return {
1719 cell
1720 };
1721 }
1722 }
1723 return null;
1724 }
1725 /**
1726 * Handle `contextmenu` event.
1727 */
1728 _evtContextMenuCapture(event) {
1729 var _a;
1730 // Allow the event to propagate un-modified if the user
1731 // is holding the shift-key (and probably requesting
1732 // the native context menu).
1733 if (event.shiftKey) {
1734 return;
1735 }
1736 const [target, index] = this._findEventTargetAndCell(event);
1737 const widget = this.widgets[index];
1738 if (widget && ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
1739 // Prevent CodeMirror from focusing the editor.
1740 // TODO: find an editor-agnostic solution.
1741 event.preventDefault();
1742 }
1743 }
1744 /**
1745 * Handle `mousedown` event in the capture phase for the widget.
1746 */
1747 _evtMouseDownCapture(event) {
1748 var _a;
1749 const { button, shiftKey } = event;
1750 const [target, index] = this._findEventTargetAndCell(event);
1751 const widget = this.widgets[index];
1752 // On OS X, the context menu may be triggered with ctrl-left-click. In
1753 // Firefox, ctrl-left-click gives an event with button 2, but in Chrome,
1754 // ctrl-left-click gives an event with button 0 with the ctrl modifier.
1755 if (button === 2 &&
1756 !shiftKey &&
1757 widget &&
1758 ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
1759 this.mode = 'command';
1760 // Prevent CodeMirror from focusing the editor.
1761 // TODO: find an editor-agnostic solution.
1762 event.preventDefault();
1763 }
1764 }
1765 /**
1766 * Handle `mousedown` events for the widget.
1767 */
1768 _evtMouseDown(event) {
1769 var _a, _b, _c;
1770 const { button, shiftKey } = event;
1771 // We only handle main or secondary button actions.
1772 if (!(button === 0 || button === 2)) {
1773 return;
1774 }
1775 // Shift right-click gives the browser default behavior.
1776 if (shiftKey && button === 2) {
1777 return;
1778 }
1779 const [target, index] = this._findEventTargetAndCell(event);
1780 const widget = this.widgets[index];
1781 let targetArea;
1782 if (widget) {
1783 if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target)) {
1784 targetArea = 'input';
1785 }
1786 else if ((_b = widget.promptNode) === null || _b === void 0 ? void 0 : _b.contains(target)) {
1787 targetArea = 'prompt';
1788 }
1789 else {
1790 targetArea = 'cell';
1791 }
1792 }
1793 else {
1794 targetArea = 'notebook';
1795 }
1796 // Make sure we go to command mode if the click isn't in the cell editor If
1797 // we do click in the cell editor, the editor handles the focus event to
1798 // switch to edit mode.
1799 if (targetArea !== 'input') {
1800 this.mode = 'command';
1801 }
1802 if (targetArea === 'notebook') {
1803 this.deselectAll();
1804 }
1805 else if (targetArea === 'prompt' || targetArea === 'cell') {
1806 // We don't want to prevent the default selection behavior
1807 // if there is currently text selected in an output.
1808 const hasSelection = ((_c = window.getSelection()) !== null && _c !== void 0 ? _c : '').toString() !== '';
1809 if (button === 0 &&
1810 shiftKey &&
1811 !hasSelection &&
1812 !['INPUT', 'OPTION'].includes(target.tagName)) {
1813 // Prevent browser selecting text in prompt or output
1814 event.preventDefault();
1815 // Shift-click - extend selection
1816 try {
1817 this.extendContiguousSelectionTo(index);
1818 }
1819 catch (e) {
1820 console.error(e);
1821 this.deselectAll();
1822 return;
1823 }
1824 // Enter selecting mode
1825 this._mouseMode = 'select';
1826 document.addEventListener('mouseup', this, true);
1827 document.addEventListener('mousemove', this, true);
1828 }
1829 else if (button === 0 && !shiftKey) {
1830 // Prepare to start a drag if we are on the drag region.
1831 if (targetArea === 'prompt') {
1832 // Prepare for a drag start
1833 this._dragData = {
1834 pressX: event.clientX,
1835 pressY: event.clientY,
1836 index: index
1837 };
1838 // Enter possible drag mode
1839 this._mouseMode = 'couldDrag';
1840 document.addEventListener('mouseup', this, true);
1841 document.addEventListener('mousemove', this, true);
1842 event.preventDefault();
1843 }
1844 if (!this.isSelectedOrActive(widget)) {
1845 this.deselectAll();
1846 this.activeCellIndex = index;
1847 }
1848 }
1849 else if (button === 2) {
1850 if (!this.isSelectedOrActive(widget)) {
1851 this.deselectAll();
1852 this.activeCellIndex = index;
1853 }
1854 event.preventDefault();
1855 }
1856 }
1857 else if (targetArea === 'input') {
1858 if (button === 2 && !this.isSelectedOrActive(widget)) {
1859 this.deselectAll();
1860 this.activeCellIndex = index;
1861 }
1862 }
1863 // If we didn't set focus above, make sure we get focus now.
1864 this._ensureFocus(true);
1865 }
1866 /**
1867 * Handle the `'mouseup'` event on the document.
1868 */
1869 _evtDocumentMouseup(event) {
1870 event.preventDefault();
1871 event.stopPropagation();
1872 // Remove the event listeners we put on the document
1873 document.removeEventListener('mousemove', this, true);
1874 document.removeEventListener('mouseup', this, true);
1875 if (this._mouseMode === 'couldDrag') {
1876 // We didn't end up dragging if we are here, so treat it as a click event.
1877 const [, index] = this._findEventTargetAndCell(event);
1878 this.deselectAll();
1879 this.activeCellIndex = index;
1880 // Focus notebook if active cell changes but does not have focus.
1881 if (!this.activeCell.node.contains(document.activeElement)) {
1882 this.node.focus();
1883 }
1884 }
1885 this._mouseMode = null;
1886 }
1887 /**
1888 * Handle the `'mousemove'` event for the widget.
1889 */
1890 _evtDocumentMousemove(event) {
1891 event.preventDefault();
1892 event.stopPropagation();
1893 // If in select mode, update the selection
1894 switch (this._mouseMode) {
1895 case 'select': {
1896 const target = event.target;
1897 const index = this._findCell(target);
1898 if (index !== -1) {
1899 this.extendContiguousSelectionTo(index);
1900 }
1901 break;
1902 }
1903 case 'couldDrag': {
1904 // Check for a drag initialization.
1905 const data = this._dragData;
1906 const dx = Math.abs(event.clientX - data.pressX);
1907 const dy = Math.abs(event.clientY - data.pressY);
1908 if (dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD) {
1909 this._mouseMode = null;
1910 this._startDrag(data.index, event.clientX, event.clientY);
1911 }
1912 break;
1913 }
1914 default:
1915 break;
1916 }
1917 }
1918 /**
1919 * Handle the `'lm-dragenter'` event for the widget.
1920 */
1921 _evtDragEnter(event) {
1922 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
1923 return;
1924 }
1925 event.preventDefault();
1926 event.stopPropagation();
1927 const target = event.target;
1928 const index = this._findCell(target);
1929 if (index === -1) {
1930 return;
1931 }
1932 const widget = this.cellsArray[index];
1933 widget.node.classList.add(DROP_TARGET_CLASS);
1934 }
1935 /**
1936 * Handle the `'lm-dragleave'` event for the widget.
1937 */
1938 _evtDragLeave(event) {
1939 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
1940 return;
1941 }
1942 event.preventDefault();
1943 event.stopPropagation();
1944 const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
1945 if (elements.length) {
1946 elements[0].classList.remove(DROP_TARGET_CLASS);
1947 }
1948 }
1949 /**
1950 * Handle the `'lm-dragover'` event for the widget.
1951 */
1952 _evtDragOver(event) {
1953 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
1954 return;
1955 }
1956 event.preventDefault();
1957 event.stopPropagation();
1958 event.dropAction = event.proposedAction;
1959 const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
1960 if (elements.length) {
1961 elements[0].classList.remove(DROP_TARGET_CLASS);
1962 }
1963 const target = event.target;
1964 const index = this._findCell(target);
1965 if (index === -1) {
1966 return;
1967 }
1968 const widget = this.cellsArray[index];
1969 widget.node.classList.add(DROP_TARGET_CLASS);
1970 }
1971 /**
1972 * Handle the `'lm-drop'` event for the widget.
1973 */
1974 _evtDrop(event) {
1975 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
1976 return;
1977 }
1978 event.preventDefault();
1979 event.stopPropagation();
1980 if (event.proposedAction === 'none') {
1981 event.dropAction = 'none';
1982 return;
1983 }
1984 let target = event.target;
1985 while (target && target.parentElement) {
1986 if (target.classList.contains(DROP_TARGET_CLASS)) {
1987 target.classList.remove(DROP_TARGET_CLASS);
1988 break;
1989 }
1990 target = target.parentElement;
1991 }
1992 // Model presence should be checked before calling event handlers
1993 const model = this.model;
1994 const source = event.source;
1995 if (source === this) {
1996 // Handle the case where we are moving cells within
1997 // the same notebook.
1998 event.dropAction = 'move';
1999 const toMove = event.mimeData.getData('internal:cells');
2000 // For collapsed markdown headings with hidden "child" cells, move all
2001 // child cells as well as the markdown heading.
2002 const cell = toMove[toMove.length - 1];
2003 if (cell instanceof MarkdownCell && cell.headingCollapsed) {
2004 const nextParent = NotebookActions.findNextParentHeading(cell, source);
2005 if (nextParent > 0) {
2006 const index = findIndex(source.widgets, (possibleCell) => {
2007 return cell.model.id === possibleCell.model.id;
2008 });
2009 toMove.push(...source.widgets.slice(index + 1, nextParent));
2010 }
2011 }
2012 // Compute the to/from indices for the move.
2013 let fromIndex = ArrayExt.firstIndexOf(this.widgets, toMove[0]);
2014 let toIndex = this._findCell(target);
2015 // This check is needed for consistency with the view.
2016 if (toIndex !== -1 && toIndex > fromIndex) {
2017 toIndex -= 1;
2018 }
2019 else if (toIndex === -1) {
2020 // If the drop is within the notebook but not on any cell,
2021 // most often this means it is past the cell areas, so
2022 // set it to move the cells to the end of the notebook.
2023 toIndex = this.widgets.length - 1;
2024 }
2025 // Don't move if we are within the block of selected cells.
2026 if (toIndex >= fromIndex && toIndex < fromIndex + toMove.length) {
2027 return;
2028 }
2029 // Move the cells one by one
2030 this.moveCell(fromIndex, toIndex, toMove.length);
2031 }
2032 else {
2033 // Handle the case where we are copying cells between
2034 // notebooks.
2035 event.dropAction = 'copy';
2036 // Find the target cell and insert the copied cells.
2037 let index = this._findCell(target);
2038 if (index === -1) {
2039 index = this.widgets.length;
2040 }
2041 const start = index;
2042 const values = event.mimeData.getData(JUPYTER_CELL_MIME);
2043 // Insert the copies of the original cells.
2044 // We preserve trust status of pasted cells by not modifying metadata.
2045 model.sharedModel.insertCells(index, values);
2046 // Select the inserted cells.
2047 this.deselectAll();
2048 this.activeCellIndex = start;
2049 this.extendContiguousSelectionTo(index - 1);
2050 }
2051 }
2052 /**
2053 * Start a drag event.
2054 */
2055 _startDrag(index, clientX, clientY) {
2056 var _a;
2057 const cells = this.model.cells;
2058 const selected = [];
2059 const toMove = [];
2060 let i = -1;
2061 for (const widget of this.widgets) {
2062 const cell = cells.get(++i);
2063 if (this.isSelectedOrActive(widget)) {
2064 widget.addClass(DROP_SOURCE_CLASS);
2065 selected.push(cell.toJSON());
2066 toMove.push(widget);
2067 }
2068 }
2069 const activeCell = this.activeCell;
2070 let dragImage = null;
2071 let countString;
2072 if ((activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.type) === 'code') {
2073 const executionCount = activeCell.model
2074 .executionCount;
2075 countString = ' ';
2076 if (executionCount) {
2077 countString = executionCount.toString();
2078 }
2079 }
2080 else {
2081 countString = '';
2082 }
2083 // Create the drag image.
2084 dragImage = Private.createDragImage(selected.length, countString, (_a = activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.sharedModel.getSource().split('\n')[0].slice(0, 26)) !== null && _a !== void 0 ? _a : '');
2085 // Set up the drag event.
2086 this._drag = new Drag({
2087 mimeData: new MimeData(),
2088 dragImage,
2089 supportedActions: 'copy-move',
2090 proposedAction: 'copy',
2091 source: this
2092 });
2093 this._drag.mimeData.setData(JUPYTER_CELL_MIME, selected);
2094 // Add mimeData for the fully reified cell widgets, for the
2095 // case where the target is in the same notebook and we
2096 // can just move the cells.
2097 this._drag.mimeData.setData('internal:cells', toMove);
2098 // Add mimeData for the text content of the selected cells,
2099 // allowing for drag/drop into plain text fields.
2100 const textContent = toMove
2101 .map(cell => cell.model.sharedModel.getSource())
2102 .join('\n');
2103 this._drag.mimeData.setData('text/plain', textContent);
2104 // Remove mousemove and mouseup listeners and start the drag.
2105 document.removeEventListener('mousemove', this, true);
2106 document.removeEventListener('mouseup', this, true);
2107 this._mouseMode = null;
2108 void this._drag.start(clientX, clientY).then(action => {
2109 if (this.isDisposed) {
2110 return;
2111 }
2112 this._drag = null;
2113 for (const widget of toMove) {
2114 widget.removeClass(DROP_SOURCE_CLASS);
2115 }
2116 });
2117 }
2118 /**
2119 * Handle `focus` events for the widget.
2120 */
2121 _evtFocusIn(event) {
2122 var _a;
2123 const target = event.target;
2124 const index = this._findCell(target);
2125 if (index !== -1) {
2126 const widget = this.widgets[index];
2127 // If the editor itself does not have focus, ensure command mode.
2128 if (widget.editorWidget && !widget.editorWidget.node.contains(target)) {
2129 this.mode = 'command';
2130 }
2131 this.activeCellIndex = index;
2132 // If the editor has focus, ensure edit mode.
2133 const node = (_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node;
2134 if (node === null || node === void 0 ? void 0 : node.contains(target)) {
2135 this.mode = 'edit';
2136 }
2137 this.activeCellIndex = index;
2138 }
2139 else {
2140 // No cell has focus, ensure command mode.
2141 this.mode = 'command';
2142 }
2143 }
2144 /**
2145 * Handle `focusout` events for the notebook.
2146 */
2147 _evtFocusOut(event) {
2148 var _a;
2149 const relatedTarget = event.relatedTarget;
2150 // Bail if the window is losing focus, to preserve edit mode. This test
2151 // assumes that we explicitly focus things rather than calling blur()
2152 if (!relatedTarget) {
2153 return;
2154 }
2155 // Bail if the item gaining focus is another cell,
2156 // and we should not be entering command mode.
2157 const index = this._findCell(relatedTarget);
2158 if (index !== -1) {
2159 const widget = this.widgets[index];
2160 if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(relatedTarget)) {
2161 return;
2162 }
2163 }
2164 // Otherwise enter command mode if not already.
2165 if (this.mode !== 'command') {
2166 this.mode = 'command';
2167 // Switching to command mode currently focuses the notebook element, so
2168 // refocus the relatedTarget so the focus actually switches as intended.
2169 if (relatedTarget) {
2170 relatedTarget.focus();
2171 }
2172 }
2173 }
2174 /**
2175 * Handle `dblclick` events for the widget.
2176 */
2177 _evtDblClick(event) {
2178 const model = this.model;
2179 if (!model) {
2180 return;
2181 }
2182 this.deselectAll();
2183 const [target, index] = this._findEventTargetAndCell(event);
2184 if (event.target.classList.contains(HEADING_COLLAPSER_CLASS)) {
2185 return;
2186 }
2187 if (index === -1) {
2188 return;
2189 }
2190 this.activeCellIndex = index;
2191 if (model.cells.get(index).type === 'markdown') {
2192 const widget = this.widgets[index];
2193 widget.rendered = false;
2194 }
2195 else if (target.localName === 'img') {
2196 target.classList.toggle(UNCONFINED_CLASS);
2197 }
2198 }
2199 /**
2200 * Remove selections from inactive cells to avoid
2201 * spurious cursors.
2202 */
2203 _trimSelections() {
2204 for (let i = 0; i < this.widgets.length; i++) {
2205 if (i !== this._activeCellIndex) {
2206 const cell = this.widgets[i];
2207 if (!cell.model.isDisposed && cell.editor) {
2208 cell.model.selections.delete(cell.editor.uuid);
2209 }
2210 }
2211 }
2212 }
2213 _updateSelectedCells() {
2214 this._selectedCells = this.widgets.filter(cell => this.isSelectedOrActive(cell));
2215 }
2216}
2217/**
2218 * The namespace for the `Notebook` class statics.
2219 */
2220(function (Notebook) {
2221 /**
2222 * The default implementation of a notebook content factory..
2223 *
2224 * #### Notes
2225 * Override methods on this class to customize the default notebook factory
2226 * methods that create notebook content.
2227 */
2228 class ContentFactory extends StaticNotebook.ContentFactory {
2229 }
2230 Notebook.ContentFactory = ContentFactory;
2231})(Notebook || (Notebook = {}));
2232/**
2233 * A namespace for private data.
2234 */
2235var Private;
2236(function (Private) {
2237 /**
2238 * An attached property for the selected state of a cell.
2239 */
2240 Private.selectedProperty = new AttachedProperty({
2241 name: 'selected',
2242 create: () => false
2243 });
2244 /**
2245 * A custom panel layout for the notebook.
2246 */
2247 class NotebookPanelLayout extends PanelLayout {
2248 /**
2249 * A message handler invoked on an `'update-request'` message.
2250 *
2251 * #### Notes
2252 * This is a reimplementation of the base class method,
2253 * and is a no-op.
2254 */
2255 onUpdateRequest(msg) {
2256 // This is a no-op.
2257 }
2258 }
2259 Private.NotebookPanelLayout = NotebookPanelLayout;
2260 /**
2261 * Create a cell drag image.
2262 */
2263 function createDragImage(count, promptNumber, cellContent) {
2264 if (count > 1) {
2265 if (promptNumber !== '') {
2266 return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
2267 }
2268 else {
2269 return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
2270 }
2271 }
2272 else {
2273 if (promptNumber !== '') {
2274 return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
2275 }
2276 else {
2277 return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
2278 }
2279 }
2280 }
2281 Private.createDragImage = createDragImage;
2282})(Private || (Private = {}));
2283//# sourceMappingURL=widget.js.map
\No newline at end of file